    /**
    * o------------------------------------------------------------------------------o
    * | This file is part of the RGraph package - you can learn more at:             |
    * |                                                                              |
    * |                          http://www.rgraph.org                               |
    * |                                                                              |
    * | This package is licensed under the RGraph license. A quick summary is        |
    * | that the code is free to use for non-commercial purposes. For commercial     |
    * | purposes there is a small license fee to pay. You can read more at:          |
    * |                                                                              |
    * |                      http://www.rgraph.org/LICENSE.txt                       |
    * o------------------------------------------------------------------------------o
    *
    * © Copyright 2008,2009 Richard Heyes
    */

    /**
    * The line chart constructor
    * 
    * @param object canvas The cxanvas object
    * @param array  data   The chart data
    * @param array  ...    Other lines to plot
    */
    function Line(id)
    {
        // Get the canvas and context objects
        this.id      = id;
        this.canvas  = $(id);
        this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null;
        this.canvas.__object__ = this;
        this.type              = 'line';
        this.max               = 0;
        this.coords            = [];
        this.hasnegativevalues = false;

        // Various config type stuff
        this.properties                    = [];
        this.properties['barcolor1']       = '#dce5fe';
        this.properties['barcolor2']       = '#f6f6f6';
        this.properties['backgroundgrid']  = 1;
        this.properties['gridwidth']       = 1;
        this.properties['horgridsize']     = 25;
        this.properties['vergridsize']     = 25;
        this.properties['gridcolor']       = '#eee';
        this.properties['xtickgap']        = 20;
        this.properties['smallxticks']     = 3;
        this.properties['largexticks']     = 5;
        this.properties['ytickgap']        = 20;
        this.properties['smallyticks']     = 3;
        this.properties['largeyticks']     = 5;
        this.properties['linewidth']       = 1;
        this.properties['colors']          = ['#f00', '#0f0', '#00f', '#f0f', '#ff0', '#0ff'];
        this.properties['bordercolor']     = '#090';
        this.properties['hmargin']         = 0;
        this.properties['tickmarks']       = 'circle';
        this.properties['ticksize']        = 3;
        this.properties['gutter']          = 20;
        this.properties['tickdirection']   = -1;
        this.properties['yaxispoints']      = 5;
        this.properties['filled']           = false;
        this.properties['xaxispos']         = 'bottom';
        this.properties['textheight']       = 10;
        this.properties['ymax']             = null;
        this.properties['title']            = '';
        this.properties['shadow']           = false;
        this.properties['shadowoffset']     = 1;
        this.properties['shadowcolor']      = 'rgba(80,80,80,0.5)';
        this.properties['tooltips']         = null;
        this.properties['stepped']          = false;
        this.properties['key']              = [];
        this.properties['keybackground']    = '#fff';
        this.properties['keyposition']      = 'graph';
        this.properties['contextmenu']      = null;
        this.properties['textangle']        = 0;
        this.properties['ylabels']          = true;
        this.properties['noaxes']           = false;
        this.properties['units.post']       = '';
        this.properties['units.pre']        = '';
        this.properties['hbars']            = null;

        // Get all the sets of data we were provided and put them in this.data
        // They're added in reverse order to make the public interface correspond with itself
        this.data = new Array();
        for (var i=(arguments.length - 1); i>=1; --i) {
            this.data.push(arguments[i]);
        }

        // Check the common library has been included
        if (typeof(RGraph) == 'undefined') {
            alert('[LINE] Fatal error: The common library does not appear to have been included');
        }

        // Store the original data
        this.original_data = [];

        for (var i=1; i<arguments.length; ++i) {
            this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
        }

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


    /**
    * An all encompassing accessor
    * 
    * @param string name The name of the property
    * @param mixed value The value of the property
    */
    Line.prototype.Set = function (name, value)
    {
        // Consolidate the tooltips
        if (name.toLowerCase() == 'tooltips') {
            this.properties['tooltips'] = [];

            for (var i=1; i<arguments.length; i++) {
                for (j=0; j<arguments[i].length; j++) {
                    this.Get('tooltips').push(arguments[i][j]);
                }
            }
        }

        this.properties[name.toLowerCase()] = value;
    }


    /**
    * An all encompassing accessor
    * 
    * @param string name The name of the property
    */
    Line.prototype.Get = function (name)
    {
        return this.properties[name.toLowerCase()];
    }


    /**
    * The function you call to draw the line chart
    */
    Line.prototype.Draw = function ()
    {
        // Reset the data back to that which was initially supplied
        this.data = RGraph.array_clone(this.original_data);

        /**
        * Check for tooltips AND context menus. You can't have both at once
        */
        if (this.Get('tooltips') && this.Get('tooltips').length && this.Get('contextmenu')) {
            alert('[LINE] (' + this.id + ') You cannot have both context menus and tooltips at the same time. Turning them BOTH off');
            this.Set('tooltips', null);
            this.Set('contextmenu', null);
        }

        /**
        * Number of tooltips must match the number of datapoints
        */
        if (this.Get('labels') != null && this.Get('labels').length) {
            for (i in this.data) {
                if (this.data[i].length != this.Get('labels').length) {
                    alert('[LINE] (' + this.id + ') Number of datapoints does not match number of labels, turning off labels');
                    this.Set('labels', []);
                }
            }
        }

        if (this.Get('filled') && this.data.length > 1) {
        
            var accumulation = [];
        
            for (var set in this.data) {
                for (var point in this.data[set]) {
                    this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
                    accumulation[point] = this.data[set][point];
                }
            }
        }

        this.max = this.Get('ymax');

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

                this.scale = RGraph.getScale(Math.max(this.max, Math.abs(parseFloat(this.data[dataset][datapoint]))));

                this.max   = this.scale[4] ? this.scale[4] : 0;

                // Check for negative values
                this.hasnegativevalues = (this.data[dataset][datapoint] < 0);
            }
        }

        /**
        * Setup the context menu if required
        */
        RGraph.ShowContext(this);

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

        /**
        * 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      = this.canvas.height - ( (2 * this.Get('gutter')));
        this.halfgrapharea  = this.grapharea / 2;
        this.halfTextHeight = this.Get('textheight') / 2;

        // Check the combination of the X axis position and if there any negative values
        if (this.Get('xaxispos') == 'bottom' && this.hasnegativevalues) {
            alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
        }

        // Set various things based on the config values
        this.canvas.style.border = this.Get('bordercolor');
        
        // Progressively Draw the chart
        RGraph.background.Draw(this);

        /**
        * Draw any horizontal bars that have been defined
        */
        if (this.Get('hbars') && this.Get('hbars').length > 0) {
            RGraph.DrawBars(this);
        }

        // Draw the axes if the graph is not filled
        if (!this.Get('filled')) {
            this.DrawAxes();
        }

        for (var i=(this.data.length - 1), j=0; i>=0; i--, j++) {

            this.context.beginPath();
            this.DrawLine(this.data[i], this.Get('colors')[j]);

            this.context.stroke();
        }

        // If the graph is filled, draw the axes again so they appear as though they're "on top"
        if (this.Get('filled')) {
            this.DrawAxes();
        }

        this.DrawLabels();
        
        // Draw a key if necessary
        if (this.Get('key').length) {
            RGraph.DrawKey(this, this.Get('key'), this.Get('colors'));
        }


        /**
        * Now draw some text off canvas to prevent a bug in the text library becoming apparent
        * It might actually not be a bug in the text libaray, but ho-hum
        * 
        * FIXME: When rgraph is converted over to native text functions, this wil probably not be needed
        */
        RGraph.Text(this.context, 'sans', 2, this.canvas.width + 50, this.canvas.height + 50, '.');
    }

    
    /**
    * Draws the axes
    */
    Line.prototype.DrawAxes = function ()
    {
        // Don't draw the axes?
        if (this.Get('noaxes')) {
            return;
        }

        this.context.lineWidth = 1;
        this.context.beginPath();
        this.context.strokeStyle = '#000';

        // Draw the X axis
        if (this.Get('xaxispos') == 'center') {
            this.context.moveTo(this.Get('gutter'), this.grapharea / 2 + this.Get('gutter'));
            this.context.lineTo(this.canvas.width - this.Get('gutter'), this.grapharea / 2 + this.Get('gutter'));
        } else {
            this.context.moveTo(this.Get('gutter'), this.canvas.height - this.Get('gutter'));
            this.context.lineTo(this.canvas.width - this.Get('gutter'), this.canvas.height - this.Get('gutter'));
        }
        
        // Draw the Y axis
        this.context.moveTo(this.Get('gutter'), this.Get('gutter'));
        this.context.lineTo(this.Get('gutter'), this.canvas.height - (this.Get('gutter')) );
        
        // Draw the X tickmarks

        var xTickInterval = (this.canvas.width - (2 * this.Get('gutter'))) / this.data[0].length;

        for (x=this.Get('gutter') + xTickInterval; x<=(this.canvas.width - this.Get('gutter') + 1 /* Not sure why this 1 is neccessary */ ); x+=xTickInterval) {

            var yStart = this.Get('xaxispos') == 'center' ? (this.canvas.height / 2) - 3 : this.canvas.height - this.Get('gutter');
            var yEnd = this.Get('xaxispos') == 'center' ? yStart + 6 : this.canvas.height - this.Get('gutter') - (x % 60 == 0 ? this.Get('largexticks') * this.Get('tickdirection') : this.Get('smallxticks') * this.Get('tickdirection'));

            this.context.moveTo(x, yStart);
            this.context.lineTo(x, yEnd);
        }

        // Draw the Y tickmarks
        var counter = 0;
        if (this.Get('xaxispos') == 'center') {
            var interval = (this.grapharea / 10);

            // Draw the upper halves Y tick marks
            for (y=this.Get('gutter'); y < (this.grapharea / 2) + this.Get('gutter'); y+=interval) {
                this.context.moveTo(this.Get('gutter') - 3, y);
                this.context.lineTo(this.Get('gutter'), y);
            }
            
            // Draw the lower halves Y tick marks
            for (y=this.Get('gutter') + (this.halfgrapharea) + interval; y <= this.grapharea + this.Get('gutter'); y+=interval) {
                this.context.moveTo(this.Get('gutter') - 3, y);
                this.context.lineTo(this.Get('gutter'), y);
            }

        } else {
            for (y=this.Get('gutter'); y < (this.canvas.height - this.Get('gutter')) && counter <= 9; y+=( (this.canvas.height - (2 * this.Get('gutter'))) / 10) ) {
                this.context.moveTo(this.Get('gutter'), y);
                this.context.lineTo(this.Get('gutter') - this.Get('smallyticks'), y);
            
                var counter = counter +1;
            }
        }

        this.context.stroke();
    }


    /**
    * Draw the text labels for the axes
    */
    Line.prototype.DrawLabels = function ()
    {
        // Black text
        this.context.strokeStyle = '#000';
        this.context.lineWidth   = 1;

        // Draw the Y axis labels
        if (this.Get('ylabels')) {
            if (this.Get('xaxispos') == 'center') {
                var half = this.grapharea / 2;
    
                //  Draw the upper halves labels
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (0/5) * half ) + this.halfTextHeight, RGraph.number_format(this.scale[4], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (1/5) * half ) + this.halfTextHeight, RGraph.number_format(this.scale[3], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (2/5) * half ) + this.halfTextHeight, RGraph.number_format(this.scale[2], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (3/5) * half ) + this.halfTextHeight, RGraph.number_format(this.scale[1], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (4/5) * half ) + this.halfTextHeight, RGraph.number_format(this.scale[0], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                
                //  Draw the lower halves labels
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (6/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this.scale[0], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (7/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this.scale[1], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (8/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this.scale[2], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (9/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this.scale[3], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + ( (10/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format( (this.scale[4] == '1.0' ? '1.0' : this.scale[4]), this.Get('units.pre'), this.Get('units.post')), null, 'right');
    
            } else {
    
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + this.halfTextHeight + ((0/5) * (this.canvas.height - (2 * this.Get('gutter')) ) ), RGraph.number_format(this.scale[4], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + this.halfTextHeight + ((1/5) * (this.canvas.height - (2 * this.Get('gutter')) ) ), RGraph.number_format(this.scale[3], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + this.halfTextHeight + ((2/5) * (this.canvas.height - (2 * this.Get('gutter')) ) ), RGraph.number_format(this.scale[2], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + this.halfTextHeight + ((3/5) * (this.canvas.height - (2 * this.Get('gutter')) ) ), RGraph.number_format(this.scale[1], this.Get('units.pre'), this.Get('units.post')), null, 'right');
                RGraph.Text(this.context, 'sans', this.Get('textheight'), this.Get('gutter') - 5, this.Get('gutter') + this.halfTextHeight + ((4/5) * (this.canvas.height - (2 * this.Get('gutter')) ) ), RGraph.number_format(this.scale[0], this.Get('units.pre'), this.Get('units.post')), null, 'right');
            }


            /**
            * Now draw some text off canvas to prevent a bug in the text library becoming apparent
            * It might actually not be a bug in the text libaray, but ho-hum.
            * 
            * FIXME: When rgraph is converted over to native text functions, this wil probably not be needed
            */
            RGraph.Text(this.context, 'sans', 2, this.canvas.width + 50, this.canvas.height + 50, '.');
        }

        // Draw the X axis labels
        if (this.Get('labels')) {
            
            var yOffset = 13;

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

            if (this.Get('textangle') == 45 || this.Get('textangle') == 90) {
                angle   = -1 * this.Get('textangle');
                valign  = 'center';
                halign  = 'right';
                yOffset = 5
            }

            this.context.strokeStyle = '#000';

            for (i in this.Get('labels')) {
                var labelX = this.coords[i][0];

                RGraph.Text(this.context, 'sans',
                                      this.Get('textheight'),
                                      labelX,
                                      (this.canvas.height - this.Get('gutter')) + yOffset,
                                      String(this.Get('labels')[i]),
                                      valign,
                                      halign,
                                      null,
                                      angle);
            }
            
            
            /**
            * This fixes a bug where tooltips are defined and the graph gets redrawn. The last letter gets "rewitten".
            * Bizarre, and rather obscure. Hence this work around. The last letter is now drawn off-canvas, and hence
            * can't be seen, so it doesn't really matter if it's redrawn.
            */
            RGraph.Text(this.context, 'sans', 10, this.canvas.width + 50, this.canvas.height + 50, '.');

        } else if (this.Get('labels')) {
                alert('[LINE] Number of labels does not match the number of data points');
        }
        
        this.context.stroke();
    }


    /**
    * Draws the line
    */
    Line.prototype.DrawLine = function (lineData, color)
    {
        var yPos = 0;
        var xPos = 0;
        this.context.lineWidth = this.Get('linewidth');
        var lineCoords = [];


        // Use yMax if it's defined
        if (this.Get('ymax')) {
            this.max = this.Get('ymax');
        }

        // Work out the X interval
        var xInterval = (this.canvas.width - (2 * this.Get('hmargin')) - ( (2 * this.Get('gutter')) + (this.Get('filled') ? 0 : 0)) ) / (lineData.length - 1);

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

            yPos  = this.canvas.height - ( (lineData[i] / this.max) * ((this.canvas.height - (2 * this.Get('gutter'))) ));

            // Make adjustments depending on the X axis position
            if (this.Get('xaxispos') == 'center') {
                yPos /= 2;
            } else if (this.Get('xaxispos') == 'bottom') {
                yPos -= this.Get('gutter'); // Without this the line is out of place due to the gutter
            }
            
            // Not very noticeable, but it does have an effect
            // It's more noticeable with a thick linewidth
            this.context.lineCap  = 'round';
            this.context.lineJoin = 'round';

            // Plot the line if we're at least on the second iteration
            if (i > 0) {
                xPos = xPos + xInterval;
            } else {
                xPos = this.Get('hmargin') + this.Get('gutter');
            }
            
            /**
            * Add the coords to an array
            */
            this.coords.push([xPos, yPos]);
            lineCoords.push([xPos, yPos]);
        }
        
        this.context.stroke();


        /**
        * Draw the shadow if we need to [FIRST]
        */
        if (this.Get('shadow') && !this.Get('filled')) {

            this.context.fillStyle = this.Get('shadowcolor');
            this.context.strokeStyle = this.Get('shadowcolor');
            this.context.beginPath();

            for (i in lineCoords) {
                
                i = Number(i);

                if (i == 0) {
                    this.context.moveTo(lineCoords[i][0] + this.Get('shadowoffset'), lineCoords[i][1] + this.Get('shadowoffset'))
                } else {

                    this.context.lineTo(lineCoords[i][0] + this.Get('shadowoffset'), lineCoords[i][1] + this.Get('shadowoffset'));

                }

                if (this.Get('stepped') && lineCoords[i + 1]) {
                    this.context.lineTo(lineCoords[i + 1][0] + this.Get('shadowoffset'), lineCoords[i][1] + this.Get('shadowoffset'));
                }
            }

            this.context.stroke();
            
            // Draw the tick shadows
            for (i in lineCoords) {
                i = Number(i);

                if (this.Get('tickmarks') != 'dot' && ((this.Get('tickmarks') != 'endcircle' && this.Get('tickmarks') != 'endsquare') || i == 0 || i == (lineCoords.length - 1))) {
                    var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
                    var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
                    this.DrawTick(lineCoords[i][0] + this.Get('shadowoffset'), lineCoords[i][1] + this.Get('shadowoffset'), this.Get('shadowcolor'), true, prevX + this.Get('shadowoffset'), prevY + this.Get('shadowoffset'));

                    if (this.Get('stepped') && lineCoords[i + 1] && (this.Get('tickmarks') != 'endcircle' && this.Get('tickmarks') != 'endsquare') ) {
                        this.DrawTick(lineCoords[i + 1][0] + this.Get('shadowoffset'), lineCoords[i][1] + this.Get('shadowoffset'), this.Get('shadowcolor'), true);
                    }
                    
                    if (this.Get('tickmarks') == 'circle') {
                        this.context.fill();
                    }
                }
            }

        } else if (this.Get('filled') && this.Get('shadow')) {
            alert('[LINE] Shadows are not permitted when the line is filled');
        }

        /**
        * Now draw the actual line [SECOND]
        */
        this.context.beginPath();
        this.context.strokeStyle = color;
        this.context.lineWidth = this.Get('linewidth');
    
        for (i in lineCoords) {
            xPos = lineCoords[i][0];
            yPos = lineCoords[i][1];

            if (i == 0) {
                if (this.Get('filled')) {
                    this.context.moveTo(xPos, this.canvas.height - this.Get('gutter'));
                    this.context.lineTo(xPos, yPos);
                } else {
                    this.context.moveTo(xPos, yPos);
                }
            } else {
                if (this.Get('stepped')) {
                    this.context.lineTo(xPos, lineCoords[i - 1][1]);
                }

                this.context.lineTo(xPos, yPos);
            }

            this.context.stroke();
        }

        if (this.Get('filled')) {
            this.context.lineTo(xPos, this.canvas.height - this.Get('gutter'));
            this.context.fillStyle = color;
            this.context.fill();
        }

        // Draw the actual tickmarks
        for (i in lineCoords) {
            i = Number(i);

            if ( (this.Get('tickmarks') != 'endcircle' && this.Get('tickmarks') != 'endsquare') || i == 0 || i == (lineCoords.length - 1)) {
                var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
                var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
                this.DrawTick(lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY);

                if (this.Get('stepped') && lineCoords[i + 1] && this.Get('tickmarks') != 'endsquare' && this.Get('tickmarks') != 'endcircle') {
                    this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
                }
            }
        }
        
        // Draw something off canvas to skirt an annoying bug
        this.context.beginPath();
        this.context.arc(this.canvas.width + 50, this.canvas.height + 50, 2, 0, 6.38, 1);

        /**
        * If TOOLTIPS are defined, handle them
        */
        if (this.Get('tooltips') && this.Get('tooltips').length) {
        
            // Need to register this object for redrawing
            RGraph.Register(this);

            this.canvas.onmousemove = function (e)
            {
                var canvas  = e.currentTarget;
                var context = canvas.getContext('2d');
                var obj     = canvas.__object__;

                RGraph.Register(obj);

                var mouseCoords = RGraph.getMouseXY(e);
                var mouseX      = mouseCoords[0];
                var mouseY      = mouseCoords[1];
                
                /**
                * Absolute positioning on the canvas object directly
                */

                if (canvas.style.position == 'absolute') {
                    if (canvas.style.left) {
                        mouseX += parseInt(canvas.style.left);
                    }
                    
                    if (canvas.style.top) {
                         mouseY += parseInt(canvas.style.top);
                    }
                }


                for (i in obj.coords) {
                    
                    var idx = i;
                    var xCoord = obj.coords[i][0];
                    var yCoord = obj.coords[i][1];
                    if (
                           mouseX <= xCoord + 5
                        && mouseX >= xCoord - 5
                        && mouseY <= yCoord + 5
                        && mouseY >= yCoord - 5
                        && obj.Get('tooltips')[i]
                       ) {

                        /**
                        * If the tooltip is the same one as is currently visible (going by the array index), don't do squat and return.
                        */
                        if (RGraph.Registry.Get('tooltip') && RGraph.Registry.Get('tooltip').__index__ == idx) {
                            return;
                        }

                       // Redraw the graph
                        RGraph.Redraw();

                        // SHOW THE CORRECT TOOLTIP
                        RGraph.Tooltip(canvas, obj.Get('tooltips')[idx], e.pageX, e.pageY);
                        
                        // Store the tooltip index on the tooltip object
                        RGraph.Registry.Get('tooltip').__index__ = Number(idx);

                        // Draw a circle at the correct point
                        context.moveTo(xCoord, yCoord);
                        context.arc(xCoord, yCoord, 2, 0, 6.28, 0);
                        context.strokeStyle = '#999';
                        context.fillStyle = 'white';
                        context.stroke();
                        context.fill();
                        
                        e.cancelBubble = true;
                        return;
                    }
                }
            }
        }
    }
    
    
    /**
    * 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
    */
    Line.prototype.DrawTick = function (xPos, yPos, color, isShadow, prevX, prevY)
    {
        this.context.beginPath();

        var offset   = 0;

        // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
        this.context.lineWidth   = this.Get('linewidth');
        this.context.strokeStyle = isShadow ? this.Get('shadowcolor') : this.context.strokeStyle;
        this.context.fillStyle   = isShadow ? this.Get('shadowcolor') : this.context.strokeStyle;

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

            if (this.Get('tickmarks') == 'circle'|| this.Get('tickmarks') == 'filledcircle' || (this.Get('tickmarks') == 'endcircle') ) {
                this.context.beginPath();
                this.context.arc(xPos + offset, yPos + offset, this.Get('ticksize'), 0, 360 / (180 / Math.PI), false);

                if (this.Get('tickmarks') == 'filledcircle') {
                    this.context.fillStyle = isShadow ? this.Get('shadowcolor') : this.context.strokeStyle;
                } else {
                    this.context.fillStyle = isShadow ? this.Get('shadowcolor') : '#fff';
                }

                this.context.fill();
                this.context.stroke();
            }

        // "Line" style tick marks
        } else if (this.Get('tickmarks') == 'tick') {
            this.context.beginPath();
            this.context.moveTo(xPos, yPos);
            this.context.lineTo(xPos, yPos + this.Get('ticksize'));

            this.context.stroke();
        
        // "Cross" style tick marks
        } else if (this.Get('tickmarks') == 'cross') {
            this.context.beginPath();
            this.context.moveTo(xPos - this.Get('ticksize'), yPos - this.Get('ticksize'));
            this.context.lineTo(xPos + this.Get('ticksize'), yPos + this.Get('ticksize'));
            this.context.moveTo(xPos + this.Get('ticksize'), yPos - this.Get('ticksize'));
            this.context.lineTo(xPos - this.Get('ticksize'), yPos + this.Get('ticksize'));
            
            this.context.stroke();
        
        // A white bordered circle
        } else if (this.Get('tickmarks') == 'borderedcircle' || this.Get('tickmarks') == 'dot') {
                this.context.lineWidth   = 1;
                this.context.strokeStyle = '#fff';
                this.context.fillStyle   = '#fff';

                // The outer white circle
                this.context.beginPath();
                this.context.arc(xPos, yPos, this.Get('ticksize'), 0, 360 / (180 / Math.PI), false);
                this.context.closePath();


                this.context.fill();
                this.context.stroke();
                
                // Now do the inners
                this.context.beginPath();
                this.context.fillStyle   = color;
                this.context.strokeStyle = color;
                this.context.arc(xPos, yPos, this.Get('ticksize') - 2, 0, 360 / (180 / Math.PI), false);

                this.context.closePath();

                this.context.fill();
                this.context.stroke();
        
        } else if (   this.Get('tickmarks') == 'square'
                   || this.Get('tickmarks') == 'filledsquare'
                   || (this.Get('tickmarks') == 'endsquare') ) {

            this.context.fillStyle   = 'white';
            this.context.strokeStyle = this.context.strokeStyle;

            this.context.beginPath();
            this.context.strokeRect(xPos - this.Get('ticksize'), yPos - this.Get('ticksize'), this.Get('ticksize') * 2, this.Get('ticksize') * 2);

            // Fillrect
            if (this.Get('tickmarks') == 'filledsquare') {
                this.context.fillStyle = isShadow ? this.Get('shadowcolor') : this.context.strokeStyle;
                this.context.fillRect(xPos - this.Get('ticksize'), yPos - this.Get('ticksize'), this.Get('ticksize') * 2, this.Get('ticksize') * 2);

            } else if (this.Get('tickmarks') == 'square' || this.Get('tickmarks') == 'endsquare') {
                this.context.fillStyle = isShadow ? this.Get('shadowcolor') : 'white';
                this.context.fillRect((xPos - this.Get('ticksize')) + 1, (yPos - this.Get('ticksize')) + 1, (this.Get('ticksize') * 2) - 2, (this.Get('ticksize') * 2) - 2);
            }

            this.context.stroke();
            this.context.fill();
        }
    }
