    /**
    * 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
    */

    /**
    * Initialise the RGraph and registry objects
    */
    RGraph = {};
    RGraph.Registry = {};
    RGraph.background = {};
    RGraph.objects = [];
    CanvasTextFunctions = {};


    /**
    * Returns five values which are used as a nice scale
    * 
    * @param  max int The maximum value of the graph
    * @return     int The highest value in the scale
    */
    RGraph.getScale = function (max)
    {
        /**
        * Manually do decimals
        */
        if (max <= 1) {
            if (max <= 0.00005) return [0.00001,0.00002,0.00003,0.00004,0.00005];
            if (max <= 0.0001)  return [0.00002,0.00004,0.00006,0.00008,'0.00010'];
            if (max <= 0.0005)  return [0.0001,0.0002,0.0003,0.0004,0.0005];
            if (max <= 0.001)   return [0.0002,0.0004,0.0006,0.0008,'0.0010'];
            if (max <= 0.005)   return [0.001,0.002,0.003,0.004,0.005];
            if (max <= 0.01)    return [0.002,0.004,0.006,0.008,'0.010'];
            if (max <= 0.05)    return [0.01,0.02,0.03,0.04,0.05];
            if (max <= 0.1)     return [0.02,0.04,0.06,0.08,'0.10'];
            if (max <= 0.5)     return [0.1,0.2,0.3,0.4,0.5];
            if (max <= 1)       return [0.2,0.4,0.6,0.8, '1.0'];
        }

        // Take off any decimals
        if (String(max).indexOf('.') > 0) {
            max = String(max).replace(/\.\d+$/, '');
        }

        var interval = Math.pow(10, Number(String(Number(max)).length - 1));
        var topValue = interval;

        while (topValue < max) {
            topValue += (interval / 2);
        }
        
        // Custom if the max is greater than 5 and less than 10
        if (max < 10) {
            topValue = (max <= 5 ? 5 : 10);
        }

        return [topValue * (1/5), topValue * (2/5), topValue * (3/5), topValue * (4/5), topValue];
    }


    /**
    * An array_range() function. Creates and returns an array
    * which goes up to "end" index, optionally starting at "start" index
    * Acts much like the PHP function range().
    * 
    * @param  int    low  The starting value
    * @param  int    high The ending value
    * @return object      The resultant array
    */
    RGraph.array_range = function (low, high)
    {
        var arr   = new Array();
        
        for (i=low; i<=high; i++) {
            arr[i] = low++;
        }
        
        return arr;
    }


    /**
    * Returns the maximum value which is in an array
    * 
    * @param  array arr The array
    * @param  int       Whether to ignore signs (ie negative/positive)
    * @return int       The maximum value in the array
    */
    RGraph.array_max = function (arr)
    {
        var max = null;
        
        for (i in arr) {
            max = (max ? Math.max(max, arguments[1] ? Math.abs(arr[i]) : arr[i]) : arr[i]);
        }
        
        return max;
    }


    /**
    * An array sum function
    * 
    * @param  array arr The  array to calculate the total of
    * @return int       The summed total of the arrays elements
    */
    RGraph.array_sum = function (arr)
    {
        // Allow integers
        if (typeof(arr) == 'number') {
            return arr;
        }

        var i, sum;

        for(i=0,sum=0;i<arr.length;sum+=arr[i++]);
        return sum;
    }


    /**
    * Converts degrees to radians
    * 
    * @param  int degrees The number of degrees
    * @return float       The number of radians
    */
    RGraph.degrees2Radians = function (degrees)
    {
        return degrees * (Math.PI / 180);
    }


    /**
    * This function draws an angled line. The angle is cosidered to be clockwise
    * 
    * @param obj ctxt   The context object
    * @param int x      The X position
    * @param int y      The Y position
    * @param int angle  The angle in RADIANS
    * @param int length The length of the line
    */
    RGraph.lineByAngle = function (context, x, y, angle, length)
    {
        context.arc(x, y, length, angle, angle, false);
        context.lineTo(x, y);
        context.arc(x, y, length, angle, angle, false);
    }


    /**
    * This is a useful function which is basically a shortcut for drawing left, right, top and bottom alligned text.
    * 
    * @param object context The context
    * @param string font    The font. Currently only "sans" is supported
    * @param int    size    The size of the text
    * @param int    x       The X coordinate
    * @param int    y       The Y coordinate
    * @param string text    The text to draw
    * @parm  string         The vertical alignment. Can be null. "center" gives center aligned  text, "top" gives top aligned text.
    *                       Anything else produces bottom aligned text. Default is bottom.
    * @param  string        The horizontal alignment. Can be null. "center" gives center aligned  text, "right" gives right aligned text.
    *                       Anything else produces left aligned text. Default is left.
    * @param  bool          Whether to show a bounding box around the text. Defaults, (naturally), not to
    * @param int            The angle that the text should be rotate at (IN DEGREES)
    */
    RGraph.Text = function (context, font, size, x, y, text)
    {
        var origX = x;
        var origY = y;

        // Need these now the angle can be specified, ie defaults for the former two args
        if (typeof(arguments[6]) == null) arguments[6] = 'bottom'; // Vertical alignment. Default to bottom/baseline
        if (typeof(arguments[7]) == null) arguments[7] = 'left';   // Horizontal alignment. Default to left
        if (typeof(arguments[8]) == null) arguments[8] = null;     // Show a bounding box. Useful for positioning during development. Defaults to false
        if (typeof(arguments[9]) == null) arguments[9] = 0;        // Angle (IN DEGREES) that the text should be drawn at. 0 is middle right, and it goes clockwise

        // First, translate to x/y coords
        context.save();
            context.translate(x, y)
            x = 0;
            y = 0;
            
            // Rotate the canvas if need be
            if (arguments[9]) {
                context.rotate(RGraph.degrees2Radians(arguments[9]));
            }

            // Vertical alignment - defaults to bottom
            if (arguments[6]) {
                if (arguments[6] == 'center') {
                    y = size / 2;
                } else if (arguments[6] == 'top') {
                    y = size;
                }
            }


            // Hoeizontal alignment - defaults to left
            if (arguments[7]) {
                var width = CanvasTextFunctions.measure('sans', size, text);
    
                if (arguments[7]) {
                    if (arguments[7] == 'center') {
                        x -= (width / 2);
                    } else if (arguments[7] == 'right') {
                        x -= width;
                    }
                }
            }
            
            /**
            * If requested, draw a background for the text
            */
            if (arguments[10]) {
                var originalStrokeStyle = context.strokeStyle;

                context.strokeStyle = 'gray';
                context.fillStyle = arguments[10];
                context.fillRect(x - 2, y - size - 2, CanvasTextFunctions.measure('sans', size, text) + 4,size + 4);
                context.strokeRect(x - 2, y - size - 2, CanvasTextFunctions.measure('sans', size, text) + 4,size + 4);
            }
            
            
            context.strokeStyle = originalStrokeStyle;

            /**
            * Draw a bounding box if requested
            */
            context.save();
                CanvasTextFunctions.draw(context, font, size, x, y, text);
                
                // Draw the bounding box if need be
                if (arguments[8]) {
                    context.translate(x, y);
                    context.strokeRect(0, 0, CanvasTextFunctions.measure('sans', size, text), 0 - size);

                    context.fillRect(
                        arguments[7] == 'left' ? 0 : (arguments[7] == 'center' ? CanvasTextFunctions.measure('sans', size, text) / 2: CanvasTextFunctions.measure('sans', size, text) ) - 2,
                        arguments[6] == 'bottom' ? 0 : (arguments[6] == 'center' ? (0 - size) / 2 : 0 - size) - 2,
                        4,
                        4
                    );

                }
            context.restore();
        context.restore();
    }


    /**
    * CanvasText is © Copyright Jim Studt, 2007
    */


    /**
    * A big list of letter points
    */
    CanvasTextFunctions.letters =
    {
        ' ': { width: 16, points: [] },
        '!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
        '"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
        '#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
        '$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
        '£': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[3,3],[2,2],[0,0],[17,0],[-1,-1],[0,9],[12,9]] },
        '%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
        '&': { width: 26, points: [[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0]] },
        '\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
        '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
        ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
        '*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
        '+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
        '-': { width: 26, points: [[8,9],[18,9]] },
        '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
        '/': { width: 22, points: [[20,25],[2,-7]] },
        '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
        '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
        '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
        '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
        '4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
        '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
        '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
        '7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
        '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
        '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
        ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
        ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
        ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
        '<': { width: 24, points: [[20,18],[4,9],[20,0]] },
        '=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
        '>': { width: 24, points: [[4,18],[20,9],[4,0]] },
        '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
        '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
        'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
        'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
        'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
        'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
        'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
        'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
        'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
        'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
        'I': { width: 8, points: [[4,21],[4,0]] },
        'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
        'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
        'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
        'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
        'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
        'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
        'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
        'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
        'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
        'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
        'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
        'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
        'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
        'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
        'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
        'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
        'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
        '[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
        '\\': { width: 14, points: [[0,21],[14,-3]] },
        ']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
        '^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
        '_': { width: 16, points: [[0,-2],[16,-2]] },
        '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
        'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
        'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
        'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
        'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
        'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
        'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
        'l': { width: 8, points: [[4,21],[4,0]] },
        'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
        'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
        'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
        'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
        'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
        'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
        's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
        't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
        'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
        'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
        'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
        'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
        'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
        'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
        '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
        '|': { width: 8, points: [[4,25],[4,-7]] },
        '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
        '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
    }


    /**
    * Returns the points for the given letter
    * 
    * @param string ch The character to get the points for
    */
    CanvasTextFunctions.letter = function (ch)
    {
        var points = CanvasTextFunctions.letters[ch];
        return points;
    }


    /**
    * Does nothing. Simply returns the size you pass to it
    */
    CanvasTextFunctions.ascent = function( font, size)
    {
        return size;
    }


    /**
    *
    */
    CanvasTextFunctions.descent = function( font, size)
    {
        return 7.0*size/25.0;
    }

    /**
    * Measures the pixel width of the given text
    * 
    * @param string font The fon to ue. Only "sans" is supported at the moment
    * @param int    size The pixel size of the text
    * @param string str  The string to measure
    * @return       int  The pixel width of the given text
    */
    CanvasTextFunctions.measure = function( font, size, str)
    {
        var total = 0;
        var len = str.length;
    
        for ( i = 0; i < len; i++) {
            var c = CanvasTextFunctions.letter( str.charAt(i));
            if ( c) total += c.width * size / 25.0;
        }
        return total;
    }

    /**
    * The CanvasText draw() function
    * 
    * @param  object ctx  The context
    * @param  string font The font to use. Only "sans" is currently supported
    * @param  int    size The pixel size of the text
    * @param  int    x    The lower left X coord of the text
    * @param  int    y    The lower left Y coord of the text
    * @param  string str  The text to draw
    * @return int         0. Always returns zero.
    */
    CanvasTextFunctions.draw = function(ctx,font,size,x,y,str)
    {
        var total = 0;
        var len = str.length;
        var mag = size / 25.0;
    
        ctx.save();
        ctx.lineCap = "round";
        ctx.lineWidth = 2.0 * mag;
    
        for ( i = 0; i < len; i++) {
            var letter = str.charAt(i);
            var c = CanvasTextFunctions.letter( letter);

            if ( !c) {
                continue;
            }

            ctx.beginPath();

            var penUp = 1;
            var needStroke = 0;

            for ( j = 0; j < c.points.length; j++) {
                var a = c.points[j];
    
                if ( a[0] == -1 && a[1] == -1) {
                penUp = 1;
                continue;
                }
                if ( penUp) {
                    ctx.moveTo( x + a[0]*mag, y - a[1]*mag);
                    penUp = false;
                } else {
                    ctx.lineTo( x + a[0]*mag, y - a[1]*mag);
                }
            }
            ctx.stroke();
            x += c.width*mag;
        }

        ctx.restore();
        return total;
    }


    /**
    * Clears the canvas by drawing a filled rect over the top of it. You can specify a colour if you wish,
    * otherwise it defaults to white.
    * 
    * @param object canvas The canvas to clear
    */
    RGraph.Clear = function (canvas)
    {
        context = canvas.getContext('2d');
        context.stroke();
        context.fill();

        // Set the colour
        context.fillStyle = arguments[1] ? arguments[1] : '#fff';
        
        
        // Draw the rectangle
        context.fillRect(0,0,canvas.width, canvas.height);
        
        context.fill();
    }


    /**
    * Draws the title of the graph
    * 
    * @param object  canvas The canvas object
    * @param string  text   The title to write
    * @param integer gutter The size of the gutter
    * @param integer        The center X point (optional - if not given it will be generated from the canvas width)
    * @param integer        Size of the text. If not given it will be 12
    */
    RGraph.DrawTitle = function (canvas, text, gutter)
    {
        var context = canvas.getContext('2d');
        var size    = arguments[4] ? arguments[4] : 12;
        var centerx = (arguments[3] ? arguments[3] : canvas.width / 2) - (CanvasTextFunctions.measure('sans', size, text) / 2);

        context.beginPath();
        context.strokeStyle = 'black';

        /**
        * Vertically center the text if the key is not present
        */
        if (canvas.__object__.Get('keyposition') && canvas.__object__.Get('keyposition') != 'gutter') {
            var vCenter = 'center';

        } else if (!canvas.__object__.Get('keyposition')) {
            var vCenter = 'center';

        } else {
            var vCenter = 'bottom';
        }

        RGraph.Text(context, 'sans', size, centerx, gutter / 2, text, vCenter);


        /**
        * Stops a strange bug that presents itself by drawing the last letter more than once.
        * Making it appear bolder than it should be. We simply make the last letter be drawn off screen.
        * Easy.
        */
        CanvasTextFunctions.draw(context, 'sans', 6, canvas.width + 50, canvas.height + 50, '.');
    }


    /**
    * This function returns the coordinates of the mouse position *** in relation to the CANVAS ***
    * 
    * @param object e The event object.
    */
    RGraph.getMouseXY = function (e)
    {
        var obj = (document.all ? event.srcElement : e.currentTarget);
        var x;
        var y;
        
        /**
        * Account for MSIE
        */
        if (typeof(event) == 'object') {
            e = event;
        }

        // Browser with offsetX and offsetY
        if (typeof(e.offsetX) == 'number' && typeof(e.offsetY) == 'number') {
            x = e.offsetX;
            y = e.offsetY;

        // FF and other
        } else {
            x = 0;
            y = 0;

            var obj = e.currentTarget;
            while (obj != document.body) {
                x += obj.offsetLeft;
                y += obj.offsetTop;

                obj = obj.offsetParent;
            }

            x = e.pageX - x;
            y = e.pageY - y;
        }

        return [x, y];
    }


    /**
    * Shows a tooltip next to the mouse pointer
    * 
    * @param  canvas object The canvas element object
    * @param  text   string The tooltip text
    * @param int     x      The X position that the tooltip should appear at. Combined with the canvases offsetLeft
    *                       gives the absolute X position
    * @param int     y      The Y position the tooltip should appear at. Combined with the canvases offsetTop
    *                       gives the absolute Y position
    * @return object       The tooltip object - a DIV
    */
    RGraph.Tooltip = function (canvas, text, x, y)
    {
        /**
        * Hide the context menu if it's currently shown
        */
        RGraph.HideContext();

        RGraph.Redraw(canvas.id);

        /**
        * Hide any currently shown tooltip
        */
        if (RGraph.Registry.Get('tooltip')) {
            RGraph.Registry.Get('tooltip').style.display = 'none';
            RGraph.Registry.Set('tooltip', null);
        }

        /**
        * Show a tool tip
        */
        var obj  = document.createElement('DIV');
        obj.className = 'RGraph_tooltip';
        obj.style.position        = 'absolute';
        obj.style.left            = 0;
        obj.style.top            = 0;
        obj.style.backgroundColor = '#ffe';
        obj.style.color           = 'black';
        obj.style.border          = '1px solid #333';
        obj.style.display         = 'block';
        obj.style.visibility      = 'visible';
        obj.style.paddingLeft     = '3px';
        obj.style.paddingRight    = '3px';
        obj.style.fontFamily      = 'Tahoma';
        obj.style.fontSize        = '10pt';
        obj.style.zIndex          = 3;
        obj.style.borderRadius    = '5px';
        obj.style.MozBorderRadius    = '5px';
        obj.style.WebkitBorderRadius = '5px';
        obj.style.WebkitBoxShadow    = 'rgba(96,96,96,0.5) 3px 3px 3px';
        obj.style.MozBoxShadow       = 'rgba(96,96,96,0.5) 3px 3px 3px';
        obj.style.filter             = 'progid:DXImageTransform.Microsoft.Shadow(color=#666666,direction=135)';
        obj.style.opacity            = 0;
        obj.style.overflow           = 'hidden';
        obj.innerHTML                = text;
        obj.__text__                 = text; // This is set because the innerHTML can change when it's set

        document.body.appendChild(obj);
        
        var width  = obj.offsetWidth;
        var height = obj.offsetHeight;

        /**
        * Set the width on the tooltip so it doesn't resize if the window is resized
        */
        obj.style.width = width + 'px';
        //obj.style.height = 0; // Initially set the tooltip height to nothing

        /**
        * If the mouse is towards the right of the browser window and the tooltip would go outside of the window,
        * move it left
        */
        if ( (x + width) > document.body.offsetWidth ) {
            var x = x - width - 7;
            var y = y - height;

            obj.style.left    = x + 'px';
            obj.style.top     = y + 'px';

        } else {

            obj.style.left = x + 'px';
            obj.style.top = (y - height) + 'px';
        }

        /**
        * Five whole frames of animation prodicing a fade/slide in effect
        */

        setTimeout("if (RGraph.Registry.Get('tooltip')) { RGraph.Registry.Get('tooltip').style.opacity = 0.2;  }", 50);
        setTimeout("if (RGraph.Registry.Get('tooltip')) { RGraph.Registry.Get('tooltip').style.opacity = 0.4;  }", 100);
        setTimeout("if (RGraph.Registry.Get('tooltip')) { RGraph.Registry.Get('tooltip').style.opacity = 0.6;  }", 150);
        setTimeout("if (RGraph.Registry.Get('tooltip')) { RGraph.Registry.Get('tooltip').style.opacity = 0.8;  }", 200);
        setTimeout("if (RGraph.Registry.Get('tooltip')) { RGraph.Registry.Get('tooltip').style.opacity = 1; }", 250);

        /**
        * Install the function for hiding the tooltip.
        */
        document.body.onmousedown = function ()
        {
            if (RGraph.Registry.Get('tooltip')) {
                RGraph.Registry.Get('tooltip').style.display = 'none';
                RGraph.Registry.Get('tooltip').style.visibility = 'hidden';
                RGraph.Registry.Set('tooltip', null);
                
                RGraph.Redraw();
            }
        }
        
        document.body.onmouseup = document.body.onmousedown;
        document.body.onclick   = document.body.onmousedown;
        
        /**
        * If the window is resized, hide the tooltip
        */
        window.onresize = function ()
        {
            if (RGraph.Registry.Get('tooltip')) {
                RGraph.Registry.Get('tooltip').style.display = 'none';                
                RGraph.Registry.Get('tooltip').style.visibility = 'hidden';
                RGraph.Registry.Set('tooltip', null);

                // Redraw the graph
                RGraph.Clear(canvas);
                canvas.__object__.Draw();
            }
        }
        
        /**
        * Keep a reference to the object
        */
        RGraph.Registry.Set('tooltip', obj);

    } // END TOOLTIP()


    /**
    * Registers a graph object (used when the canvas is redrawn)
    * 
    * @param object obj The object to be registered
    */
    RGraph.Register = function (obj)
    {
        var key = obj.id + '_' + obj.type;
        RGraph.objects[key] = obj;
    }


    /**
    * Causes all registered objects to be redrawn
    * 
    * @param string   An optional string indicating which canvas is not to be redrawn
    */
    RGraph.Redraw = function ()
    {
        for (i in RGraph.objects) {
            if (!arguments[0] || arguments[0] != RGraph.objects[i].id) {
                RGraph.Clear(RGraph.objects[i].canvas);
                RGraph.objects[i].Draw();
            }
        }
    }


    /**
    * Loosly mimicks the PHP function print_r();
    */
    RGraph.pr = function (obj)
    {
        var str = '';
        var indent = (arguments[2] ? arguments[2] : '');

        switch (typeof(obj)) {
            case 'number':
                if (indent == '') {
                    str+= 'Number: '
                }
                str += String(obj);
                break;
            
            case 'string':
                if (indent == '') {
                    str+= 'String (' + obj.length + '):'
                }
                str += '"' + String(obj) + '"';
                break;

            case 'object':
                // In case of null
                if (obj == null) {
                    str += 'null';
                    break;
                }

                str += 'Object \n' + indent + '(\n';
                
                for (i in obj) {
                    str += indent + '  ' + i + ' => ' + RGraph.pr(obj[i], true, indent + '\t') + '\n';
                }
                
                var str = str + indent + ')';
                break;
            
            case 'function':
                str += obj;
                break;
        }

        /**
        * Finished, now either return if we're in a recursed call, or alert()
        * if we're not.
        */
        if (arguments[1]) {
            return str;
        } else {
            alert(str);
        }
    }


    /**
    * The RGraph registry Set() function
    * 
    * @param  string name  The name of the key
    * @param  mixed  value The value to set
    * @return mixed        Returns the same value as you pass it
    */
    RGraph.Registry.Set = function (name, value)
    {
        // Initialise the store of settings
        if (RGraph.Registry.store == null) {
            RGraph.Registry.store = [];
        }
        
        // Store the setting
        RGraph.Registry.store[name] = value;
        
        // Don't really need to do this, but ho-hum
        return value;
    }


    /**
    * The RGraph registry Get() function
    * 
    * @param  string name The name of the particular setting to fetch
    * @return mixed       The value if exists, null otherwise
    */
    RGraph.Registry.Get = function (name)
    {
        if (!RGraph.Registry.store) {
            return null;
        }

        return RGraph.Registry.store[name] == null ? null : RGraph.Registry.store[name];
    }


    /**
    * This function draws the background for the bar chart, line chart and scatter chart.
    * 
    * @param  object obj The graph object
    */
    RGraph.background.Draw = function (obj)
    {
        obj.context.beginPath();

        // Draw the horizontal bars
        obj.context.fillStyle = obj.Get('barcolor1');
        for (var i=obj.Get('gutter'); i < (obj.canvas.height - obj.Get('gutter') ); i+=80) {
            obj.context.fillRect (obj.Get('gutter'), i, obj.canvas.width - (obj.Get('gutter') * 2), Math.min(40, obj.canvas.height - obj.Get('gutter') - i) );
        }

        obj.context.fillStyle = obj.Get('barcolor2');
        for (var i= (40 + obj.Get('gutter')); i < (obj.canvas.height - obj.Get('gutter')); i+=80) {
            obj.context.fillRect (obj.Get('gutter'), i, obj.canvas.width - (obj.Get('gutter') * 2), i + 40 > (obj.canvas.height - obj.Get('gutter')) ? obj.canvas.height - (obj.Get('gutter') + i) : 40);
        }
        
        obj.context.stroke();

        // Draw the background grid
        if (obj.Get('backgroundgrid')) {

            obj.context.beginPath();
            obj.context.lineWidth = obj.Get('gridwidth') ? obj.Get('gridwidth') : 1;
            obj.context.strokeStyle = obj.Get('gridcolor');

            // Draw the horizontal lines
            for (y=obj.Get('gutter'); y < (obj.canvas.height - obj.Get('gutter')); y+=obj.Get('horgridsize')) {
                obj.context.moveTo(obj.Get('gutter'), y);
                obj.context.lineTo(obj.canvas.width - obj.Get('gutter'), y);
            }

            // Draw the vertical lines
            for (x=obj.Get('gutter'); x <= (obj.canvas.width - obj.Get('gutter')); x+=obj.Get('vergridsize')) {
                obj.context.moveTo(x, obj.Get('gutter'));
                obj.context.lineTo(x, obj.canvas.height - obj.Get('gutter'));
            }
            
            // Make sure a rectangle, the same colour as the grid goes around the graph
            obj.context.strokeStyle = obj.Get('gridcolor');
            obj.context.strokeRect(obj.Get('gutter'), obj.Get('gutter'), obj.canvas.width - (2 * obj.Get('gutter')), obj.canvas.height - (2 * obj.Get('gutter')));
        }
        
        obj.context.stroke();

        // Draw the title if one is set
        if ( typeof(obj.Get('title')) == 'string') {
            RGraph.DrawTitle(obj.canvas, obj.Get('title'), obj.Get('gutter'), null, obj.Get('textheight') + 2);
        }
        
        obj.context.stroke();
    }


    /**
    * Returns the day number for a particular date. Eg 1st February would be 32
    * 
    * @param   object obj A date object
    * @return  int        The day number of the given date
    */
    RGraph.GetDays = function (obj)
    {
        var days = obj.getDate();
        
        if (obj.getMonth() == 0) return days;
        if (obj.getMonth() >= 1) days += 31; 
        if (obj.getMonth() >= 2) days += 28;

            // Leap years. Crude, but if this code is still being used
            // when it stops working, then you have my permission to shoot
            // me. Oh, you won't be able to - I'll be dead...
            if (obj.getFullYear() >= 2008 && obj.getFullYear() % 4 == 0) days += 1;

        if (obj.getMonth() >= 3) days += 31;
        if (obj.getMonth() >= 4) days += 30;
        if (obj.getMonth() >= 5) days += 31;
        if (obj.getMonth() >= 6) days += 30;
        if (obj.getMonth() >= 7) days += 31;
        if (obj.getMonth() >= 8) days += 31;
        if (obj.getMonth() >= 9) days += 30;
        if (obj.getMonth() >= 10) days += 31;
        if (obj.getMonth() >= 11) days += 30;
        
        return days;
    }


    /**
    * Draws the graph key (used by:
    *  o line
    *  o bar
    *  o radar
    *  o tradar
    * 
    * @param object obj The graph object
    * @param array  key An array of the texts to be listed in the key
    * @param colors An array of the colors to be used
    */
    RGraph.DrawKey = function (obj, key, colors)
    {
        var canvas  = obj.canvas;
        var context = canvas.getContext('2d');
        context.beginPath();

        /**
        * Key positioned in the gutter (much like my humour)
        */
        if (obj.Get('keyposition') && obj.Get('keyposition') == 'gutter') {

            // Measure the texts
            var length = 0;
            for (i in obj.Get('key')) {
                length += CanvasTextFunctions.measure('sans', obj.Get('textheight'), obj.Get('key')[i]);
                length += 20; // This accounts for the square of color
                length += 10;// And this is an extra 10 pixels on the right of each bit of text
            }
            
            var start = (canvas.width / 2) - (length / 2);
            for (i in obj.Get('key')) {
                start += 10;
                context.fillStyle = obj.Get('colors')[i];
            
                // Draw the rectangle of color
                context.fillRect(start + 9, obj.Get('gutter') - 6 - obj.Get('textheight'), obj.Get('textheight'), obj.Get('textheight') + 1);
                
                RGraph.Text(context, 'sans', obj.Get('textheight'),
                                             start + 25,
                                             obj.Get('gutter') - 6 - obj.Get('textheight'),
                                             obj.Get('key')[i],
                                             'top');

                // Drawe the text
                //
                start += CanvasTextFunctions.measure('sans', obj.Get('textheight'), obj.Get('key')[i]) + 15;
            }


        /**
        * In-graph style key
        */
        } else if (obj.Get('keyposition') && obj.Get('keyposition') == 'graph') {
            // Work out the longest bit of text
            var width = 0;
            for (i in key) {
                width = Math.max(width, CanvasTextFunctions.measure('sans', obj.Get('textheight'), key[i]));
            }
    
            width += 29;
    
            context.fillStyle   = obj.Get('keybackground');
            context.strokeStyle = 'black';
    
            context.fillRect(canvas.width - width - obj.Get('gutter'), canvas.__object__.Get('gutter') + 5, width - 5, 5 + ( (obj.Get('textheight') + 5) * key.length) );
            context.strokeRect(canvas.width - width - obj.Get('gutter'), canvas.__object__.Get('gutter') + 5, width - 5, 5 + ( (obj.Get('textheight') + 5) * key.length));
    
            // Draw the labels given
            for (var i=key.length - 1; i>=0; i--) {
                var j = Number(i) + 1;
    
                // Draw the rectangle of color
                context.fillStyle = colors[i];
                context.fillRect(canvas.width - width - obj.Get('gutter') + 5, 5 + obj.Get('gutter') + (5 * j) + (obj.Get('textheight') * j) - (obj.Get('textheight')), obj.Get('textheight'), obj.Get('textheight'));
    
                RGraph.Text(context, 'sans', obj.Get('textheight'), canvas.width - width + 18 - obj.Get('gutter'), obj.Get('gutter') + (5 * j) + (obj.Get('textheight') * j) + 4, key[i]);
            }
        
        } else {
            alert('[COMMON] (' + obj.id + ') Unknown key position: ' + obj.Get('keyposition'));
        }

        /**
        * Now draw some text off screen to get around that bizaare bug
        * in the text library and redrawing the entire thing
        */
        RGraph.Text(context, 'sans', 1, canvas.width + 50, canvas.height + 50, '.');
    }


    /**
    * A shortcut for RGraph.pr()
    */
    function pd(variable)
    {
        RGraph.pr(variable);
    }


    /**
    * Makes a clone of an object, since objects in JS are (seem to be) passed around by reference
    * 
    * @param obj val The object to clone
    */
    RGraph.array_clone = function (obj)
    {
        if(obj == null || typeof(obj) != 'object') {
            return obj;
        }

        var temp = new obj.constructor();

        for(var key in obj) {
            temp[key] = RGraph.array_clone(obj[key]);
        }

        return temp;
    }


    /**
    * This function reverses an array
    */
    RGraph.array_reverse = function (arr)
    {
        var newarr = [];

        for (var i=arr.length - 1; i>=0; i--) {
            newarr.push(arr[i]);
        }

        return newarr;
    }
    
    
    /**
    * Formats a number with thousand seperators so it's easy to read
    * 
    * @param  integer num The number to format
    * @param  string      The character to use as the thousand separator. Defaults to '.'
    * @return string      The formatted number
    */
    RGraph.number_format = function (num)
    {
        var prepend = arguments[1] ? String(arguments[1]) : '';
        var append  = arguments[2] ? String(arguments[2]) : '';
        var output  = '';
        var decimal = '';
        RegExp.$1   = '';

        // We need then number as a string
        num = String(num);
        
        // Take off the decimal part - we re-append it later
        if (num.indexOf('.') > 0) {
            num     = num.replace(/\.(.*)/, '');
            decimal = RegExp.$1;
        }

        // Thousand seperator
        var seperator = arguments[1] ? String(arguments[1]) : ',';
        
        /**
        * Work backwards adding the thousand seperators
        */
        var foundPoint;
        for (i=(num.length - 1),j=0; i>=0; j++,i--) {
            var char = num.charAt(i);
            
            if ( j % 3 == 0 && j != 0) {
                output += seperator;
            }
            
            /**
            * Build the output
            */
            output += char;
        }
        
        /**
        * Now need to reverse the string
        */
        var rev = output;
        output = '';
        for (i=(rev.length - 1); i>=0; i--) {
            output += rev.charAt(i);
        }

        // Tidy up
        output = output.replace(/^-,/, '-');

        // Reappend the decimal
        if (decimal.length) {
            output =  output + '.' + decimal;
            decimal = '';
            RegExp.$1 = '';
        }

        return prepend + output + append;
    }
    
    
    /**
    * Th ubiquitous $ function. Dollar is not special in any way - it's simply a shotcut
    * for document.getElementById("...").
    * 
    * @param string id The ID of the object to get
    */
    function $ (id)
    {
        return document.getElementById(id);
    }



    /**
    * This gunction shows a context menu containing the parameters
    * provided to it
    * 
    * @param object e The event object
    */
    RGraph.Contextmenu = function (canvas, menuitems, e)
    {
        /**
        * Hide any existing menu
        */
        if (RGraph.Registry.Get('contextmenu')) {
            RGraph.HideContext();
        }

        var x      = document.all ? event.clientX + document.body.scrollLeft : e.pageX;
        var y      = document.all ? event.clientY + document.body.scrollTop : e.pageY;
        var div    = document.createElement('div');
        var bg     = document.createElement('div');

        div.className = 'RGraph_contextmenu';
        div.style.position = 'absolute';
        div.style.left = 0;
        div.style.top = 0;
        div.style.border = '1px solid black';
        div.style.backgroundColor = '#fff';
        div.style.opacity = 0;
        
        bg.className = 'RGraph_contextmenu_background';
        bg.style.position = 'absolute';
        bg.style.backgroundColor = '#ccc';
        bg.style.borderRight = '1px solid #aaa';
        bg.style.top = 0;
        bg.style.left = 0;
        bg.style.width = '18px';
        bg.style.opacity = 0;
        bg.style.height = '100%';


        div    = document.body.appendChild(div);
        bg     = div.appendChild(bg);


        /**
        * Now add the context menu items
        */
        for (i in menuitems) {
            
            var menuitem = document.createElement('div');
            
            menuitem.className = 'RGraph_contextmenu_item';
            
            if (menuitems[i]) {
                menuitem.style.padding = '2px 5px 2px 23px';
                menuitem.style.fontFamily = 'Arial';
                menuitem.style.fontSize = '10pt';
                menuitem.style.fontWeight = 'normal';
                menuitem.style.cursor = 'pointer'; // FF
                menuitem.style.cursor = 'hand';    // IE
                menuitem.innerHTML = menuitems[i][0];
            
            } else {
                menuitem.style.borderBottom = '1px solid #ddd';
                menuitem.style.marginLeft = '25px';
            }
 
            // Add the mouseover event
            if (menuitem.addEventListener) {
                menuitem.addEventListener("mouseover", function (e) {e.currentTarget.style.backgroundColor = '#eee'}, false);
                menuitem.addEventListener("mouseout", function (e) {e.currentTarget.style.backgroundColor = 'white'}, false);
            } else {
                menuitem.attachEvent("onmouseover", function () {event.srcElement.style.backgroundColor = '#eee'; event.srcElement.style.cursor = 'hand'}, false);
                menuitem.attachEvent("onmouseout", function () {event.srcElement.style.backgroundColor = 'white'}, false);
            }

            div.appendChild(menuitem);

            /**
            * Install the event handler
            */
            if (menuitems[i] && menuitems[i][1]) {
                // FIXME MSIE uses attachEvent()
                menuitem.addEventListener('click', menuitems[i][1], false);
            }
        }
        
        /**
        * Now all the menu items have been added, set the shadow height
        */
        div.style.width = div.offsetWidth + 'px';
        
        // Show the menu to the left or the right (normal) of the cursor?
        if (x + div.offsetWidth > document.body.offsetWidth) {
            x -= div.offsetWidth;
        }
        
        // Reposition the menu (now we have the real offsetWidth)
        div.style.left = x + 'px';
        div.style.top = y + 'px';

        /**
        * Do a little fade in effect
        */
        setTimeout("if (RGraph.Registry.Get('contextmenu')) RGraph.Registry.Get('contextmenu').style.opacity = 0.2", 50);
        setTimeout("if (RGraph.Registry.Get('contextmenu')) RGraph.Registry.Get('contextmenu').style.opacity = 0.4", 100);
        setTimeout("if (RGraph.Registry.Get('contextmenu')) RGraph.Registry.Get('contextmenu').style.opacity = 0.6", 150);
        setTimeout("if (RGraph.Registry.Get('contextmenu')) RGraph.Registry.Get('contextmenu').style.opacity = 0.8", 200);
        setTimeout("if (RGraph.Registry.Get('contextmenu')) RGraph.Registry.Get('contextmenu').style.opacity = 1", 250);

        // The fade in effect on the left gray bar
        setTimeout("if (RGraph.Registry.Get('contextmenu.bg')) RGraph.Registry.Get('contextmenu.bg').style.opacity = 0.2", 50);
        setTimeout("if (RGraph.Registry.Get('contextmenu.bg')) RGraph.Registry.Get('contextmenu.bg').style.opacity = 0.4", 100);
        setTimeout("if (RGraph.Registry.Get('contextmenu.bg')) RGraph.Registry.Get('contextmenu.bg').style.opacity = 0.6", 150);
        setTimeout("if (RGraph.Registry.Get('contextmenu.bg')) RGraph.Registry.Get('contextmenu.bg').style.opacity = 0.8", 200);
        setTimeout("if (RGraph.Registry.Get('contextmenu.bg')) RGraph.Registry.Get('contextmenu.bg').style.opacity = 1", 250);

        // Store the context menu in the registry
        RGraph.Registry.Set('contextmenu', div);
        RGraph.Registry.Set('contextmenu.bg', bg);
        RGraph.Registry.Get('contextmenu').oncontextmenu = function () {return false;};
        RGraph.Registry.Get('contextmenu.bg').oncontextmenu = function () {return false;};

        /**
        * Install the event handlers that hide the context menu
        */
        if (navigator.userAgent.indexOf('Opera') == -1) {
            canvas.onclick = function () {RGraph.HideContext();}
        }


        window.onclick = function () {RGraph.HideContext();}
        window.onresize = function () {RGraph.HideContext();}
        e.cancelBubble = true;
        return false;
    }


    /**
    * Hides the context menu if it's currently visible
    */
    RGraph.HideContext = function ()
    {
        if (RGraph.Registry.Get('contextmenu')) {
            RGraph.Registry.Get('contextmenu').style.visibility = 'hidden';
            RGraph.Registry.Get('contextmenu').style.display = 'none';
            RGraph.Registry.Set('contextmenu', null);
            
            RGraph.Registry.Get('contextmenu.bg').style.visibility = 'hidden';
            RGraph.Registry.Get('contextmenu.bg').style.display = 'none';
            RGraph.Registry.Set('contextmenu.bg', null);
        }
    }


    /**
    * Shows the context menu after making a few checks
    */
    RGraph.ShowContext = function (obj)
    {
        if (obj.Get('contextmenu') && obj.Get('contextmenu').length) {
            if (navigator.userAgent.indexOf('Opera') == -1) {
                obj.canvas.oncontextmenu = function (e)
                {
                    if (e.ctrlKey) return true;

                    RGraph.Contextmenu(obj.canvas, obj.Get('contextmenu'), e);
                    return false;
                }
            
            // Accomodate Opera
            } else {
    
                obj.canvas.onclick = function (e)
                {
                    if (e.ctrlKey) return true;

                    RGraph.Contextmenu(obj.canvas, obj.Get('contextmenu'), e);
                }
            }
        }
    }


    /**
    * Draws horizontal coloured bars on something like the bar, line or scatter
    */
    RGraph.DrawBars = function (obj)
    {
        var hbars = obj.Get('hbars');

        /**
        * Draws a horizontal bar
        */
        obj.context.beginPath();
        
        for (i in hbars) {
            var ystart = (obj.grapharea - ((hbars[i][0] / obj.max) * obj.grapharea));
            var height = (Math.min(hbars[i][1], obj.max - hbars[i][0]) / obj.max) * obj.grapharea;
    
            // Account for the X axis being in the center
            if (obj.Get('xaxispos') == 'center') {
                ystart /= 2;
                height /= 2;
            }
            
            ystart += obj.Get('gutter')

            var x = obj.Get('gutter');
            var y = ystart - height;
            var w = obj.canvas.width - (2 * obj.Get('gutter'));
            var h = height;
            
            // Accommodate Opera :-/
            if (navigator.userAgent.indexOf('Opera') != -1 && obj.Get('xaxispos') == 'center' && h < 0) {
                h *= -1;
                y = y - h;
            }

            obj.context.fillStyle = hbars[i][2];
            obj.context.fillRect(x, y, w, h);
        }

        obj.context.fill();
    }
