source: Dev/trunk/RGraph/libraries/RGraph.common.core.js @ 201

Last change on this file since 201 was 77, checked in by fpvanagthoven, 14 years ago

RGraph

File size: 92.7 KB
Line 
1    /**
2    * o------------------------------------------------------------------------------o
3    * | This file is part of the RGraph package - you can learn more at:             |
4    * |                                                                              |
5    * |                          http://www.rgraph.net                               |
6    * |                                                                              |
7    * | This package is licensed under the RGraph license. For all kinds of business |
8    * | purposes there is a small one-time licensing fee to pay and for non          |
9    * | commercial  purposes it is free to use. You can read the full license here:  |
10    * |                                                                              |
11    * |                      http://www.rgraph.net/LICENSE.txt                       |
12    * o------------------------------------------------------------------------------o
13    */
14
15    /**
16    * Initialise the various objects
17    */
18    if (typeof(RGraph) == 'undefined') RGraph = {isRGraph:true,type:'common'};
19
20
21    RGraph.Registry       = {};
22    RGraph.Registry.store = [];
23    RGraph.Registry.store['chart.event.handlers'] = [];
24    RGraph.background     = {};
25    RGraph.objects        = [];
26    RGraph.Resizing       = {};
27    RGraph.events         = [];
28   
29
30
31    /**
32    * Returns five values which are used as a nice scale
33    *
34    * @param  max int    The maximum value of the graph
35    * @param  obj object The graph object
36    * @return     array   An appropriate scale
37    */
38    RGraph.getScale = function (max, obj)
39    {
40        /**
41        * Special case for 0
42        */
43        if (max == 0) {
44            return ['0.2', '0.4', '0.6', '0.8', '1.0'];
45        }
46
47        var original_max = max;
48
49        /**
50        * Manually do decimals
51        */
52        if (max <= 1) {
53            if (max > 0.5) {
54                return [0.2,0.4,0.6,0.8, Number(1).toFixed(1)];
55
56            } else if (max >= 0.1) {
57                return obj.Get('chart.scale.round') ? [0.2,0.4,0.6,0.8,1] : [0.1,0.2,0.3,0.4,0.5];
58
59            } else {
60
61                var tmp = max;
62                var exp = 0;
63
64                while (tmp < 1.01) {
65                    exp += 1;
66                    tmp *= 10;
67                }
68
69                var ret = ['2e-' + exp, '4e-' + exp, '6e-' + exp, '8e-' + exp, '10e-' + exp];
70
71
72                if (max <= ('5e-' + exp)) {
73                    ret = ['1e-' + exp, '2e-' + exp, '3e-' + exp, '4e-' + exp, '5e-' + exp];
74                }
75
76                return ret;
77            }
78        }
79
80        // Take off any decimals
81        if (String(max).indexOf('.') > 0) {
82            max = String(max).replace(/\.\d+$/, '');
83        }
84
85        var interval = Math.pow(10, Number(String(Number(max)).length - 1));
86        var topValue = interval;
87
88        while (topValue < max) {
89            topValue += (interval / 2);
90        }
91
92        // Handles cases where the max is (for example) 50.5
93        if (Number(original_max) > Number(topValue)) {
94            topValue += (interval / 2);
95        }
96
97        // Custom if the max is greater than 5 and less than 10
98        if (max < 10) {
99            topValue = (Number(original_max) <= 5 ? 5 : 10);
100        }
101       
102        /**
103        * Added 02/11/2010 to create "nicer" scales
104        */
105        if (obj && typeof(obj.Get('chart.scale.round')) == 'boolean' && obj.Get('chart.scale.round')) {
106            topValue = 10 * interval;
107        }
108
109        return [topValue * 0.2, topValue * 0.4, topValue * 0.6, topValue * 0.8, topValue];
110    }
111
112
113    /**
114    * Returns the maximum numeric value which is in an array
115    *
116    * @param  array arr The array (can also be a number, in which case it's returned as-is)
117    * @param  int       Whether to ignore signs (ie negative/positive)
118    * @return int       The maximum value in the array
119    */
120    RGraph.array_max = function (arr)
121    {
122        var max = null;
123       
124        if (typeof(arr) == 'number') {
125            return arr;
126        }
127       
128        for (var i=0; i<arr.length; ++i) {
129            if (typeof(arr[i]) == 'number') {
130
131                var val = arguments[1] ? Math.abs(arr[i]) : arr[i];
132               
133                if (typeof(max) == 'number') {
134                    max = Math.max(max, val);
135                } else {
136                    max = val;
137                }
138            }
139        }
140       
141        return max;
142    }
143
144
145    /**
146    * Returns the maximum value which is in an array
147    *
148    * @param  array arr The array
149    * @param  int   len The length to pad the array to
150    * @param  mixed     The value to use to pad the array (optional)
151    */
152    RGraph.array_pad = function (arr, len)
153    {
154        if (arr.length < len) {
155            var val = arguments[2] ? arguments[2] : null;
156           
157            for (var i=arr.length; i<len; ++i) {
158                arr[i] = val;
159            }
160        }
161       
162        return arr;
163    }
164
165
166    /**
167    * An array sum function
168    *
169    * @param  array arr The  array to calculate the total of
170    * @return int       The summed total of the arrays elements
171    */
172    RGraph.array_sum = function (arr)
173    {
174        // Allow integers
175        if (typeof(arr) == 'number') {
176            return arr;
177        }
178
179        var i, sum;
180        var len = arr.length;
181
182        for(i=0,sum=0;i<len;sum+=arr[i++]);
183        return sum;
184    }
185
186
187
188    /**
189    * A simple is_array() function
190    *
191    * @param  mixed obj The object you want to check
192    * @return bool      Whether the object is an array or not
193    */
194    RGraph.is_array = function (obj)
195    {
196        return obj != null && obj.constructor.toString().indexOf('Array') != -1;
197    }
198
199
200    /**
201    * Converts degrees to radians
202    *
203    * @param  int degrees The number of degrees
204    * @return float       The number of radians
205    */
206    RGraph.degrees2Radians = function (degrees)
207    {
208        return degrees * (Math.PI / 180);
209    }
210
211
212    /**
213    * This function draws an angled line. The angle is cosidered to be clockwise
214    *
215    * @param obj ctxt   The context object
216    * @param int x      The X position
217    * @param int y      The Y position
218    * @param int angle  The angle in RADIANS
219    * @param int length The length of the line
220    */
221    RGraph.lineByAngle = function (context, x, y, angle, length)
222    {
223        context.arc(x, y, length, angle, angle, false);
224        context.lineTo(x, y);
225        context.arc(x, y, length, angle, angle, false);
226    }
227
228
229    /**
230    * This is a useful function which is basically a shortcut for drawing left, right, top and bottom alligned text.
231    *
232    * @param object context The context
233    * @param string font    The font
234    * @param int    size    The size of the text
235    * @param int    x       The X coordinate
236    * @param int    y       The Y coordinate
237    * @param string text    The text to draw
238    * @parm  string         The vertical alignment. Can be null. "center" gives center aligned  text, "top" gives top aligned text.
239    *                       Anything else produces bottom aligned text. Default is bottom.
240    * @param  string        The horizontal alignment. Can be null. "center" gives center aligned  text, "right" gives right aligned text.
241    *                       Anything else produces left aligned text. Default is left.
242    * @param  bool          Whether to show a bounding box around the text. Defaults not to
243    * @param int            The angle that the text should be rotate at (IN DEGREES)
244    * @param string         Background color for the text
245    * @param bool           Whether the text is bold or not
246    * @param bool           Whether the bounding box has a placement indicator
247    */
248    RGraph.Text = function (context, font, size, x, y, text)
249    {
250        /**
251        * This calls the text function recursively to accommodate multi-line text
252        */
253        if (typeof(text) == 'string' && text.match(/\r\n/)) {
254           
255            var arr = text.split('\r\n');
256
257            text = arr[0];
258            arr = RGraph.array_shift(arr);
259
260            var nextline = arr.join('\r\n')
261
262            RGraph.Text(context, font, size, arguments[9] == -90 ? (x + (size * 1.5)) : x, y + (size * 1.5), nextline, arguments[6] ? arguments[6] : null, 'center', arguments[8], arguments[9], arguments[10], arguments[11], arguments[12]);
263        }
264
265
266        // Accommodate MSIE
267        if (RGraph.isIE8()) {
268            y += 2;
269        }
270
271
272        context.font = (arguments[11] ? 'Bold ': '') + size + 'pt ' + font;
273
274        var i;
275        var origX = x;
276        var origY = y;
277        var originalFillStyle = context.fillStyle;
278        var originalLineWidth = context.lineWidth;
279
280        // Need these now the angle can be specified, ie defaults for the former two args
281        if (typeof(arguments[6]) == null) arguments[6]  = 'bottom'; // Vertical alignment. Default to bottom/baseline
282        if (typeof(arguments[7]) == null) arguments[7]  = 'left';   // Horizontal alignment. Default to left
283        if (typeof(arguments[8]) == null) arguments[8]  = null;     // Show a bounding box. Useful for positioning during development. Defaults to false
284        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
285        if (typeof(arguments[12]) == null) arguments[12] = true;    // Whether the bounding box has the placement indicator
286
287        // The alignment is recorded here for purposes of Opera compatibility
288        if (navigator.userAgent.indexOf('Opera') != -1) {
289            context.canvas.__rgraph_valign__ = arguments[6];
290            context.canvas.__rgraph_halign__ = arguments[7];
291        }
292
293        // First, translate to x/y coords
294        context.save();
295
296            context.canvas.__rgraph_originalx__ = x;
297            context.canvas.__rgraph_originaly__ = y;
298
299            context.translate(x, y);
300            x = 0;
301            y = 0;
302           
303            // Rotate the canvas if need be
304            if (arguments[9]) {
305                context.rotate(arguments[9] / 57.3);
306            }
307
308            // Vertical alignment - defaults to bottom
309            if (arguments[6]) {
310                var vAlign = arguments[6];
311
312                if (vAlign == 'center') {
313                    context.translate(0, size / 2);
314                } else if (vAlign == 'top') {
315                    context.translate(0, size);
316                }
317            }
318
319
320            // Hoeizontal alignment - defaults to left
321            if (arguments[7]) {
322                var hAlign = arguments[7];
323                var width  = context.measureText(text).width;
324   
325                if (hAlign) {
326                    if (hAlign == 'center') {
327                        context.translate(-1 * (width / 2), 0)
328                    } else if (hAlign == 'right') {
329                        context.translate(-1 * width, 0)
330                    }
331                }
332            }
333           
334           
335            context.fillStyle = originalFillStyle;
336
337            /**
338            * Draw a bounding box if requested
339            */
340            context.save();
341                 context.fillText(text,0,0);
342                 context.lineWidth = 0.5;
343               
344                if (arguments[8]) {
345
346                    var width = context.measureText(text).width;
347                    var ieOffset = RGraph.isIE8() ? 2 : 0;
348
349                    context.translate(x, y);
350                    context.strokeRect(0 - 3, 0 - 3 - size - ieOffset, width + 6, 0 + size + 6);
351   
352                    /**
353                    * If requested, draw a background for the text
354                    */
355                    if (arguments[10]) {
356       
357                        var offset = 3;
358                        var ieOffset = RGraph.isIE8() ? 2 : 0;
359                        var width = context.measureText(text).width
360
361                        //context.strokeStyle = 'gray';
362                        context.fillStyle = arguments[10];
363                        context.fillRect(x - offset, y - size - offset - ieOffset, width + (2 * offset), size + (2 * offset));
364                        //context.strokeRect(x - offset, y - size - offset - ieOffset, width + (2 * offset), size + (2 * offset));
365                    }
366                   
367                    /**
368                    * Do the actual drawing of the text
369                    */
370                    context.fillStyle = originalFillStyle;
371                    context.fillText(text,0,0);
372
373                    if (arguments[12]) {
374                        context.fillRect(
375                            arguments[7] == 'left' ? 0 : (arguments[7] == 'center' ? width / 2 : width ) - 2,
376                            arguments[6] == 'bottom' ? 0 : (arguments[6] == 'center' ? (0 - size) / 2 : 0 - size) - 2,
377                            4,
378                            4
379                        );
380                    }
381                }
382            context.restore();
383           
384            // Reset the lineWidth
385            context.lineWidth = originalLineWidth;
386
387        context.restore();
388    }
389
390
391    /**
392    * Clears the canvas by setting the width. You can specify a colour if you wish.
393    *
394    * @param object canvas The canvas to clear
395    */
396    RGraph.Clear = function (canvas)
397    {
398        var context = canvas.getContext('2d');
399        var color   = arguments[1];
400
401        if (RGraph.isIE8() && !color) {
402            color = 'white';
403        }
404
405        /**
406        * Can now clear the canvas back to fully transparent
407        */
408        if (!color || (color && color == 'transparent')) {
409
410            context.fillStyle = 'rgba(0,0,0,0)';
411            context.globalCompositeOperation = 'source-in';
412            context = canvas.getContext('2d');
413            context.beginPath();
414            context.fillRect(-1000,-1000,canvas.width + 2000,canvas.height + 2000);
415            context.fill();
416           
417            // Reset the globalCompositeOperation
418            context.globalCompositeOperation = 'source-over';
419
420        } else {
421
422            context.fillStyle = color;
423            context = canvas.getContext('2d');
424            context.beginPath();
425
426            if (RGraph.isIE8()) {
427                context.fillRect(0,0,canvas.width,canvas.height);
428            } else {
429                context.fillRect(-1000,-1000,canvas.width + 2000,canvas.height + 2000);
430            }
431
432            context.fill();
433        }
434       
435        // Don't do this as it also clears any translation that may have occurred
436        //canvas.width = canvas.width;
437       
438        if (RGraph.ClearAnnotations) {
439            RGraph.ClearAnnotations(canvas.id);
440        }
441
442        RGraph.FireCustomEvent(canvas.__object__, 'onclear');
443    }
444
445
446    /**
447    * Draws the title of the graph
448    *
449    * @param object  canvas The canvas object
450    * @param string  text   The title to write
451    * @param integer gutter The size of the gutter
452    * @param integer        The center X point (optional - if not given it will be generated from the canvas width)
453    * @param integer        Size of the text. If not given it will be 14
454    */
455    RGraph.DrawTitle = function (canvas, text, gutterTop)
456    {
457        var obj          = canvas.__object__;
458        var context      = canvas.getContext('2d');
459        var gutterLeft   = obj.Get('chart.gutter.left');
460        var gutterRight  = obj.Get('chart.gutter.right');
461        var gutterBottom = obj.Get('chart.gutter.bottom');
462        var size         = arguments[4] ? arguments[4] : 12;
463        var centerx      = (arguments[3] ? arguments[3] : ((RGraph.GetWidth(obj) - gutterLeft - gutterRight) / 2) + gutterLeft);
464        var keypos       = obj.Get('chart.key.position');
465        var vpos         = obj.Get('chart.title.vpos');
466        var hpos         = obj.Get('chart.title.hpos');
467        var bgcolor      = obj.Get('chart.title.background');
468
469        // Account for 3D effect by faking the key position
470        if (obj.type == 'bar' && obj.Get('chart.variant') == '3d') {
471            keypos = 'gutter';
472        }
473
474        context.beginPath();
475        context.fillStyle = obj.Get('chart.text.color') ? obj.Get('chart.text.color') : 'black';
476
477        /**
478        * Vertically center the text if the key is not present
479        */
480        if (keypos && keypos != 'gutter') {
481            var vCenter = 'center';
482
483        } else if (!keypos) {
484            var vCenter = 'center';
485
486        } else {
487            var vCenter = 'bottom';
488        }
489
490        // if chart.title.vpos does not equal 0.5, use that
491        if (typeof(obj.Get('chart.title.vpos')) == 'number') {
492            vpos = obj.Get('chart.title.vpos') * gutterTop;
493
494            if (obj.Get('chart.xaxispos') == 'top') {
495                vpos = obj.Get('chart.title.vpos') * gutterBottom + gutterTop + (obj.canvas.height - gutterTop - gutterBottom);
496            }
497        } else {
498            vpos = gutterTop - size - 5;
499
500            if (obj.Get('chart.xaxispos') == 'top') {
501                vpos = obj.canvas.height  - gutterBottom + size + 5;
502            }
503        }
504
505        // if chart.title.hpos is a number, use that. It's multiplied with the (entire) canvas width
506        if (typeof(hpos) == 'number') {
507            centerx = hpos * canvas.width;
508        }
509       
510        // Set the colour
511        if (typeof(obj.Get('chart.title.color') != null)) {
512            var oldColor = context.fillStyle
513            var newColor = obj.Get('chart.title.color')
514            context.fillStyle = newColor ? newColor : 'black';
515        }
516       
517        /**
518        * Default font is Verdana
519        */
520        var font = obj.Get('chart.text.font');
521
522        /**
523        * Draw the title itself
524        */
525        RGraph.Text(context, font, size, centerx, vpos, text, vCenter, 'center', bgcolor != null, null, bgcolor, true);
526       
527        // Reset the fill colour
528        context.fillStyle = oldColor;
529    }
530
531
532    /**
533    * This function returns the mouse position in relation to the canvas
534    *
535    * @param object e The event object.
536    */
537    RGraph.getMouseXY = function (e)
538    {
539        var obj = (RGraph.isIE8() ? event.srcElement : e.target);
540        var x;
541        var y;
542       
543        if (RGraph.isIE8()) e = event;
544
545        // Browser with offsetX and offsetY
546        if (typeof(e.offsetX) == 'number' && typeof(e.offsetY) == 'number') {
547            x = e.offsetX;
548            y = e.offsetY;
549
550        // FF and other
551        } else {
552            x = 0;
553            y = 0;
554
555            while (obj != document.body && obj) {
556                x += obj.offsetLeft;
557                y += obj.offsetTop;
558
559                obj = obj.offsetParent;
560            }
561
562            x = e.pageX - x;
563            y = e.pageY - y;
564        }
565
566        return [x, y];
567    }
568   
569   
570    /**
571    * This function returns a two element array of the canvas x/y position in
572    * relation to the page
573    *
574    * @param object canvas
575    */
576    RGraph.getCanvasXY = function (canvas)
577    {
578        var x   = 0;
579        var y   = 0;
580        var obj = canvas;
581
582        do {
583
584            x += obj.offsetLeft;
585            y += obj.offsetTop;
586
587            obj = obj.offsetParent;
588
589        } while (obj && obj.tagName.toLowerCase() != 'body');
590
591        return [x, y];
592    }
593
594
595    /**
596    * Registers a graph object (used when the canvas is redrawn)
597    *
598    * @param object obj The object to be registered
599    */
600    RGraph.Register = function (obj)
601    {
602        var key = obj.id + '_' + obj.type;
603
604        RGraph.objects[key] = obj;
605    }
606
607
608    /**
609    * Causes all registered objects to be redrawn
610    *
611    * @param string   An optional string indicating which canvas is not to be redrawn
612    * @param string An optional color to use to clear the canvas
613    */
614    RGraph.Redraw = function ()
615    {
616        for (i in RGraph.objects) {
617            // TODO FIXME Maybe include more intense checking for whether the object is an RGraph object, eg obj.isRGraph == true ...?
618            if (
619                   typeof(i) == 'string'
620                && typeof(RGraph.objects[i]) == 'object'
621                && typeof(RGraph.objects[i].type) == 'string'
622                && RGraph.objects[i].isRGraph)  {
623
624                if (!arguments[0] || arguments[0] != RGraph.objects[i].id) {
625                    RGraph.Clear(RGraph.objects[i].canvas, arguments[1] ? arguments[1] : null);
626                    RGraph.objects[i].Draw();
627                }
628            }
629        }
630    }
631
632
633    /**
634    * Loosly mimicks the PHP function print_r();
635    */
636    RGraph.pr = function (obj)
637    {
638        var str = '';
639        var indent = (arguments[2] ? arguments[2] : '');
640
641        switch (typeof(obj)) {
642            case 'number':
643                if (indent == '') {
644                    str+= 'Number: '
645                }
646                str += String(obj);
647                break;
648           
649            case 'string':
650                if (indent == '') {
651                    str+= 'String (' + obj.length + '):'
652                }
653                str += '"' + String(obj) + '"';
654                break;
655
656            case 'object':
657                // In case of null
658                if (obj == null) {
659                    str += 'null';
660                    break;
661                }
662
663                str += 'Object\n' + indent + '(\n';
664               
665                for (var i=0; i<obj.length; ++i) {
666                    str += indent + ' ' + i + ' => ' + RGraph.pr(obj[i], true, indent + '    ') + '\n';
667                }
668               
669                var str = str + indent + ')';
670                break;
671           
672            case 'function':
673                str += obj;
674                break;
675           
676            case 'boolean':
677                str += 'Boolean: ' + (obj ? 'true' : 'false');
678                break;
679        }
680
681        /**
682        * Finished, now either return if we're in a recursed call, or alert()
683        * if we're not.
684        */
685        if (arguments[1]) {
686            return str;
687        } else {
688            alert(str);
689        }
690    }
691
692
693    /**
694    * The RGraph registry Set() function
695    *
696    * @param  string name  The name of the key
697    * @param  mixed  value The value to set
698    * @return mixed        Returns the same value as you pass it
699    */
700    RGraph.Registry.Set = function (name, value)
701    {
702        // Store the setting
703        RGraph.Registry.store[name] = value;
704       
705        // Don't really need to do this, but ho-hum
706        return value;
707    }
708
709
710    /**
711    * The RGraph registry Get() function
712    *
713    * @param  string name The name of the particular setting to fetch
714    * @return mixed       The value if exists, null otherwise
715    */
716    RGraph.Registry.Get = function (name)
717    {
718        //return RGraph.Registry.store[name] == null ? null : RGraph.Registry.store[name];
719        return RGraph.Registry.store[name];
720    }
721
722
723    /**
724    * This function draws the background for the bar chart, line chart and scatter chart.
725    *
726    * @param  object obj The graph object
727    */
728    RGraph.background.Draw = function (obj)
729    {
730        var canvas       = obj.canvas;
731        var context      = obj.context;
732        var height       = 0;
733        var gutterLeft   = obj.Get('chart.gutter.left');
734        var gutterRight  = obj.Get('chart.gutter.right');
735        var gutterTop    = obj.Get('chart.gutter.top');
736        var gutterBottom = obj.Get('chart.gutter.bottom');
737        var variant      = obj.Get('chart.variant');
738       
739        context.fillStyle = obj.Get('chart.text.color');
740       
741        // If it's a bar and 3D variant, translate
742        if (variant == '3d') {
743            context.save();
744            context.translate(10, -5);
745        }
746
747        // X axis title
748        if (typeof(obj.Get('chart.title.xaxis')) == 'string' && obj.Get('chart.title.xaxis').length) {
749       
750            var size = obj.Get('chart.text.size');
751            var font = obj.Get('chart.text.font');
752       
753            context.beginPath();
754            RGraph.Text(context, font, size + 2, RGraph.GetWidth(obj) / 2, RGraph.GetHeight(obj) - (gutterBottom * obj.Get('chart.title.xaxis.pos')), obj.Get('chart.title.xaxis'), 'center', 'center', false, false, false, true);
755            context.fill();
756        }
757
758        // Y axis title
759        if (typeof(obj.Get('chart.title.yaxis')) == 'string' && obj.Get('chart.title.yaxis').length) {
760       
761            var size            = obj.Get('chart.text.size');
762            var font            = obj.Get('chart.text.font');
763            var angle           = 270;
764            var yaxis_title_pos = obj.Get('chart.title.yaxis.pos');
765
766            if (obj.Get('chart.title.yaxis.align') == 'right' || obj.Get('chart.title.yaxis.position') == 'right') {
767                angle = 90;
768                yaxis_title_pos = yaxis_title_pos * obj.Get('chart.gutter.right') + (obj.canvas.width - obj.Get('chart.gutter.right'));
769            } else {
770                yaxis_title_pos *= obj.Get('chart.gutter.left');
771            }
772           
773       
774            context.beginPath();
775            RGraph.Text(context,
776                        font,
777                        size + 2,
778                        yaxis_title_pos,
779                        RGraph.GetHeight(obj) / 2,
780                        obj.Get('chart.title.yaxis'),
781                        'center',
782                        'center',
783                        false,
784                        angle,
785                        false,
786                        true);
787            context.fill();
788        }
789
790        obj.context.beginPath();
791
792        // Draw the horizontal bars
793        context.fillStyle = obj.Get('chart.background.barcolor1');
794        height = (RGraph.GetHeight(obj) - gutterBottom);
795
796        for (var i=gutterTop; i < height ; i+=80) {
797            obj.context.fillRect(gutterLeft, i, RGraph.GetWidth(obj) - gutterLeft - gutterRight, Math.min(40, RGraph.GetHeight(obj) - gutterBottom - i) );
798        }
799
800            context.fillStyle = obj.Get('chart.background.barcolor2');
801            height = (RGraph.GetHeight(obj) - gutterBottom);
802   
803            for (var i= (40 + gutterTop); i < height; i+=80) {
804                obj.context.fillRect(gutterLeft, i, RGraph.GetWidth(obj) - gutterLeft - gutterRight, i + 40 > (RGraph.GetHeight(obj) - gutterBottom) ? RGraph.GetHeight(obj) - (gutterBottom + i) : 40);
805            }
806           
807            context.stroke();
808   
809
810        // Draw the background grid
811        if (obj.Get('chart.background.grid')) {
812
813            // If autofit is specified, use the .numhlines and .numvlines along with the width to work
814            // out the hsize and vsize
815            if (obj.Get('chart.background.grid.autofit')) {
816
817                /**
818                * Align the grid to the tickmarks
819                */
820                if (obj.Get('chart.background.grid.autofit.align')) {
821
822                    // Align the horizontal lines
823                    obj.Set('chart.background.grid.autofit.numhlines', obj.Get('chart.ylabels.count'));
824
825                    // Align the vertical lines for the line
826                    if (obj.type == 'line') {
827                        if (obj.Get('chart.labels') && obj.Get('chart.labels').length) {
828                            obj.Set('chart.background.grid.autofit.numvlines', obj.Get('chart.labels').length - 1);
829                        } else {
830                            obj.Set('chart.background.grid.autofit.numvlines', obj.data[0].length - 1);
831                        }
832
833                    // Align the vertical lines for the bar
834                    } else if (obj.type == 'bar' && obj.Get('chart.labels') && obj.Get('chart.labels').length) {
835                        obj.Set('chart.background.grid.autofit.numvlines', obj.Get('chart.labels').length);
836                    }
837                }
838
839                var vsize = ((RGraph.GetWidth(obj) - gutterLeft - gutterRight)) / obj.Get('chart.background.grid.autofit.numvlines');
840                var hsize = (RGraph.GetHeight(obj) - gutterTop - gutterBottom) / obj.Get('chart.background.grid.autofit.numhlines');
841
842                obj.Set('chart.background.grid.vsize', vsize);
843                obj.Set('chart.background.grid.hsize', hsize);
844            }
845
846            context.beginPath();
847            context.lineWidth   = obj.Get('chart.background.grid.width') ? obj.Get('chart.background.grid.width') : 1;
848            context.strokeStyle = obj.Get('chart.background.grid.color');
849
850            // Draw the horizontal lines
851            if (obj.Get('chart.background.grid.hlines')) {
852                height = (RGraph.GetHeight(obj) - gutterBottom)
853                for (y=gutterTop; y<height; y+=obj.Get('chart.background.grid.hsize')) {
854                    context.moveTo(gutterLeft, y);
855                    context.lineTo(RGraph.GetWidth(obj) - gutterRight, y);
856                }
857            }
858
859            if (obj.Get('chart.background.grid.vlines')) {
860                // Draw the vertical lines
861                var width = (RGraph.GetWidth(obj) - gutterRight)
862                for (x=gutterLeft; x<=width; x+=obj.Get('chart.background.grid.vsize')) {
863                    context.moveTo(x, gutterTop);
864                    context.lineTo(x, RGraph.GetHeight(obj) - gutterBottom);
865                }
866            }
867
868            if (obj.Get('chart.background.grid.border')) {
869                // Make sure a rectangle, the same colour as the grid goes around the graph
870                context.strokeStyle = obj.Get('chart.background.grid.color');
871                context.strokeRect(gutterLeft, gutterTop, RGraph.GetWidth(obj) - gutterLeft - gutterRight, RGraph.GetHeight(obj) - gutterTop - gutterBottom);
872            }
873        }
874
875        context.stroke();
876
877        // If it's a bar and 3D variant, translate
878        if (variant == '3d') {
879            context.restore();
880        }
881
882        // Draw the title if one is set
883        if ( typeof(obj.Get('chart.title')) == 'string') {
884
885            if (obj.type == 'gantt') {
886                gutterTop -= 10;
887            }
888
889            RGraph.DrawTitle(canvas,
890                             obj.Get('chart.title'),
891                             gutterTop,
892                             null,
893                             obj.Get('chart.text.size') + 2);
894        }
895
896        context.stroke();
897    }
898
899
900    /**
901    * Returns the day number for a particular date. Eg 1st February would be 32
902    *
903    * @param   object obj A date object
904    * @return  int        The day number of the given date
905    */
906    RGraph.GetDays = function (obj)
907    {
908        var year  = obj.getFullYear();
909        var days  = obj.getDate();
910        var month = obj.getMonth();
911       
912        if (month == 0) return days;
913        if (month >= 1) days += 31;
914        if (month >= 2) days += 28;
915
916            // Leap years. Crude, but if this code is still being used
917            // when it stops working, then you have my permission to shoot
918            // me. Oh, you won't be able to - I'll be dead...
919            if (year >= 2008 && year % 4 == 0) days += 1;
920
921        if (month >= 3) days += 31;
922        if (month >= 4) days += 30;
923        if (month >= 5) days += 31;
924        if (month >= 6) days += 30;
925        if (month >= 7) days += 31;
926        if (month >= 8) days += 31;
927        if (month >= 9) days += 30;
928        if (month >= 10) days += 31;
929        if (month >= 11) days += 30;
930       
931        return days;
932    }
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948    /**
949    * Draws the graph key (used by various graphs)
950    *
951    * @param object obj The graph object
952    * @param array  key An array of the texts to be listed in the key
953    * @param colors An array of the colors to be used
954    */
955    RGraph.DrawKey = function (obj, key, colors)
956    {
957        var canvas  = obj.canvas;
958        var context = obj.context;
959        context.lineWidth = 1;
960
961        context.beginPath();
962
963        /**
964        * Key positioned in the gutter
965        */
966        var keypos   = obj.Get('chart.key.position');
967        var textsize = obj.Get('chart.text.size');
968       
969        /**
970        * Change the older chart.key.vpos to chart.key.position.y
971        */
972        if (typeof(obj.Get('chart.key.vpos')) == 'number') {
973            obj.Set('chart.key.position.y', obj.Get('chart.key.vpos') * this.Get('chart.gutter.top') );
974        }
975
976        /**
977        * Account for null values in the key
978        */
979        var key_non_null    = [];
980        var colors_non_null = [];
981        for (var i=0; i<key.length; ++i) {
982            if (key[i] != null) {
983                colors_non_null.push(colors[i]);
984                key_non_null.push(key[i]);
985            }
986        }
987       
988        key    = key_non_null;
989        colors = colors_non_null;
990
991
992
993        if (keypos && keypos == 'gutter') {
994   
995            RGraph.DrawKey_gutter(obj, key, colors);
996
997
998        /**
999        * In-graph style key
1000        */
1001        } else if (keypos && keypos == 'graph') {
1002
1003            RGraph.DrawKey_graph(obj, key, colors);
1004       
1005        } else {
1006            alert('[COMMON] (' + obj.id + ') Unknown key position: ' + keypos);
1007        }
1008    }
1009
1010
1011
1012
1013
1014    /**
1015    * This does the actual drawing of the key when it's in the graph
1016    *
1017    * @param object obj The graph object
1018    * @param array  key The key items to draw
1019    * @param array colors An aray of colors that the key will use
1020    */
1021    RGraph.DrawKey_graph = function (obj, key, colors)
1022    {
1023        var canvas      = obj.canvas;
1024        var context     = obj.context;
1025        var text_size   = typeof(obj.Get('chart.key.text.size')) == 'number' ? obj.Get('chart.key.text.size') : obj.Get('chart.text.size');
1026        var text_font   = obj.Get('chart.text.font');
1027       
1028        var gutterLeft   = obj.Get('chart.gutter.left');
1029        var gutterRight  = obj.Get('chart.gutter.right');
1030        var gutterTop    = obj.Get('chart.gutter.top');
1031        var gutterBottom = obj.Get('chart.gutter.bottom');
1032
1033        var hpos        = obj.Get('chart.yaxispos') == 'right' ? gutterLeft + 10 : RGraph.GetWidth(obj) - gutterRight - 10;
1034        var vpos        = gutterTop + 10;
1035        var title       = obj.Get('chart.title');
1036        var blob_size   = text_size; // The blob of color
1037        var hmargin      = 8; // This is the size of the gaps between the blob of color and the text
1038        var vmargin      = 4; // This is the vertical margin of the key
1039        var fillstyle    = obj.Get('chart.key.background');
1040        var strokestyle  = '#333';
1041        var height       = 0;
1042        var width        = 0;
1043
1044
1045        obj.coordsKey = [];
1046
1047
1048        // Need to set this so that measuring the text works out OK
1049        context.font = text_size + 'pt ' + obj.Get('chart.text.font');
1050
1051        // Work out the longest bit of text
1052        for (i=0; i<key.length; ++i) {
1053            width = Math.max(width, context.measureText(key[i]).width);
1054        }
1055
1056        width += 5;
1057        width += blob_size;
1058        width += 5;
1059        width += 5;
1060        width += 5;
1061
1062        /**
1063        * Now we know the width, we can move the key left more accurately
1064        */
1065        if (   obj.Get('chart.yaxispos') == 'left'
1066            || (obj.type == 'pie' && !obj.Get('chart.yaxispos'))
1067            || (obj.type == 'hbar' && !obj.Get('chart.yaxispos'))
1068            || (obj.type == 'hbar' && obj.Get('chart.yaxispos') == 'center')
1069            || (obj.type == 'rscatter' && !obj.Get('chart.yaxispos'))
1070            || (obj.type == 'radar' && !obj.Get('chart.yaxispos'))
1071            || (obj.type == 'rose' && !obj.Get('chart.yaxispos'))
1072            || (obj.type == 'funnel' && !obj.Get('chart.yaxispos'))
1073            || (obj.type == 'vprogress' && !obj.Get('chart.yaxispos'))
1074            || (obj.type == 'hprogress' && !obj.Get('chart.yaxispos'))
1075           ) {
1076
1077            hpos -= width;
1078        }
1079
1080        /**
1081        * Horizontal alignment
1082        */
1083        if (typeof(obj.Get('chart.key.halign')) == 'string') {
1084            if (obj.Get('chart.key.halign') == 'left') {
1085                hpos = gutterLeft + 10;
1086            } else if (obj.Get('chart.key.halign') == 'right') {
1087                hpos = RGraph.GetWidth(obj) - gutterRight  - width;
1088            }
1089        }
1090
1091        /**
1092        * Specific location coordinates
1093        */
1094        if (typeof(obj.Get('chart.key.position.x')) == 'number') {
1095            hpos = obj.Get('chart.key.position.x');
1096        }
1097       
1098        if (typeof(obj.Get('chart.key.position.y')) == 'number') {
1099            vpos = obj.Get('chart.key.position.y');
1100        }
1101
1102
1103        // Stipulate the shadow for the key box
1104        if (obj.Get('chart.key.shadow')) {
1105            context.shadowColor   = obj.Get('chart.key.shadow.color');
1106            context.shadowBlur    = obj.Get('chart.key.shadow.blur');
1107            context.shadowOffsetX = obj.Get('chart.key.shadow.offsetx');
1108            context.shadowOffsetY = obj.Get('chart.key.shadow.offsety');
1109        }
1110
1111
1112
1113
1114        // Draw the box that the key resides in
1115        context.beginPath();
1116            context.fillStyle   = obj.Get('chart.key.background');
1117            context.strokeStyle = 'black';
1118
1119
1120        if (arguments[3] != false) {
1121
1122            context.lineWidth = obj.Get('chart.key.linewidth') ? obj.Get('chart.key.linewidth') : 1;
1123
1124            // The older square rectangled key
1125            if (obj.Get('chart.key.rounded') == true) {
1126                context.beginPath();
1127                    context.strokeStyle = strokestyle;
1128                    RGraph.strokedCurvyRect(context, hpos, vpos, width - 5, 5 + ( (text_size + 5) * RGraph.getKeyLength(key)),4);
1129       
1130                context.stroke();
1131                context.fill();
1132       
1133                RGraph.NoShadow(obj);
1134       
1135            } else {
1136                context.strokeRect(hpos, vpos, width - 5, 5 + ( (text_size + 5) * RGraph.getKeyLength(key)));
1137                context.fillRect(hpos, vpos, width - 5, 5 + ( (text_size + 5) * RGraph.getKeyLength(key)));
1138            }
1139        }
1140
1141        RGraph.NoShadow(obj);
1142
1143        context.beginPath();
1144
1145            // Draw the labels given
1146            for (var i=key.length - 1; i>=0; i--) {
1147           
1148                var j = Number(i) + 1;
1149
1150                // Draw the blob of color
1151                if (obj.Get('chart.key.color.shape') == 'circle') {
1152                    context.beginPath();
1153                        context.strokeStyle = 'rgba(0,0,0,0)';
1154                        context.fillStyle = colors[i];
1155                        context.arc(hpos + 5 + (blob_size / 2), vpos + (5 * j) + (text_size * j) - text_size + (blob_size / 2), blob_size / 2, 0, 6.26, 0);
1156                    context.fill();
1157               
1158                } else if (obj.Get('chart.key.color.shape') == 'line') {
1159                    context.beginPath();
1160                        context.strokeStyle = colors[i];
1161                        context.moveTo(hpos + 5, vpos + (5 * j) + (text_size * j) - text_size + (blob_size / 2));
1162                        context.lineTo(hpos + blob_size + 5, vpos + (5 * j) + (text_size * j) - text_size + (blob_size / 2));
1163                    context.stroke();
1164
1165                } else {
1166                    context.fillStyle =  colors[i];
1167                    context.fillRect(hpos + 5, vpos + (5 * j) + (text_size * j) - text_size, text_size, text_size + 1);
1168                }
1169
1170                context.beginPath();
1171           
1172                context.fillStyle = 'black';
1173           
1174                RGraph.Text(context,
1175                            text_font,
1176                            text_size,
1177                            hpos + blob_size + 5 + 5,
1178                            vpos + (5 * j) + (text_size * j),
1179                            key[i]);
1180
1181                if (obj.Get('chart.key.interactive')) {
1182               
1183                    var px = hpos + 5;
1184                    var py = vpos + (5 * j) + (text_size * j) - text_size;
1185                    var pw = width - 5 - 5 - 5;
1186                    var ph = text_size;
1187                   
1188                   
1189                    obj.coordsKey.push([px, py, pw, ph]);
1190                }
1191
1192            }
1193        context.fill();
1194
1195        /**
1196        * Install the interactivity event handler
1197        */
1198        if (obj.Get('chart.key.interactive')) {
1199       
1200            RGraph.Register(obj);
1201       
1202            var key_mousemove = function (e)
1203            {
1204                var obj         = e.target.__object__;
1205                var canvas      = obj.canvas;
1206                var context     = obj.context;
1207                var mouseCoords = RGraph.getMouseXY(e);
1208                var mouseX      = mouseCoords[0];
1209                var mouseY      = mouseCoords[1];
1210       
1211                for (var i=0; i<obj.coordsKey.length; ++i) {
1212               
1213                    var px = obj.coordsKey[i][0];
1214                    var py = obj.coordsKey[i][1];
1215                    var pw = obj.coordsKey[i][2];
1216                    var ph = obj.coordsKey[i][3];
1217       
1218                    if (   mouseX > px && mouseX < (px + pw) && mouseY > py && mouseY < (py + ph) ) {
1219                       
1220                        // Necessary?
1221                        //var index = obj.coordsKey.length - i - 1;
1222       
1223                        canvas.style.cursor = 'pointer';
1224                       
1225
1226                       
1227                        return;
1228                    }
1229                   
1230                    canvas.style.cursor = 'default';
1231                   
1232                    if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(canvas_onmousemove_func) == 'function') {
1233                        canvas_onmousemove_func(e);
1234                    }
1235                }
1236            }
1237            canvas.addEventListener('mousemove', key_mousemove, false);
1238            RGraph.AddEventListener(canvas.id, 'mousemove', key_mousemove);
1239       
1240       
1241            var key_click = function (e)
1242            {
1243                RGraph.Redraw();
1244
1245                var obj         = e.target.__object__;
1246                var canvas      = obj.canvas;
1247                var context     = obj.context;
1248                var mouseCoords = RGraph.getMouseXY(e);
1249                var mouseX      = mouseCoords[0];
1250                var mouseY      = mouseCoords[1];
1251
1252                RGraph.DrawKey(obj, obj.Get('chart.key'), obj.Get('chart.colors'));
1253       
1254                for (var i=0; i<obj.coordsKey.length; ++i) {
1255               
1256                    var px = obj.coordsKey[i][0];
1257                    var py = obj.coordsKey[i][1];
1258                    var pw = obj.coordsKey[i][2];
1259                    var ph = obj.coordsKey[i][3];
1260       
1261                    if (   mouseX > px && mouseX < (px + pw) && mouseY > py && mouseY < (py + ph) ) {
1262                       
1263                        var index = obj.coordsKey.length - i - 1;
1264
1265                        // HIGHLIGHT THE LINE HERE
1266                        context.beginPath();
1267                            context.fillStyle = 'rgba(255,255,255,0.9)';
1268                            context.fillRect(obj.Get('chart.gutter.left'),obj.Get('chart.gutter.top'),canvas.width - obj.Get('chart.gutter.left') - obj.Get('chart.gutter.right'),canvas.height - obj.Get('chart.gutter.top') - obj.Get('chart.gutter.bottom'));
1269                        context.fill();
1270
1271                        context.beginPath();
1272                            context.strokeStyle = obj.Get('chart.colors')[index];
1273                            context.lineWidth  = obj.Get('chart.linewidth');
1274                            for (var j=0; j<obj.coords2[index].length; ++j) {
1275                               
1276                                var x = obj.coords2[index][j][0];
1277                                var y = obj.coords2[index][j][1];
1278                           
1279                                if (j == 0) {
1280                                    context.moveTo(x, y);
1281                                } else {
1282                                    context.lineTo(x, y);
1283                                }
1284                        }
1285                        context.stroke();
1286
1287
1288                        context.lineWidth  = 1;
1289                        context.beginPath();
1290                            context.strokeStyle = 'black';
1291                            context.fillStyle   = 'white';
1292                           
1293                            RGraph.SetShadow(obj, 'rgba(0,0,0,0.5)', 0,0,10);
1294
1295                            context.strokeRect(px - 2, py - 2, pw + 4, ph + 4);
1296                            context.fillRect(px - 2, py - 2, pw + 4, ph + 4);
1297
1298                        context.stroke();
1299                        context.fill();
1300
1301
1302                        RGraph.NoShadow(obj);
1303
1304
1305                        context.beginPath();
1306                            context.fillStyle = obj.Get('chart.colors')[index];
1307                            context.fillRect(px, py, blob_size, blob_size);
1308                        context.fill();
1309
1310                        context.beginPath();
1311                            context.fillStyle = obj.Get('chart.text.color');
1312                       
1313                            RGraph.Text(context,
1314                                        obj.Get('chart.text.font'),
1315                                        obj.Get('chart.text.size'),
1316                                        px + 5 + blob_size,
1317                                        py + ph,
1318                                        obj.Get('chart.key')[obj.Get('chart.key').length - i - 1]
1319                                       );
1320                        context.fill();
1321
1322       
1323                        canvas.style.cursor = 'pointer';
1324       
1325                        return;
1326                    }
1327                   
1328                    canvas.style.cursor = 'default';
1329                }
1330            }
1331            canvas.addEventListener('click', key_click, false);
1332            RGraph.AddEventListener(canvas.id, 'click', key_click);
1333           
1334           
1335            //var key_window_click = function (e)
1336            //{
1337            //    RGraph.Redraw();
1338            //}
1339            //window.addEventListener('click', key_window_click, false);
1340            //RGraph.AddEventListener(canvas.id, 'window_click', key_window_click);
1341        }
1342    }
1343
1344
1345
1346
1347
1348
1349    /**
1350    * This does the actual drawing of the key when it's in the gutter
1351    *
1352    * @param object obj The graph object
1353    * @param array  key The key items to draw
1354    * @param array colors An aray of colors that the key will use
1355    */
1356    RGraph.DrawKey_gutter = function (obj, key, colors)
1357    {
1358        var canvas      = obj.canvas;
1359        var context     = obj.context;
1360        var text_size   = typeof(obj.Get('chart.key.text.size')) == 'number' ? obj.Get('chart.key.text.size') : obj.Get('chart.text.size');
1361        var text_font   = obj.Get('chart.text.font');
1362       
1363        var gutterLeft   = obj.Get('chart.gutter.left');
1364        var gutterRight  = obj.Get('chart.gutter.right');
1365        var gutterTop    = obj.Get('chart.gutter.top');
1366        var gutterBottom = obj.Get('chart.gutter.bottom');
1367
1368        var hpos        = RGraph.GetWidth(obj) / 2;
1369        var vpos        = (gutterTop / 2) - 5;
1370        var title       = obj.Get('chart.title');
1371        var blob_size   = text_size; // The blob of color
1372        var hmargin      = 8; // This is the size of the gaps between the blob of color and the text
1373        var vmargin      = 4; // This is the vertical margin of the key
1374        var fillstyle   = obj.Get('chart.key.background');
1375        var strokestyle = 'black';
1376        var length      = 0;
1377
1378
1379
1380        // Need to work out the length of the key first
1381        context.font = text_size + 'pt ' + text_font;
1382        for (i=0; i<key.length; ++i) {
1383            length += hmargin;
1384            length += blob_size;
1385            length += hmargin;
1386            length += context.measureText(key[i]).width;
1387        }
1388        length += hmargin;
1389
1390
1391
1392
1393        /**
1394        * Work out hpos since in the Pie it isn't necessarily dead center
1395        */
1396        if (obj.type == 'pie') {
1397            if (obj.Get('chart.align') == 'left') {
1398                var hpos = obj.radius + gutterLeft;
1399               
1400            } else if (obj.Get('chart.align') == 'right') {
1401                var hpos = obj.canvas.width - obj.radius - gutterRight;
1402
1403            } else {
1404                hpos = canvas.width / 2;
1405            }
1406        }
1407
1408
1409
1410
1411
1412        /**
1413        * This makes the key centered
1414        */ 
1415        hpos -= (length / 2);
1416
1417
1418        /**
1419        * Override the horizontal/vertical positioning
1420        */
1421        if (typeof(obj.Get('chart.key.position.x')) == 'number') {
1422            hpos = obj.Get('chart.key.position.x');
1423        }
1424        if (typeof(obj.Get('chart.key.position.y')) == 'number') {
1425            vpos = obj.Get('chart.key.position.y');
1426        }
1427
1428
1429
1430        /**
1431        * Draw the box that the key sits in
1432        */
1433        if (obj.Get('chart.key.position.gutter.boxed')) {
1434
1435            if (obj.Get('chart.key.shadow')) {
1436                context.shadowColor   = obj.Get('chart.key.shadow.color');
1437                context.shadowBlur    = obj.Get('chart.key.shadow.blur');
1438                context.shadowOffsetX = obj.Get('chart.key.shadow.offsetx');
1439                context.shadowOffsetY = obj.Get('chart.key.shadow.offsety');
1440            }
1441
1442           
1443            context.beginPath();
1444                context.fillStyle = fillstyle;
1445                context.strokeStyle = strokestyle;
1446
1447                if (obj.Get('chart.key.rounded')) {
1448                    RGraph.strokedCurvyRect(context, hpos, vpos - vmargin, length, text_size + vmargin + vmargin)
1449                    // Odd... RGraph.filledCurvyRect(context, hpos, vpos - vmargin, length, text_size + vmargin + vmargin);
1450                } else {
1451                    context.strokeRect(hpos, vpos - vmargin, length, text_size + vmargin + vmargin);
1452                    context.fillRect(hpos, vpos - vmargin, length, text_size + vmargin + vmargin);
1453                }
1454               
1455            context.stroke();
1456            context.fill();
1457
1458
1459            RGraph.NoShadow(obj);
1460        }
1461
1462
1463        /**
1464        * Draw the blobs of color and the text
1465        */
1466        for (var i=0, pos=hpos; i<key.length; ++i) {
1467            pos += hmargin;
1468           
1469            // Draw the blob of color - line
1470            if (obj.Get('chart.key.color.shape') =='line') {
1471               
1472                context.beginPath();
1473                    context.strokeStyle = colors[i];
1474                    context.moveTo(pos, vpos + (blob_size / 2));
1475                    context.lineTo(pos + blob_size, vpos + (blob_size / 2));
1476                context.stroke();
1477               
1478            // Circle
1479            } else if (obj.Get('chart.key.color.shape') == 'circle') {
1480               
1481                context.beginPath();
1482                    context.fillStyle = colors[i];
1483                    context.moveTo(pos, vpos + (blob_size / 2));
1484                    context.arc(pos + (blob_size / 2), vpos + (blob_size / 2), (blob_size / 2), 0, 6.28, 0);
1485                context.fill();
1486
1487
1488            } else {
1489
1490                context.beginPath();
1491                    context.fillStyle = colors[i];
1492                    context.fillRect(pos, vpos, blob_size, blob_size);
1493                context.fill();
1494            }
1495
1496            pos += blob_size;
1497           
1498            pos += hmargin;
1499
1500            context.beginPath();
1501                context.fillStyle = 'black';
1502                RGraph.Text(context, text_font, text_size, pos, vpos + text_size - 1, key[i]);
1503            context.fill();
1504            pos += context.measureText(key[i]).width;
1505        }
1506    }
1507   
1508   
1509    /**
1510    * Returns the key length, but accounts for null values
1511    *
1512    * @param array key The key elements
1513    */
1514    RGraph.getKeyLength = function (key)
1515    {
1516        var len = 0;
1517
1518        for (var i=0; i<key.length; ++i) {
1519            if (key[i] != null) {
1520                ++len;
1521            }
1522        }
1523
1524        return len;
1525    }
1526
1527
1528
1529
1530
1531
1532    /**
1533    * A shortcut for RGraph.pr()
1534    */
1535    function pd(variable)
1536    {
1537        RGraph.pr(variable);
1538    }
1539   
1540    function p(variable)
1541    {
1542        RGraph.pr(variable);
1543    }
1544   
1545    /**
1546    * A shortcut for console.log - as used by Firebug and Chromes console
1547    */
1548    function cl (variable)
1549    {
1550        return console.log(variable);
1551    }
1552
1553
1554    /**
1555    * Makes a clone of an object
1556    *
1557    * @param obj val The object to clone
1558    */
1559    RGraph.array_clone = function (obj)
1560    {
1561        if(obj == null || typeof(obj) != 'object') {
1562            return obj;
1563        }
1564
1565        var temp = [];
1566        //var temp = new obj.constructor();
1567
1568        for(var i=0;i<obj.length; ++i) {
1569            temp[i] = RGraph.array_clone(obj[i]);
1570        }
1571
1572        return temp;
1573    }
1574
1575
1576    /**
1577    * This function reverses an array
1578    */
1579    RGraph.array_reverse = function (arr)
1580    {
1581        var newarr = [];
1582
1583        for (var i=arr.length - 1; i>=0; i--) {
1584            newarr.push(arr[i]);
1585        }
1586
1587        return newarr;
1588    }
1589
1590
1591    /**
1592    * Formats a number with thousand seperators so it's easier to read
1593    *
1594    * @param  integer num The number to format
1595    * @param  string      The (optional) string to prepend to the string
1596    * @param  string      The (optional) string to ap
1597    * pend to the string
1598    * @return string      The formatted number
1599    */
1600    RGraph.number_format = function (obj, num)
1601    {
1602        var i;
1603        var prepend = arguments[2] ? String(arguments[2]) : '';
1604        var append  = arguments[3] ? String(arguments[3]) : '';
1605        var output  = '';
1606        var decimal = '';
1607        var decimal_seperator  = obj.Get('chart.scale.point') ? obj.Get('chart.scale.point') : '.';
1608        var thousand_seperator = obj.Get('chart.scale.thousand') ? obj.Get('chart.scale.thousand') : ',';
1609        RegExp.$1   = '';
1610        var i,j;
1611
1612if (typeof(obj.Get('chart.scale.formatter')) == 'function') {
1613    return obj.Get('chart.scale.formatter')(obj, num);
1614}
1615
1616        // Ignore the preformatted version of "1e-2"
1617        if (String(num).indexOf('e') > 0) {
1618            return String(prepend + String(num) + append);
1619        }
1620
1621        // We need then number as a string
1622        num = String(num);
1623       
1624        // Take off the decimal part - we re-append it later
1625        if (num.indexOf('.') > 0) {
1626            num     = num.replace(/\.(.*)/, '');
1627            decimal = RegExp.$1;
1628        }
1629
1630        // Thousand seperator
1631        //var seperator = arguments[1] ? String(arguments[1]) : ',';
1632        var seperator = thousand_seperator;
1633       
1634        /**
1635        * Work backwards adding the thousand seperators
1636        */
1637        var foundPoint;
1638        for (i=(num.length - 1),j=0; i>=0; j++,i--) {
1639            var character = num.charAt(i);
1640           
1641            if ( j % 3 == 0 && j != 0) {
1642                output += seperator;
1643            }
1644           
1645            /**
1646            * Build the output
1647            */
1648            output += character;
1649        }
1650       
1651        /**
1652        * Now need to reverse the string
1653        */
1654        var rev = output;
1655        output = '';
1656        for (i=(rev.length - 1); i>=0; i--) {
1657            output += rev.charAt(i);
1658        }
1659
1660        // Tidy up
1661        output = output.replace(/^-,/, '-');
1662
1663        // Reappend the decimal
1664        if (decimal.length) {
1665            output =  output + decimal_seperator + decimal;
1666            decimal = '';
1667            RegExp.$1 = '';
1668        }
1669
1670        // Minor bugette
1671        if (output.charAt(0) == '-') {
1672            output = output.replace(/-/, '');
1673            prepend = '-' + prepend;
1674        }
1675
1676        return prepend + output + append;
1677    }
1678
1679
1680    /**
1681    * Draws horizontal coloured bars on something like the bar, line or scatter
1682    */
1683    RGraph.DrawBars = function (obj)
1684    {
1685        var hbars = obj.Get('chart.background.hbars');
1686
1687        /**
1688        * Draws a horizontal bar
1689        */
1690        obj.context.beginPath();
1691       
1692        for (i=0; i<hbars.length; ++i) {
1693           
1694            // If null is specified as the "height", set it to the upper max value
1695            if (hbars[i][1] == null) {
1696                hbars[i][1] = obj.max;
1697           
1698            // If the first index plus the second index is greater than the max value, adjust accordingly
1699            } else if (hbars[i][0] + hbars[i][1] > obj.max) {
1700                hbars[i][1] = obj.max - hbars[i][0];
1701            }
1702
1703
1704            // If height is negative, and the abs() value is greater than .max, use a negative max instead
1705            if (Math.abs(hbars[i][1]) > obj.max) {
1706                hbars[i][1] = -1 * obj.max;
1707            }
1708
1709
1710            // If start point is greater than max, change it to max
1711            if (Math.abs(hbars[i][0]) > obj.max) {
1712                hbars[i][0] = obj.max;
1713            }
1714           
1715            // If start point plus height is less than negative max, use the negative max plus the start point
1716            if (hbars[i][0] + hbars[i][1] < (-1 * obj.max) ) {
1717                hbars[i][1] = -1 * (obj.max + hbars[i][0]);
1718            }
1719
1720            // If the X axis is at the bottom, and a negative max is given, warn the user
1721            if (obj.Get('chart.xaxispos') == 'bottom' && (hbars[i][0] < 0 || (hbars[i][1] + hbars[i][1] < 0)) ) {
1722                alert('[' + obj.type.toUpperCase() + ' (ID: ' + obj.id + ') BACKGROUND HBARS] You have a negative value in one of your background hbars values, whilst the X axis is in the center');
1723            }
1724
1725            var ystart = (obj.grapharea - ((hbars[i][0] / obj.max) * obj.grapharea));
1726            var height = (Math.min(hbars[i][1], obj.max - hbars[i][0]) / obj.max) * obj.grapharea;
1727
1728            // Account for the X axis being in the center
1729            if (obj.Get('chart.xaxispos') == 'center') {
1730                ystart /= 2;
1731                height /= 2;
1732            }
1733           
1734            ystart += obj.Get('chart.gutter.top')
1735
1736            var x = obj.Get('chart.gutter.left');
1737            var y = ystart - height;
1738            var w = obj.canvas.width - obj.Get('chart.gutter.left') - obj.Get('chart.gutter.right');
1739            var h = height;
1740           
1741            // Accommodate Opera :-/
1742            if (navigator.userAgent.indexOf('Opera') != -1 && obj.Get('chart.xaxispos') == 'center' && h < 0) {
1743                h *= -1;
1744                y = y - h;
1745            }
1746           
1747            /**
1748            * Account for X axis at the top
1749            */
1750            if (obj.Get('chart.xaxispos') == 'top') {
1751                y  = obj.canvas.height - y;
1752                h *= -1;
1753            }
1754
1755            obj.context.fillStyle = hbars[i][2];
1756            obj.context.fillRect(x, y, w, h);
1757        }
1758
1759        obj.context.fill();
1760    }
1761
1762
1763    /**
1764    * Draws in-graph labels.
1765    *
1766    * @param object obj The graph object
1767    */
1768    RGraph.DrawInGraphLabels = function (obj)
1769    {
1770        var canvas  = obj.canvas;
1771        var context = obj.context;
1772        var labels  = obj.Get('chart.labels.ingraph');
1773        var labels_processed = [];
1774
1775        // Defaults
1776        var fgcolor   = 'black';
1777        var bgcolor   = 'white';
1778        var direction = 1;
1779
1780        if (!labels) {
1781            return;
1782        }
1783
1784        /**
1785        * Preprocess the labels array. Numbers are expanded
1786        */
1787        for (var i=0; i<labels.length; ++i) {
1788            if (typeof(labels[i]) == 'number') {
1789                for (var j=0; j<labels[i]; ++j) {
1790                    labels_processed.push(null);
1791                }
1792            } else if (typeof(labels[i]) == 'string' || typeof(labels[i]) == 'object') {
1793                labels_processed.push(labels[i]);
1794           
1795            } else {
1796                labels_processed.push('');
1797            }
1798        }
1799
1800        /**
1801        * Turn off any shadow
1802        */
1803        RGraph.NoShadow(obj);
1804
1805        if (labels_processed && labels_processed.length > 0) {
1806
1807            for (var i=0; i<labels_processed.length; ++i) {
1808                if (labels_processed[i]) {
1809                    var coords = obj.coords[i];
1810                   
1811                    if (coords && coords.length > 0) {
1812                        var x      = (obj.type == 'bar' ? coords[0] + (coords[2] / 2) : coords[0]);
1813                        var y      = (obj.type == 'bar' ? coords[1] + (coords[3] / 2) : coords[1]);
1814                        var length = typeof(labels_processed[i][4]) == 'number' ? labels_processed[i][4] : 25;
1815
1816   
1817                        context.beginPath();
1818                        context.fillStyle   = 'black';
1819                        context.strokeStyle = 'black';
1820                       
1821   
1822                        if (obj.type == 'bar') {
1823                       
1824                            /**
1825                            * X axis at the top
1826                            */
1827                            if (obj.Get('chart.xaxispos') == 'top') {
1828                                length *= -1;
1829                            }
1830   
1831                            if (obj.Get('chart.variant') == 'dot') {
1832                                context.moveTo(x, obj.coords[i][1] - 5);
1833                                context.lineTo(x, obj.coords[i][1] - 5 - length);
1834                               
1835                                var text_x = x;
1836                                var text_y = obj.coords[i][1] - 5 - length;
1837                           
1838                            } else if (obj.Get('chart.variant') == 'arrow') {
1839                                context.moveTo(x, obj.coords[i][1] - 5);
1840                                context.lineTo(x, obj.coords[i][1] - 5 - length);
1841                               
1842                                var text_x = x;
1843                                var text_y = obj.coords[i][1] - 5 - length;
1844                           
1845                            } else {
1846   
1847                                context.arc(x, y, 2.5, 0, 6.28, 0);
1848                                context.moveTo(x, y);
1849                                context.lineTo(x, y - length);
1850
1851                                var text_x = x;
1852                                var text_y = y - length;
1853                            }
1854
1855                            context.stroke();
1856                            context.fill();
1857                           
1858   
1859                        } else if (obj.type == 'line') {
1860                       
1861                            if (
1862                                typeof(labels_processed[i]) == 'object' &&
1863                                typeof(labels_processed[i][3]) == 'number' &&
1864                                labels_processed[i][3] == -1
1865                               ) {
1866
1867                                context.moveTo(x, y + 5);
1868                                context.lineTo(x, y + 5 + length);
1869                               
1870                                context.stroke();
1871                                context.beginPath();                               
1872                               
1873                                // This draws the arrow
1874                                context.moveTo(x, y + 5);
1875                                context.lineTo(x - 3, y + 10);
1876                                context.lineTo(x + 3, y + 10);
1877                                context.closePath();
1878                               
1879                                var text_x = x;
1880                                var text_y = y + 5 + length;
1881                           
1882                            } else {
1883                               
1884                                var text_x = x;
1885                                var text_y = y - 5 - length;
1886
1887                                context.moveTo(x, y - 5);
1888                                context.lineTo(x, y - 5 - length);
1889                               
1890                                context.stroke();
1891                                context.beginPath();
1892                               
1893                                // This draws the arrow
1894                                context.moveTo(x, y - 5);
1895                                context.lineTo(x - 3, y - 10);
1896                                context.lineTo(x + 3, y - 10);
1897                                context.closePath();
1898                            }
1899                       
1900                            context.fill();
1901                        }
1902
1903                        // Taken out on the 10th Nov 2010 - unnecessary
1904                        //var width = context.measureText(labels[i]).width;
1905                       
1906                        context.beginPath();
1907                           
1908                            // Fore ground color
1909                            context.fillStyle = (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][1]) == 'string') ? labels_processed[i][1] : 'black';
1910
1911                            RGraph.Text(context,
1912                                        obj.Get('chart.text.font'),
1913                                        obj.Get('chart.text.size'),
1914                                        text_x,
1915                                        text_y,
1916                                        (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][0]) == 'string') ? labels_processed[i][0] : labels_processed[i],
1917                                        'bottom',
1918                                        'center',
1919                                        true,
1920                                        null,
1921                                        (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][2]) == 'string') ? labels_processed[i][2] : 'white');
1922                        context.fill();
1923                    }
1924                }
1925            }
1926        }
1927    }
1928
1929
1930    /**
1931    * This function "fills in" key missing properties that various implementations lack
1932    *
1933    * @param object e The event object
1934    */
1935    RGraph.FixEventObject = function (e)
1936    {
1937        if (RGraph.isIE8()) {
1938           
1939            var e = event;
1940
1941            e.pageX  = (event.clientX + document.body.scrollLeft);
1942            e.pageY  = (event.clientY + document.body.scrollTop);
1943            e.target = event.srcElement;
1944           
1945            if (!document.body.scrollTop && document.documentElement.scrollTop) {
1946                e.pageX += parseInt(document.documentElement.scrollLeft);
1947                e.pageY += parseInt(document.documentElement.scrollTop);
1948            }
1949        }
1950
1951        // This is mainly for FF which doesn't provide offsetX
1952        if (typeof(e.offsetX) == 'undefined' && typeof(e.offsetY) == 'undefined') {
1953            var coords = RGraph.getMouseXY(e);
1954            e.offsetX = coords[0];
1955            e.offsetY = coords[1];
1956        }
1957       
1958        // Any browser that doesn't implement stopPropagation() (MSIE)
1959        if (!e.stopPropagation) {
1960            e.stopPropagation = function () {window.event.cancelBubble = true;}
1961        }
1962       
1963        return e;
1964    }
1965
1966
1967    /**
1968    * Draw crosshairs if enabled
1969    *
1970    * @param object obj The graph object (from which we can get the context and canvas as required)
1971    */
1972    RGraph.DrawCrosshairs = function (obj)
1973    {
1974        if (obj.Get('chart.crosshairs')) {
1975            var canvas  = obj.canvas;
1976            var context = obj.context;
1977           
1978            // 5th November 2010 - removed now that tooltips are DOM2 based.
1979            //if (obj.Get('chart.tooltips') && obj.Get('chart.tooltips').length > 0) {
1980                //alert('[' + obj.type.toUpperCase() + '] Sorry - you cannot have crosshairs enabled with tooltips! Turning off crosshairs...');
1981                //obj.Set('chart.crosshairs', false);
1982                //return;
1983            //}
1984           
1985            canvas.onmousemove = function (e)
1986            {
1987                var e       = RGraph.FixEventObject(e);
1988                var canvas  = obj.canvas;
1989                var context = obj.context;
1990                var width   = canvas.width;
1991                var height  = canvas.height;
1992                var adjustments = obj.Get('chart.tooltips.coords.adjust');
1993               
1994                var gutterLeft   = obj.Get('chart.gutter.left');
1995                var gutterRight  = obj.Get('chart.gutter.right');
1996                var gutterTop    = obj.Get('chart.gutter.top');
1997                var gutterBottom = obj.Get('chart.gutter.bottom');
1998   
1999                var mouseCoords = RGraph.getMouseXY(e);
2000                var x = mouseCoords[0];
2001                var y = mouseCoords[1];
2002               
2003                if (typeof(adjustments) == 'object' && adjustments[0] && adjustments[1]) {
2004                    x = x - adjustments[0];
2005                    y = y - adjustments[1];
2006                }
2007
2008                RGraph.Clear(canvas);
2009                obj.Draw();
2010
2011                if (   x >= gutterLeft
2012                    && y >= gutterTop
2013                    && x <= (width - gutterRight)
2014                    && y <= (height - gutterBottom)
2015                   ) {
2016
2017                    var linewidth = obj.Get('chart.crosshairs.linewidth');
2018                    context.lineWidth = linewidth ? linewidth : 1;
2019
2020                    context.beginPath();
2021                    context.strokeStyle = obj.Get('chart.crosshairs.color');
2022                   
2023                    // Draw a top vertical line
2024                    context.moveTo(x, gutterTop);
2025                    context.lineTo(x, height - gutterBottom);
2026                   
2027                    // Draw a horizontal line
2028                    context.moveTo(gutterLeft, y);
2029                    context.lineTo(width - gutterRight, y);
2030
2031                    context.stroke();
2032                   
2033                    /**
2034                    * Need to show the coords?
2035                    */
2036                    if (obj.Get('chart.crosshairs.coords')) {
2037                        if (obj.type == 'scatter') {
2038
2039                            var xCoord = (((x - obj.Get('chart.gutter.left')) / (obj.canvas.width - gutterLeft - gutterRight)) * (obj.Get('chart.xmax') - obj.Get('chart.xmin'))) + obj.Get('chart.xmin');
2040                                xCoord = xCoord.toFixed(obj.Get('chart.scale.decimals'));
2041                            var yCoord = obj.max - (((y - obj.Get('chart.gutter.top')) / (obj.canvas.height - gutterTop - gutterBottom)) * obj.max);
2042
2043                                if (obj.type == 'scatter' && obj.Get('chart.xaxispos') == 'center') {
2044                                    yCoord = (yCoord - (obj.max / 2)) * 2;
2045                                }
2046
2047                                yCoord = yCoord.toFixed(obj.Get('chart.scale.decimals'));
2048                            var div    = RGraph.Registry.Get('chart.coordinates.coords.div');
2049                            var mouseCoords = RGraph.getMouseXY(e);
2050                            var canvasXY = RGraph.getCanvasXY(canvas);
2051                           
2052                            if (!div) {
2053
2054                                div = document.createElement('DIV');
2055                                div.__object__     = obj;
2056                                div.style.position = 'absolute';
2057                                div.style.backgroundColor = 'white';
2058                                div.style.border = '1px solid black';
2059                                div.style.fontFamily = 'Arial, Verdana, sans-serif';
2060                                div.style.fontSize = '10pt'
2061                                div.style.padding = '2px';
2062                                div.style.opacity = 1;
2063                                div.style.WebkitBorderRadius = '3px';
2064                                div.style.borderRadius = '3px';
2065                                div.style.MozBorderRadius = '3px';
2066                                document.body.appendChild(div);
2067                               
2068                                RGraph.Registry.Set('chart.coordinates.coords.div', div);
2069                            }
2070                           
2071                            // Convert the X/Y pixel coords to correspond to the scale
2072                           
2073                            div.style.opacity = 1;
2074                            div.style.display = 'inline';
2075
2076                            if (!obj.Get('chart.crosshairs.coords.fixed')) {
2077                                div.style.left = Math.max(2, (e.pageX - div.offsetWidth - 3)) + 'px';
2078                                div.style.top = Math.max(2, (e.pageY - div.offsetHeight - 3))  + 'px';
2079                            } else {
2080                                div.style.left = canvasXY[0] + gutterLeft + 3 + 'px';
2081                                div.style.top  = canvasXY[1] + gutterTop + 3 + 'px';
2082                            }
2083
2084                            div.innerHTML = '<span style="color: #666">' + obj.Get('chart.crosshairs.coords.labels.x') + ':</span> ' + xCoord + '<br><span style="color: #666">' + obj.Get('chart.crosshairs.coords.labels.y') + ':</span> ' + yCoord;
2085                           
2086                            canvas.addEventListener('mouseout', RGraph.HideCrosshairCoords, false);
2087
2088                        } else {
2089                            alert('[RGRAPH] Showing crosshair coordinates is only supported on the Scatter chart');
2090                        }
2091                    }
2092                } else {
2093                    RGraph.HideCrosshairCoords();
2094                }
2095            }
2096        }
2097    }
2098
2099    /**
2100    * Thisz function hides the crosshairs coordinates
2101    */
2102    RGraph.HideCrosshairCoords = function ()
2103    {
2104        var div = RGraph.Registry.Get('chart.coordinates.coords.div');
2105
2106        if (   div
2107            && div.style.opacity == 1
2108            && div.__object__.Get('chart.crosshairs.coords.fadeout')
2109           ) {
2110            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.9;}, 50);
2111            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.8;}, 100);
2112            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.7;}, 150);
2113            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.6;}, 200);
2114            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.5;}, 250);
2115            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.4;}, 300);
2116            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.3;}, 350);
2117            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.2;}, 400);
2118            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0.1;}, 450);
2119            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.opacity = 0;}, 500);
2120            setTimeout(function() {RGraph.Registry.Get('chart.coordinates.coords.div').style.display = 'none';}, 550);
2121        }
2122    }
2123
2124
2125    /**
2126    * Trims the right hand side of a string. Removes SPACE, TAB
2127    * CR and LF.
2128    *
2129    * @param string str The string to trim
2130    */
2131    RGraph.rtrim = function (str)
2132    {
2133        return str.replace(/( |\n|\r|\t)+$/, '');
2134    }
2135
2136
2137    /**
2138    * Draws the3D axes/background
2139    */
2140    RGraph.Draw3DAxes = function (obj)
2141    {
2142        var gutterLeft    = obj.Get('chart.gutter.left');
2143        var gutterRight   = obj.Get('chart.gutter.right');
2144        var gutterTop     = obj.Get('chart.gutter.top');
2145        var gutterBottom  = obj.Get('chart.gutter.bottom');
2146
2147        var context = obj.context;
2148        var canvas  = obj.canvas;
2149
2150        context.strokeStyle = '#aaa';
2151        context.fillStyle = '#ddd';
2152
2153        // Draw the vertical left side
2154        context.beginPath();
2155            context.moveTo(gutterLeft, gutterTop);
2156            context.lineTo(gutterLeft + 10, gutterTop - 5);
2157            context.lineTo(gutterLeft + 10, canvas.height - gutterBottom - 5);
2158            context.lineTo(gutterLeft, canvas.height - gutterBottom);
2159        context.closePath();
2160       
2161        context.stroke();
2162        context.fill();
2163
2164        // Draw the bottom floor
2165        context.beginPath();
2166            context.moveTo(gutterLeft, canvas.height - gutterBottom);
2167            context.lineTo(gutterLeft + 10, canvas.height - gutterBottom - 5);
2168            context.lineTo(canvas.width - gutterRight + 10,  canvas.height - gutterBottom - 5);
2169            context.lineTo(canvas.width - gutterRight, canvas.height - gutterBottom);
2170        context.closePath();
2171       
2172        context.stroke();
2173        context.fill();
2174    }
2175
2176    /**
2177    * Turns off any shadow
2178    *
2179    * @param object obj The graph object
2180    */
2181    RGraph.NoShadow = function (obj)
2182    {
2183        obj.context.shadowColor   = 'rgba(0,0,0,0)';
2184        obj.context.shadowBlur    = 0;
2185        obj.context.shadowOffsetX = 0;
2186        obj.context.shadowOffsetY = 0;
2187    }
2188   
2189   
2190    /**
2191    * Sets the four shadow properties - a shortcut function
2192    *
2193    * @param object obj     Your graph object
2194    * @param string color   The shadow color
2195    * @param number offsetx The shadows X offset
2196    * @param number offsety The shadows Y offset
2197    * @param number blur    The blurring effect applied to the shadow
2198    */
2199    RGraph.SetShadow = function (obj, color, offsetx, offsety, blur)
2200    {
2201        obj.context.shadowColor   = color;
2202        obj.context.shadowOffsetX = offsetx;
2203        obj.context.shadowOffsetY = offsety;
2204        obj.context.shadowBlur    = blur;
2205    }
2206
2207
2208    /**
2209    * This function attempts to "fill in" missing functions from the canvas
2210    * context object. Only two at the moment - measureText() nd fillText().
2211    *
2212    * @param object context The canvas 2D context
2213    */
2214    RGraph.OldBrowserCompat = function (context)
2215    {
2216        if (!context.measureText) {
2217       
2218            // This emulates the measureText() function
2219            context.measureText = function (text)
2220            {
2221                var textObj = document.createElement('DIV');
2222                textObj.innerHTML = text;
2223                textObj.style.backgroundColor = 'white';
2224                textObj.style.position = 'absolute';
2225                textObj.style.top = -100
2226                textObj.style.left = 0;
2227                document.body.appendChild(textObj);
2228
2229                var width = {width: textObj.offsetWidth};
2230               
2231                textObj.style.display = 'none';
2232               
2233                return width;
2234            }
2235        }
2236
2237        if (!context.fillText) {
2238            // This emulates the fillText() method
2239            context.fillText    = function (text, targetX, targetY)
2240            {
2241                return false;
2242            }
2243        }
2244       
2245        // If IE8, add addEventListener()
2246        if (!context.canvas.addEventListener) {
2247            window.addEventListener = function (ev, func, bubble)
2248            {
2249                return this.attachEvent('on' + ev, func);
2250            }
2251
2252            context.canvas.addEventListener = function (ev, func, bubble)
2253            {
2254                return this.attachEvent('on' + ev, func);
2255            }
2256        }
2257    }
2258
2259
2260
2261    /**
2262    * This is a function that can be used to run code asynchronously, which can
2263    * be used to speed up the loading of you pages.
2264    *
2265    * @param string func This is the code to run. It can also be a function pointer.
2266    *                    The front page graphs show this function in action. Basically
2267    *                   each graphs code is made in a function, and that function is
2268    *                   passed to this function to run asychronously.
2269    */
2270    RGraph.Async = function (func)
2271    {
2272        return setTimeout(func, arguments[1] ? arguments[1] : 1);
2273    }
2274
2275
2276    /**
2277    * A custom random number function
2278    *
2279    * @param number min The minimum that the number should be
2280    * @param number max The maximum that the number should be
2281    * @param number    How many decimal places there should be. Default for this is 0
2282    */
2283    RGraph.random = function (min, max)
2284    {
2285        var dp = arguments[2] ? arguments[2] : 0;
2286        var r = Math.random();
2287       
2288        return Number((((max - min) * r) + min).toFixed(dp));
2289    }
2290
2291
2292    /**
2293    * Draws a rectangle with curvy corners
2294    *
2295    * @param context object The context
2296    * @param x       number The X coordinate (top left of the square)
2297    * @param y       number The Y coordinate (top left of the square)
2298    * @param w       number The width of the rectangle
2299    * @param h       number The height of the rectangle
2300    * @param         number The radius of the curved corners
2301    * @param         boolean Whether the top left corner is curvy
2302    * @param         boolean Whether the top right corner is curvy
2303    * @param         boolean Whether the bottom right corner is curvy
2304    * @param         boolean Whether the bottom left corner is curvy
2305    */
2306    RGraph.strokedCurvyRect = function (context, x, y, w, h)
2307    {
2308        // The corner radius
2309        var r = arguments[5] ? arguments[5] : 3;
2310
2311        // The corners
2312        var corner_tl = (arguments[6] || arguments[6] == null) ? true : false;
2313        var corner_tr = (arguments[7] || arguments[7] == null) ? true : false;
2314        var corner_br = (arguments[8] || arguments[8] == null) ? true : false;
2315        var corner_bl = (arguments[9] || arguments[9] == null) ? true : false;
2316
2317        context.beginPath();
2318
2319            // Top left side
2320            context.moveTo(x + (corner_tl ? r : 0), y);
2321            context.lineTo(x + w - (corner_tr ? r : 0), y);
2322           
2323            // Top right corner
2324            if (corner_tr) {
2325                context.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2, false);
2326            }
2327
2328            // Top right side
2329            context.lineTo(x + w, y + h - (corner_br ? r : 0) );
2330
2331            // Bottom right corner
2332            if (corner_br) {
2333                context.arc(x + w - r, y - r + h, r, Math.PI * 2, Math.PI * 0.5, false);
2334            }
2335
2336            // Bottom right side
2337            context.lineTo(x + (corner_bl ? r : 0), y + h);
2338
2339            // Bottom left corner
2340            if (corner_bl) {
2341                context.arc(x + r, y - r + h, r, Math.PI * 0.5, Math.PI, false);
2342            }
2343
2344            // Bottom left side
2345            context.lineTo(x, y + (corner_tl ? r : 0) );
2346
2347            // Top left corner
2348            if (corner_tl) {
2349                context.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5, false);
2350            }
2351
2352        context.stroke();
2353    }
2354
2355
2356    /**
2357    * Draws a filled rectangle with curvy corners
2358    *
2359    * @param context object The context
2360    * @param x       number The X coordinate (top left of the square)
2361    * @param y       number The Y coordinate (top left of the square)
2362    * @param w       number The width of the rectangle
2363    * @param h       number The height of the rectangle
2364    * @param         number The radius of the curved corners
2365    * @param         boolean Whether the top left corner is curvy
2366    * @param         boolean Whether the top right corner is curvy
2367    * @param         boolean Whether the bottom right corner is curvy
2368    * @param         boolean Whether the bottom left corner is curvy
2369    */
2370    RGraph.filledCurvyRect = function (context, x, y, w, h)
2371    {
2372        // The corner radius
2373        var r = arguments[5] ? arguments[5] : 3;
2374
2375        // The corners
2376        var corner_tl = (arguments[6] || arguments[6] == null) ? true : false;
2377        var corner_tr = (arguments[7] || arguments[7] == null) ? true : false;
2378        var corner_br = (arguments[8] || arguments[8] == null) ? true : false;
2379        var corner_bl = (arguments[9] || arguments[9] == null) ? true : false;
2380
2381        context.beginPath();
2382
2383            // First draw the corners
2384
2385            // Top left corner
2386            if (corner_tl) {
2387                context.moveTo(x + r, y + r);
2388                context.arc(x + r, y + r, r, Math.PI, 1.5 * Math.PI, false);
2389            } else {
2390                context.fillRect(x, y, r, r);
2391            }
2392
2393            // Top right corner
2394            if (corner_tr) {
2395                context.moveTo(x + w - r, y + r);
2396                context.arc(x + w - r, y + r, r, 1.5 * Math.PI, 0, false);
2397            } else {
2398                context.moveTo(x + w - r, y);
2399                context.fillRect(x + w - r, y, r, r);
2400            }
2401
2402
2403            // Bottom right corner
2404            if (corner_br) {
2405                context.moveTo(x + w - r, y + h - r);
2406                context.arc(x + w - r, y - r + h, r, 0, Math.PI / 2, false);
2407            } else {
2408                context.moveTo(x + w - r, y + h - r);
2409                context.fillRect(x + w - r, y + h - r, r, r);
2410            }
2411
2412            // Bottom left corner
2413            if (corner_bl) {
2414                context.moveTo(x + r, y + h - r);
2415                context.arc(x + r, y - r + h, r, Math.PI / 2, Math.PI, false);
2416            } else {
2417                context.moveTo(x, y + h - r);
2418                context.fillRect(x, y + h - r, r, r);
2419            }
2420
2421            // Now fill it in
2422            context.fillRect(x + r, y, w - r - r, h);
2423            context.fillRect(x, y + r, r + 1, h - r - r);
2424            context.fillRect(x + w - r - 1, y + r, r + 1, h - r - r);
2425
2426        context.fill();
2427    }
2428
2429
2430    /**
2431    * A crude timing function
2432    *
2433    * @param string label The label to use for the time
2434    */
2435    RGraph.Timer = function (label)
2436    {
2437        var d = new Date();
2438
2439        // This uses the Firebug console
2440        console.log(label + ': ' + d.getSeconds() + '.' + d.getMilliseconds());
2441    }
2442
2443
2444    /**
2445    * Hides the palette if it's visible
2446    */
2447    RGraph.HidePalette = function ()
2448    {
2449        var div = RGraph.Registry.Get('palette');
2450
2451        if (typeof(div) == 'object' && div) {
2452            div.style.visibility = 'hidden';
2453            div.style.display    = 'none';
2454            RGraph.Registry.Set('palette', null);
2455        }
2456    }
2457
2458
2459    /**
2460    * Hides the zoomed canvas
2461    */
2462    RGraph.HideZoomedCanvas = function ()
2463    {
2464        if (typeof(__zoomedimage__) == 'object') {
2465            obj = __zoomedimage__.obj;
2466        } else {
2467            return;
2468        }
2469
2470        if (obj.Get('chart.zoom.fade.out')) {
2471            for (var i=10,j=1; i>=0; --i, ++j) {
2472                if (typeof(__zoomedimage__) == 'object') {
2473                    setTimeout("__zoomedimage__.style.opacity = " + String(i / 10), j * 30);
2474                }
2475            }
2476
2477            if (typeof(__zoomedbackground__) == 'object') {
2478                setTimeout("__zoomedbackground__.style.opacity = " + String(i / 10), j * 30);
2479            }
2480        }
2481
2482        if (typeof(__zoomedimage__) == 'object') {
2483            setTimeout("__zoomedimage__.style.display = 'none'", obj.Get('chart.zoom.fade.out') ? 310 : 0);
2484        }
2485
2486        if (typeof(__zoomedbackground__) == 'object') {
2487            setTimeout("__zoomedbackground__.style.display = 'none'", obj.Get('chart.zoom.fade.out') ? 310 : 0);
2488        }
2489    }
2490
2491
2492    /**
2493    * Adds an event handler
2494    *
2495    * @param object obj   The graph object
2496    * @param string event The name of the event, eg ontooltip
2497    * @param object func  The callback function
2498    */
2499    RGraph.AddCustomEventListener = function (obj, name, func)
2500    {
2501        if (typeof(RGraph.events[obj.id]) == 'undefined') {
2502            RGraph.events[obj.id] = [];
2503        }
2504
2505        RGraph.events[obj.id].push([obj, name, func]);
2506       
2507        return RGraph.events[obj.id].length - 1;
2508    }
2509
2510
2511    /**
2512    * Used to fire one of the RGraph custom events
2513    *
2514    * @param object obj   The graph object that fires the event
2515    * @param string event The name of the event to fire
2516    */
2517    RGraph.FireCustomEvent = function (obj, name)
2518    {
2519        if (obj && obj.isRGraph) {
2520            var id = obj.id;
2521   
2522            if (   typeof(id) == 'string'
2523                && typeof(RGraph.events) == 'object'
2524                && typeof(RGraph.events[id]) == 'object'
2525                && RGraph.events[id].length > 0) {
2526   
2527                for(var j=0; j<RGraph.events[id].length; ++j) {
2528                    if (RGraph.events[id][j] && RGraph.events[id][j][1] == name) {
2529                        RGraph.events[id][j][2](obj);
2530                    }
2531                }
2532            }
2533        }
2534    }
2535
2536
2537    /**
2538    * Checks the browser for traces of MSIE8
2539    */
2540    RGraph.isIE8 = function ()
2541    {
2542        return navigator.userAgent.indexOf('MSIE 8') > 0;
2543    }
2544
2545
2546    /**
2547    * Checks the browser for traces of MSIE9
2548    */
2549    RGraph.isIE9 = function ()
2550    {
2551        return navigator.userAgent.indexOf('MSIE 9') > 0;
2552    }
2553
2554
2555    /**
2556    * Checks the browser for traces of MSIE9
2557    */
2558    RGraph.isIE9up = function ()
2559    {
2560        navigator.userAgent.match(/MSIE (\d+)/);
2561
2562        return Number(RegExp.$1) >= 9;
2563    }
2564
2565
2566    /**
2567    * This clears a canvases event handlers. Used at the start of each graphs .Draw() method.
2568    *
2569    * @param string id The ID of the canvas whose event handlers will be cleared
2570    */
2571    RGraph.ClearEventListeners = function (id)
2572    {
2573        for (var i=0; i<RGraph.Registry.Get('chart.event.handlers').length; ++i) {
2574
2575            var el = RGraph.Registry.Get('chart.event.handlers')[i];
2576
2577            if (el && (el[0] == id || el[0] == ('window_' + id)) ) {
2578                if (el[0].substring(0, 7) == 'window_') {
2579                    window.removeEventListener(el[1], el[2], false);
2580                } else {
2581                    document.getElementById(id).removeEventListener(el[1], el[2], false);
2582                }
2583               
2584                RGraph.Registry.Get('chart.event.handlers')[i] = null;
2585            }
2586        }
2587    }
2588
2589
2590    /**
2591    *
2592    */
2593    RGraph.AddEventListener = function (id, e, func)
2594    {
2595        RGraph.Registry.Get('chart.event.handlers').push([id, e, func]);
2596    }
2597
2598
2599    /**
2600    * This function suggests a gutter size based on the widest left label. Given that the bottom
2601    * labels may be longer, this may be a little out.
2602    *
2603    * @param object obj  The graph object
2604    * @param array  data An array of graph data
2605    * @return int        A suggested gutter setting
2606    */
2607    RGraph.getGutterSuggest = function (obj, data)
2608    {
2609        var str = RGraph.number_format(obj, RGraph.array_max(RGraph.getScale(RGraph.array_max(data), obj)), obj.Get('chart.units.pre'), obj.Get('chart.units.post'));
2610
2611        // Take into account the HBar
2612        if (obj.type == 'hbar') {
2613
2614            var str = '';
2615            var len = 0;
2616
2617            for (var i=0; i<obj.Get('chart.labels').length; ++i) {
2618                str = (obj.Get('chart.labels').length > str.length ? obj.Get('chart.labels')[i] : str);
2619            }
2620        }
2621
2622        obj.context.font = obj.Get('chart.text.size') + 'pt ' + obj.Get('chart.text.font');
2623
2624        len = obj.context.measureText(str).width + 5;
2625
2626        return (obj.type == 'hbar' ? len / 3 : len);
2627    }
2628
2629
2630    /**
2631    * A basic Array shift gunction
2632    *
2633    * @param  object The numerical array to work on
2634    * @return        The new array
2635    */
2636    RGraph.array_shift = function (arr)
2637    {
2638        var ret = [];
2639       
2640        for (var i=1; i<arr.length; ++i) ret.push(arr[i]);
2641       
2642        return ret;
2643    }
2644
2645
2646    /**
2647    * If you prefer, you can use the SetConfig() method to set the configuration information
2648    * for your chart. You may find that setting the configuration this way eases reuse.
2649    *
2650    * @param object obj    The graph object
2651    * @param object config The graph configuration information
2652    */
2653    RGraph.SetConfig = function (obj, c)
2654    {
2655        for (i in c) {
2656            if (typeof(i) == 'string') {
2657                obj.Set(i, c[i]);
2658            }
2659        }
2660       
2661        return obj;
2662    }
2663
2664
2665    /**
2666    * This function gets the canvas height. Defaults to the actual
2667    * height but this can be changed by setting chart.height.
2668    *
2669    * @param object obj The graph object
2670    */
2671    RGraph.GetHeight = function (obj)
2672    {
2673        return obj.canvas.height;
2674    }
2675
2676
2677    /**
2678    * This function gets the canvas width. Defaults to the actual
2679    * width but this can be changed by setting chart.width.
2680    *
2681    * @param object obj The graph object
2682    */
2683    RGraph.GetWidth = function (obj)
2684    {       
2685        return obj.canvas.width;
2686    }
2687
2688
2689    /**
2690    * Clears all the custom event listeners that have been registered
2691    *
2692    * @param    string Limits the clearing to this object ID
2693    */
2694    RGraph.RemoveAllCustomEventListeners = function ()
2695    {
2696        var id = arguments[0];
2697
2698        if (id && RGraph.events[id]) {
2699            RGraph.events[id] = [];
2700        } else {
2701            RGraph.events = [];
2702        }
2703    }
2704
2705
2706    /**
2707    * Clears a particular custom event listener
2708    *
2709    * @param object obj The graph object
2710    * @param number i   This is the index that is return by .AddCustomEventListener()
2711    */
2712    RGraph.RemoveCustomEventListener = function (obj, i)
2713    {
2714        if (   typeof(RGraph.events) == 'object'
2715            && typeof(RGraph.events[obj.id]) == 'object'
2716            && typeof(RGraph.events[obj.id][i]) == 'object') {
2717           
2718            RGraph.events[obj.id][i] = null;
2719        }
2720    }
2721
2722
2723    /**
2724    * This draws the background
2725    *
2726    * @param object obj The graph object
2727    */
2728    RGraph.DrawBackgroundImage = function (obj)
2729    {
2730        var img = new Image();
2731        img.__object__  = obj;
2732        img.__canvas__  = obj.canvas;
2733        img.__context__ = obj.context;
2734        img.src         = obj.Get('chart.background.image');
2735       
2736        obj.__background_image__ = img;
2737
2738        img.onload = function ()
2739        {
2740            var obj = this.__object__;
2741           
2742            var gutterLeft   = obj.Get('chart.gutter.left');
2743            var gutterRight  = obj.Get('chart.gutter.right');
2744            var gutterTop    = obj.Get('chart.gutter.top');
2745            var gutterBottom = obj.Get('chart.gutter.bottom');
2746
2747            RGraph.Clear(obj.canvas);
2748
2749            obj.context.drawImage(this,gutterLeft,gutterTop, RGraph.GetWidth(obj) - gutterLeft - gutterRight, RGraph.GetHeight(obj) - gutterTop - gutterBottom);
2750
2751            // Draw the graph
2752            obj.Draw();
2753        }
2754       
2755        img.onerror = function ()
2756        {
2757            var obj = this.__canvas__.__object__;
2758
2759            // Show an error alert
2760            alert('[ERROR] There was an error with the background image that you specified: ' + img.src);
2761           
2762            // Draw the graph, because the onload doesn't fire, and thus that won't draw the chart
2763            obj.Draw();
2764        }
2765    }
2766
2767
2768    /**
2769    * This resets the canvas. Keep in mind that any translate() that has been performed will also be reset.
2770    *
2771    * @param object canvas The canvas
2772    */
2773    RGraph.Reset = function (canvas)
2774    {
2775        canvas.width = canvas.width;
2776    }
Note: See TracBrowser for help on using the repository browser.