Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
    /**
2
    * o-------------------------------------------------------------------------------o
3
    * | This file is part of the RGraph package. RGraph is Free software, licensed    |
4
    * | under the MIT license - so it's free to use for all purposes. Extended        |
5
    * | support is available if required and donations are always welcome! You can    |
6
    * | read more here:                                                               |
7
    * |                         http://www.rgraph.net/support                         |
8
    * o-------------------------------------------------------------------------------o
9
    */
10
 
11
    /**
12
    * Initialise the various objects
13
    */
14
    if (typeof(RGraph) == 'undefined') RGraph = {isRGraph:true,type:'common'};
15
 
16
    RGraph.Highlight      = {};
17
    RGraph.Registry       = {};
18
    RGraph.Registry.store = [];
19
    RGraph.Registry.store['chart.event.handlers']       = [];
20
    RGraph.Registry.store['__rgraph_event_listeners__'] = []; // Used in the new system for tooltips
21
    RGraph.background     = {};
22
    RGraph.objects        = [];
23
    RGraph.Resizing       = {};
24
    RGraph.events         = [];
25
    RGraph.cursor         = [];
26
 
27
    RGraph.HTML = RGraph.HTML || {};
28
 
29
    RGraph.ObjectRegistry                    = {};
30
    RGraph.ObjectRegistry.objects            = {};
31
    RGraph.ObjectRegistry.objects.byUID      = [];
32
    RGraph.ObjectRegistry.objects.byCanvasID = [];
33
 
34
 
35
    /**
36
    * Some "constants"
37
    */
38
    PI       = Math.PI;
39
    HALFPI   = PI / 2;
40
    TWOPI    = PI * 2;
41
    ISFF     = navigator.userAgent.indexOf('Firefox') != -1;
42
    ISOPERA  = navigator.userAgent.indexOf('Opera') != -1;
43
    ISCHROME = navigator.userAgent.indexOf('Chrome') != -1;
44
    ISSAFARI = navigator.userAgent.indexOf('Safari') != -1 && !ISCHROME;
45
    ISWEBKIT = navigator.userAgent.indexOf('WebKit') != -1;
46
    //ISIE     is defined below
47
    //ISIE6    is defined below
48
    //ISIE7    is defined below
49
    //ISIE8    is defined below
50
    //ISIE9    is defined below
51
    //ISIE9    is defined below
52
    //ISIE9UP  is defined below
53
    //ISIE10   is defined below
54
    //ISIE10UP is defined below
55
    //ISIE11   is defined below
56
    //ISIE11UP is defined below
57
    //ISOLD    is defined below
58
 
59
 
60
    /**
61
    * Returns five values which are used as a nice scale
62
    *
63
    * @param  max int    The maximum value of the graph
64
    * @param  obj object The graph object
65
    * @return     array   An appropriate scale
66
    */
67
    RGraph.getScale = function (max, obj)
68
    {
69
        /**
70
        * Special case for 0
71
        */
72
        if (max == 0) {
73
            return ['0.2', '0.4', '0.6', '0.8', '1.0'];
74
        }
75
 
76
        var original_max = max;
77
 
78
        /**
79
        * Manually do decimals
80
        */
81
        if (max <= 1) {
82
            if (max > 0.5) {
83
                return [0.2,0.4,0.6,0.8, Number(1).toFixed(1)];
84
 
85
            } else if (max >= 0.1) {
86
                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];
87
 
88
            } else {
89
 
90
                var tmp = max;
91
                var exp = 0;
92
 
93
                while (tmp < 1.01) {
94
                    exp += 1;
95
                    tmp *= 10;
96
                }
97
 
98
                var ret = ['2e-' + exp, '4e-' + exp, '6e-' + exp, '8e-' + exp, '10e-' + exp];
99
 
100
 
101
                if (max <= ('5e-' + exp)) {
102
                    ret = ['1e-' + exp, '2e-' + exp, '3e-' + exp, '4e-' + exp, '5e-' + exp];
103
                }
104
 
105
                return ret;
106
            }
107
        }
108
 
109
        // Take off any decimals
110
        if (String(max).indexOf('.') > 0) {
111
            max = String(max).replace(/\.\d+$/, '');
112
        }
113
 
114
        var interval = Math.pow(10, Number(String(Number(max)).length - 1));
115
        var topValue = interval;
116
 
117
        while (topValue < max) {
118
            topValue += (interval / 2);
119
        }
120
 
121
        // Handles cases where the max is (for example) 50.5
122
        if (Number(original_max) > Number(topValue)) {
123
            topValue += (interval / 2);
124
        }
125
 
126
        // Custom if the max is greater than 5 and less than 10
127
        if (max < 10) {
128
            topValue = (Number(original_max) <= 5 ? 5 : 10);
129
        }
130
 
131
        /**
132
        * Added 02/11/2010 to create "nicer" scales
133
        */
134
        if (obj && typeof(obj.Get('chart.scale.round')) == 'boolean' && obj.Get('chart.scale.round')) {
135
            topValue = 10 * interval;
136
        }
137
 
138
        return [topValue * 0.2, topValue * 0.4, topValue * 0.6, topValue * 0.8, topValue];
139
    }
140
 
141
 
142
 
143
 
144
 
145
 
146
 
147
 
148
 
149
 
150
 
151
 
152
 
153
 
154
 
155
 
156
 
157
 
158
 
159
 
160
 
161
 
162
 
163
 
164
    /**
165
    * Returns an appropriate scale. The return value is actualy anm object consiosting of:
166
    *  scale.max
167
    *  scale.min
168
    *  scale.scale
169
    *
170
    * @param  obj object  The graph object
171
    * @param  prop object An object consisting of configuration properties
172
    * @return     object  An object containg scale information
173
    */
174
    RGraph.getScale2 = function (obj, opt)
175
    {
176
        var RG   = RGraph;
177
        var ca   = obj.canvas;
178
        var co   = obj.context;
179
        var prop = obj.properties;
180
 
181
        var numlabels    = typeof(opt['ylabels.count']) == 'number' ? opt['ylabels.count'] : 5;
182
        var units_pre    = typeof(opt['units.pre']) == 'string' ? opt['units.pre'] : '';
183
        var units_post   = typeof(opt['units.post']) == 'string' ? opt['units.post'] : '';
184
        var max          = Number(opt['max']);
185
        var min          = typeof(opt['min']) == 'number' ? opt['min'] : 0;
186
        var strict       = opt['strict'];
187
        var decimals     = Number(opt['scale.decimals']); // Sometimes the default is null
188
        var point        = opt['scale.point']; // Default is a string in all chart libraries so no need to cast it
189
        var thousand     = opt['scale.thousand']; // Default is a string in all chart libraries so no need to cast it
190
        var original_max = max;
191
        var round        = opt['scale.round'];
192
        var scale        = {'max':1,'labels':[]};
193
 
194
 
195
 
196
        /**
197
        * Special case for 0
198
        *
199
        * ** Must be first **
200
        */
201
        if (!max) {
202
 
203
            var max   = 1;
204
 
205
            var scale = {max:1,min:0,labels:[]};
206
 
207
            for (var i=0; i<numlabels; ++i) {
208
                var label = ((((max - min) / numlabels) + min) * (i + 1)).toFixed(decimals);
209
                scale.labels.push(units_pre + label + units_post);
210
            }
211
 
212
        /**
213
        * Manually do decimals
214
        */
215
        } else if (max <= 1 && !strict) {
216
 
217
            if (max > 0.5) {
218
 
219
                max  = 1;
220
                min  = min;
221
                scale.min = min;
222
 
223
                for (var i=0; i<numlabels; ++i) {
224
                    var label = ((((max - min) / numlabels) * (i + 1)) + min).toFixed(decimals);
225
 
226
                    scale.labels.push(units_pre + label + units_post);
227
                }
228
 
229
            } else if (max >= 0.1) {
230
 
231
                max   = 0.5;
232
                min   = min;
233
                scale = {'max': 0.5, 'min':min,'labels':[]}
234
 
235
                for (var i=0; i<numlabels; ++i) {
236
                    var label = ((((max - min) / numlabels) + min) * (i + 1)).toFixed(decimals);
237
                    scale.labels.push(units_pre + label + units_post);
238
                }
239
 
240
            } else {
241
 
242
                scale = {'min':min,'labels':[]}
243
                var max_str = String(max);
244
 
245
                if (max_str.indexOf('e') > 0) {
246
                    var numdecimals = Math.abs(max_str.substring(max_str.indexOf('e') + 1));
247
                } else {
248
                    var numdecimals = String(max).length - 2;
249
                }
250
 
251
                var max = 1  / Math.pow(10,numdecimals - 1);
252
 
253
                for (var i=0; i<numlabels; ++i) {
254
                    var label = ((((max - min) / numlabels) + min) * (i + 1));
255
                        label = label.toExponential();
256
                        label = label.split(/e/);
257
                        label[0] = Math.round(label[0]);
258
                        label = label.join('e');
259
                    scale.labels.push(label);
260
                }
261
 
262
                //This makes the top scale value of the format 10e-2 instead of 1e-1
263
                tmp = scale.labels[scale.labels.length - 1].split(/e/);
264
                tmp[0] += 0;
265
                tmp[1] = Number(tmp[1]) - 1;
266
                tmp = tmp[0] + 'e' + tmp[1];
267
                scale.labels[scale.labels.length - 1] = tmp;
268
 
269
                // Add the units
270
                for (var i=0; i<scale.labels.length ; ++i) {
271
                    scale.labels[i] = units_pre + scale.labels[i] + units_post;
272
                }
273
 
274
                scale.max = Number(max);
275
            }
276
 
277
 
278
        } else if (!strict) {
279
 
280
 
281
            /**
282
            * Now comes the scale handling for integer values
283
            */
284
 
285
 
286
            // This accomodates decimals by rounding the max up to the next integer
287
            max = Math.ceil(max);
288
 
289
            var interval = Math.pow(10, Math.max(1, Number(String(Number(max) - Number(min)).length - 1)) );
290
 
291
            var topValue = interval;
292
 
293
            while (topValue < max) {
294
                topValue += (interval / 2);
295
            }
296
 
297
            // Handles cases where the max is (for example) 50.5
298
            if (Number(original_max) > Number(topValue)) {
299
                topValue += (interval / 2);
300
            }
301
 
302
            // Custom if the max is greater than 5 and less than 10
303
            if (max <= 10) {
304
                topValue = (Number(original_max) <= 5 ? 5 : 10);
305
            }
306
 
307
 
308
            // Added 02/11/2010 to create "nicer" scales
309
            if (obj && typeof(round) == 'boolean' && round) {
310
                topValue = 10 * interval;
311
            }
312
 
313
            scale.max = topValue;
314
 
315
            // Now generate the scale. Temporarily set the objects chart.scale.decimal and chart.scale.point to those
316
            //that we've been given as the number_format functuion looks at those instead of using argumrnts.
317
            var tmp_point    = prop['chart.scale.point'];
318
            var tmp_thousand = prop['chart.scale.thousand'];
319
 
320
            obj.Set('chart.scale.thousand', thousand);
321
            obj.Set('chart.scale.point', point);
322
 
323
 
324
            for (var i=0; i<numlabels; ++i) {
325
                scale.labels.push( RG.number_format(obj, ((((i+1) / numlabels) * (topValue - min)) + min).toFixed(decimals), units_pre, units_post) );
326
            }
327
 
328
            obj.Set('chart.scale.thousand', tmp_thousand);
329
            obj.Set('chart.scale.point', tmp_point);
330
 
331
        } else if (typeof(max) == 'number' && strict) {
332
 
333
            /**
334
            * ymax is set and also strict
335
            */
336
            for (var i=0; i<numlabels; ++i) {
337
                scale.labels.push( RG.number_format(obj, ((((i+1) / numlabels) * (max - min)) + min).toFixed(decimals), units_pre, units_post) );
338
            }
339
 
340
            // ???
341
            scale.max = max;
342
        }
343
 
344
 
345
        scale.units_pre  = units_pre;
346
        scale.units_post = units_post;
347
        scale.point      = point;
348
        scale.decimals   = decimals;
349
        scale.thousand   = thousand;
350
        scale.numlabels  = numlabels;
351
        scale.round      = Boolean(round);
352
        scale.min        = min;
353
 
354
 
355
        return scale;
356
    }
357
 
358
 
359
 
360
 
361
 
362
 
363
 
364
 
365
 
366
 
367
 
368
 
369
    /**
370
    * Returns the maximum numeric value which is in an array
371
    *
372
    * @param  array arr The array (can also be a number, in which case it's returned as-is)
373
    * @param  int       Whether to ignore signs (ie negative/positive)
374
    * @return int       The maximum value in the array
375
    */
376
    RGraph.array_max = function (arr)
377
    {
378
        var max       = null;
379
        var MathLocal = Math;
380
 
381
        if (typeof(arr) == 'number') {
382
            return arr;
383
        }
384
 
385
        if (RGraph.is_null(arr)) {
386
            return 0;
387
        }
388
 
389
        for (var i=0,len=arr.length; i<len; ++i) {
390
            if (typeof(arr[i]) == 'number') {
391
 
392
                var val = arguments[1] ? MathLocal.abs(arr[i]) : arr[i];
393
 
394
                if (typeof max == 'number') {
395
                    max = MathLocal.max(max, val);
396
                } else {
397
                    max = val;
398
                }
399
            }
400
        }
401
 
402
        return max;
403
    }
404
 
405
 
406
 
407
 
408
    /**
409
    * Returns the maximum value which is in an array
410
    *
411
    * @param  array arr The array
412
    * @param  int   len The length to pad the array to
413
    * @param  mixed     The value to use to pad the array (optional)
414
    */
415
    RGraph.array_pad = function (arr, len)
416
    {
417
        if (arr.length < len) {
418
            var val = arguments[2] ? arguments[2] : null;
419
 
420
            for (var i=arr.length; i<len; i+=1) {
421
                arr[i] = val;
422
            }
423
        }
424
 
425
        return arr;
426
    }
427
 
428
 
429
 
430
 
431
    /**
432
    * An array sum function
433
    *
434
    * @param  array arr The  array to calculate the total of
435
    * @return int       The summed total of the arrays elements
436
    */
437
    RGraph.array_sum = function (arr)
438
    {
439
        // Allow integers
440
        if (typeof(arr) == 'number') {
441
            return arr;
442
        }
443
 
444
        // Account for null
445
        if (RGraph.is_null(arr)) {
446
            return 0;
447
        }
448
 
449
        var i, sum;
450
        var len = arr.length;
451
 
452
        for(i=0,sum=0;i<len;sum+=arr[i++]);
453
        return sum;
454
    }
455
 
456
 
457
 
458
 
459
    /**
460
    * Takes any number of arguments and adds them to one big linear array
461
    * which is then returned
462
    *
463
    * @param ... mixed The data to linearise. You can strings, booleans, numbers or arrays
464
    */
465
    RGraph.array_linearize = function ()
466
    {
467
        var arr  = [];
468
        var args = arguments;
469
        var RG   = RGraph;
470
 
471
        for (var i=0,len=args.length; i<len; ++i) {
472
 
473
            if (typeof(args[i]) == 'object' && args[i]) {
474
                for (var j=0; j<args[i].length; ++j) {
475
                    var sub = RG.array_linearize(args[i][j]);
476
 
477
                    for (var k=0; k<sub.length; ++k) {
478
                        arr.push(sub[k]);
479
                    }
480
                }
481
            } else {
482
                arr.push(args[i]);
483
            }
484
        }
485
 
486
        return arr;
487
    }
488
 
489
 
490
 
491
 
492
    /**
493
    * This is a useful function which is basically a shortcut for drawing left, right, top and bottom alligned text.
494
    *
495
    * @param object context The context
496
    * @param string font    The font
497
    * @param int    size    The size of the text
498
    * @param int    x       The X coordinate
499
    * @param int    y       The Y coordinate
500
    * @param string text    The text to draw
501
    * @parm  string         The vertical alignment. Can be null. "center" gives center aligned  text, "top" gives top aligned text.
502
    *                       Anything else produces bottom aligned text. Default is bottom.
503
    * @param  string        The horizontal alignment. Can be null. "center" gives center aligned  text, "right" gives right aligned text.
504
    *                       Anything else produces left aligned text. Default is left.
505
    * @param  bool          Whether to show a bounding box around the text. Defaults not to
506
    * @param int            The angle that the text should be rotate at (IN DEGREES)
507
    * @param string         Background color for the text
508
    * @param bool           Whether the text is bold or not
509
    */
510
    RGraph.Text = function (context, font, size, x, y, text)
511
    {
512
        // "Cache" the args as a local variable
513
        var args = arguments;
514
 
515
        // Handle undefined - change it to an empty string
516
        if ((typeof(text) != 'string' && typeof(text) != 'number') || text == 'undefined') {
517
            return;
518
        }
519
 
520
 
521
 
522
 
523
        /**
524
        * This accommodates multi-line text
525
        */
526
        if (typeof(text) == 'string' && text.match(/\r\n/)) {
527
 
528
            var dimensions = RGraph.MeasureText('M', args[11], font, size);
529
 
530
            /**
531
            * Measure the text (width and height)
532
            */
533
 
534
            var arr = text.split('\r\n');
535
 
536
            /**
537
            * Adjust the Y position
538
            */
539
 
540
            // This adjusts the initial y position
541
            if (args[6] && args[6] == 'center') y = (y - (dimensions[1] * ((arr.length - 1) / 2)));
542
 
543
            for (var i=1; i<arr.length; ++i) {
544
 
545
                RGraph.Text(context,
546
                            font,
547
                            size,
548
                            args[9] == -90 ? (x + (size * 1.5)) : x,
549
                            y + (dimensions[1] * i),
550
                            arr[i],
551
                            args[6] ? args[6] : null,
552
                            args[7],
553
                            args[8],
554
                            args[9],
555
                            args[10],
556
                            args[11],
557
                            args[12]);
558
            }
559
 
560
            // Update text to just be the first line
561
            text = arr[0];
562
        }
563
 
564
 
565
        // Accommodate MSIE
566
        if (document.all && ISOLD) {
567
            y += 2;
568
        }
569
 
570
 
571
        context.font = (args[11] ? 'Bold ': '') + size + 'pt ' + font;
572
 
573
        var i;
574
        var origX = x;
575
        var origY = y;
576
        var originalFillStyle = context.fillStyle;
577
        var originalLineWidth = context.lineWidth;
578
 
579
        // Need these now the angle can be specified, ie defaults for the former two args
580
        if (typeof(args[6])  == 'undefined') args[6]  = 'bottom'; // Vertical alignment. Default to bottom/baseline
581
        if (typeof(args[7])  == 'undefined') args[7]  = 'left';   // Horizontal alignment. Default to left
582
        if (typeof(args[8])  == 'undefined') args[8]  = null;     // Show a bounding box. Useful for positioning during development. Defaults to false
583
        if (typeof(args[9])  == 'undefined') args[9]  = 0;        // Angle (IN DEGREES) that the text should be drawn at. 0 is middle right, and it goes clockwise
584
 
585
        // The alignment is recorded here for purposes of Opera compatibility
586
        if (navigator.userAgent.indexOf('Opera') != -1) {
587
            context.canvas.__rgraph_valign__ = args[6];
588
            context.canvas.__rgraph_halign__ = args[7];
589
        }
590
 
591
        // First, translate to x/y coords
592
        context.save();
593
 
594
            context.canvas.__rgraph_originalx__ = x;
595
            context.canvas.__rgraph_originaly__ = y;
596
 
597
            context.translate(x, y);
598
            x = 0;
599
            y = 0;
600
 
601
            // Rotate the canvas if need be
602
            if (args[9]) {
603
                context.rotate(args[9] / (180 / PI));
604
            }
605
 
606
 
607
            // Vertical alignment - defaults to bottom
608
            if (args[6]) {
609
 
610
                var vAlign = args[6];
611
 
612
                if (vAlign == 'center') {
613
                    context.textBaseline = 'middle';
614
                } else if (vAlign == 'top') {
615
                    context.textBaseline = 'top';
616
                }
617
            }
618
 
619
 
620
            // Hoeizontal alignment - defaults to left
621
            if (args[7]) {
622
 
623
                var hAlign = args[7];
624
                var width  = context.measureText(text).width;
625
 
626
                if (hAlign) {
627
                    if (hAlign == 'center') {
628
                        context.textAlign = 'center';
629
                    } else if (hAlign == 'right') {
630
                        context.textAlign = 'right';
631
                    }
632
                }
633
            }
634
 
635
 
636
            context.fillStyle = originalFillStyle;
637
 
638
            /**
639
            * Draw a bounding box if requested
640
            */
641
            context.save();
642
                 context.fillText(text,0,0);
643
                 context.lineWidth = 1;
644
 
645
                var width = context.measureText(text).width;
646
                var width_offset = (hAlign == 'center' ? (width / 2) : (hAlign == 'right' ? width : 0));
647
                var height = size * 1.5; // !!!
648
                var height_offset = (vAlign == 'center' ? (height / 2) : (vAlign == 'top' ? height : 0));
649
                var ieOffset = ISOLD ? 2 : 0;
650
 
651
                if (args[8]) {
652
 
653
                    context.strokeRect(-3 - width_offset,
654
 
655
                                       width + 6,
656
                                       height + 6);
657
                    /**
658
                    * If requested, draw a background for the text
659
                    */
660
                    if (args[10]) {
661
                        context.fillStyle = args[10];
662
                        context.fillRect(-3 - width_offset,
663
 
664
                                           width + 6,
665
                                           height + 6);
666
                    }
667
 
668
 
669
                    context.fillStyle = originalFillStyle;
670
 
671
 
672
                    /**
673
                    * Do the actual drawing of the text
674
                    */
675
                    context.fillText(text,0,0);
676
                }
677
            context.restore();
678
 
679
            // Reset the lineWidth
680
            context.lineWidth = originalLineWidth;
681
 
682
        context.restore();
683
    }
684
 
685
 
686
 
687
 
688
    /**
689
    * Clears the canvas by setting the width. You can specify a colour if you wish.
690
    *
691
    * @param object canvas The canvas to clear
692
    */
693
    RGraph.Clear = function (ca)
694
    {
695
        var RG    = RGraph;
696
        var co    = ca.getContext('2d');
697
        var color = arguments[1];
698
 
699
        if (!ca) {
700
            return;
701
        }
702
 
703
        RG.FireCustomEvent(ca.__object__, 'onbeforeclear');
704
 
705
        if (ISIE8 && !color) {
706
            color = 'white';
707
        }
708
 
709
        /**
710
        * Can now clear the canvas back to fully transparent
711
        */
712
        if (!color || (color && color == 'rgba(0,0,0,0)' || color == 'transparent')) {
713
 
714
            co.clearRect(0,0,ca.width, ca.height);
715
 
716
            // Reset the globalCompositeOperation
717
            co.globalCompositeOperation = 'source-over';
718
 
719
        } else {
720
 
721
            co.fillStyle = color;
722
            co.beginPath();
723
 
724
            if (ISIE8) {
725
                co.fillRect(0,0,ca.width,ca.height);
726
            } else {
727
                co.fillRect(-10,-10,ca.width + 20,ca.height + 20);
728
            }
729
 
730
            co.fill();
731
        }
732
 
733
        //if (RG.ClearAnnotations) {
734
            //RG.ClearAnnotations(ca.id);
735
        //}
736
 
737
        /**
738
        * This removes any background image that may be present
739
        */
740
        if (RG.Registry.Get('chart.background.image.' + ca.id)) {
741
            var img = RG.Registry.Get('chart.background.image.' + ca.id);
742
            img.style.position = 'absolute';
743
            img.style.left     = '-10000px';
744
            img.style.top      = '-10000px';
745
        }
746
 
747
        /**
748
        * This hides the tooltip that is showing IF it has the same canvas ID as
749
        * that which is being cleared
750
        */
751
        if (RG.Registry.Get('chart.tooltip')) {
752
            RG.HideTooltip(ca);
753
            //RG.Redraw();
754
        }
755
 
756
        /**
757
        * Set the cursor to default
758
        */
759
        ca.style.cursor = 'default';
760
 
761
        RG.FireCustomEvent(ca.__object__, 'onclear');
762
    }
763
 
764
 
765
 
766
 
767
    /**
768
    * Draws the title of the graph
769
    *
770
    * @param object  canvas The canvas object
771
    * @param string  text   The title to write
772
    * @param integer gutter The size of the gutter
773
    * @param integer        The center X point (optional - if not given it will be generated from the canvas width)
774
    * @param integer        Size of the text. If not given it will be 14
775
    */
776
    RGraph.DrawTitle = function (obj, text, gutterTop)
777
    {
778
        var RG           = RGraph;
779
        var ca = canvas  = obj.canvas;
780
        var co = context = obj.context;
781
        var prop         = obj.properties;
782
 
783
        var gutterLeft   = prop['chart.gutter.left'];
784
        var gutterRight  = prop['chart.gutter.right'];
785
        var gutterTop    = gutterTop;
786
        var gutterBottom = prop['chart.gutter.bottom'];
787
        var size         = arguments[4] ? arguments[4] : 12;
788
        var bold         = prop['chart.title.bold'];
789
        var centerx      = (arguments[3] ? arguments[3] : ((ca.width - gutterLeft - gutterRight) / 2) + gutterLeft);
790
        var keypos       = prop['chart.key.position'];
791
        var vpos         = prop['chart.title.vpos'];
792
        var hpos         = prop['chart.title.hpos'];
793
        var bgcolor      = prop['chart.title.background'];
794
        var x            = prop['chart.title.x'];
795
        var y            = prop['chart.title.y'];
796
        var halign       = 'center';
797
        var valign       = 'center';
798
 
799
        // Account for 3D effect by faking the key position
800
        if (obj.type == 'bar' && prop['chart.variant'] == '3d') {
801
            keypos = 'gutter';
802
        }
803
 
804
        co.beginPath();
805
        co.fillStyle = prop['chart.text.color'] ? prop['chart.text.color'] : 'black';
806
 
807
 
808
 
809
 
810
 
811
        /**
812
        * Vertically center the text if the key is not present
813
        */
814
        if (keypos && keypos != 'gutter') {
815
            var valign = 'center';
816
 
817
        } else if (!keypos) {
818
            var valign = 'center';
819
 
820
        } else {
821
            var valign = 'bottom';
822
        }
823
 
824
 
825
 
826
 
827
 
828
        // if chart.title.vpos is a number, use that
829
        if (typeof(prop['chart.title.vpos']) == 'number') {
830
            vpos = prop['chart.title.vpos'] * gutterTop;
831
 
832
            if (prop['chart.xaxispos'] == 'top') {
833
                vpos = prop['chart.title.vpos'] * gutterBottom + gutterTop + (ca.height - gutterTop - gutterBottom);
834
            }
835
 
836
        } else {
837
            vpos = gutterTop - size - 5;
838
 
839
            if (prop['chart.xaxispos'] == 'top') {
840
                vpos = ca.height  - gutterBottom + size + 5;
841
            }
842
        }
843
 
844
 
845
 
846
 
847
        // if chart.title.hpos is a number, use that. It's multiplied with the (entire) canvas width
848
        if (typeof(hpos) == 'number') {
849
            centerx = hpos * ca.width;
850
        }
851
 
852
        /**
853
        * Now the chart.title.x and chart.title.y settings override (is set) the above
854
        */
855
        if (typeof(x) == 'number') centerx = x;
856
        if (typeof(y) == 'number') vpos    = y;
857
 
858
 
859
 
860
 
861
        /**
862
        * Horizontal alignment can now (Jan 2013) be specified
863
        */
864
        if (typeof(prop['chart.title.halign']) == 'string') {
865
            halign = prop['chart.title.halign'];
866
        }
867
 
868
        /**
869
        * Vertical alignment can now (Jan 2013) be specified
870
        */
871
        if (typeof(prop['chart.title.valign']) == 'string') {
872
            valign = prop['chart.title.valign'];
873
        }
874
 
875
 
876
 
877
 
878
 
879
        // Set the colour
880
        if (typeof(prop['chart.title.color'] != null)) {
881
            var oldColor = co.fillStyle
882
            var newColor = prop['chart.title.color']
883
            co.fillStyle = newColor ? newColor : 'black';
884
        }
885
 
886
 
887
 
888
 
889
        /**
890
        * Default font is Arial
891
        */
892
        var font = prop['chart.text.font'];
893
 
894
 
895
 
896
 
897
        /**
898
        * Override the default font with chart.title.font
899
        */
900
        if (typeof(prop['chart.title.font']) == 'string') {
901
            font = prop['chart.title.font'];
902
        }
903
 
904
 
905
 
906
 
907
        /**
908
        * Draw the title
909
        */
910
        RG.Text2(obj,{'font':font,
911
                          'size':size,
912
                          'x':centerx,
913
                          'y':vpos,
914
                          'text':text,
915
                          'valign':valign,
916
                          'halign':halign,
917
                          'bounding':bgcolor != null,
918
                          'bounding.fill':bgcolor,
919
                          'bold':bold,
920
                          'tag':'title'
921
                         });
922
 
923
        // Reset the fill colour
924
        co.fillStyle = oldColor;
925
    }
926
 
927
 
928
 
929
 
930
 
931
    /**
932
    * This function returns the mouse position in relation to the canvas
933
    *
934
    * @param object e The event object.
935
    *
936
    RGraph.getMouseXY = function (e)
937
    {
938
        var el = (ISOLD ? event.srcElement : e.target);
939
        var x;
940
        var y;
941
 
942
        // ???
943
        var paddingLeft = el.style.paddingLeft ? parseInt(el.style.paddingLeft) : 0;
944
        var paddingTop  = el.style.paddingTop ? parseInt(el.style.paddingTop) : 0;
945
        var borderLeft  = el.style.borderLeftWidth ? parseInt(el.style.borderLeftWidth) : 0;
946
        var borderTop   = el.style.borderTopWidth  ? parseInt(el.style.borderTopWidth) : 0;
947
 
948
        if (ISIE8) e = event;
949
 
950
        // Browser with offsetX and offsetY
951
        if (typeof(e.offsetX) == 'number' && typeof(e.offsetY) == 'number') {
952
            x = e.offsetX;
953
            y = e.offsetY;
954
 
955
        // FF and other
956
        } else {
957
            x = 0;
958
            y = 0;
959
 
960
            while (el != document.body && el) {
961
                x += el.offsetLeft;
962
                y += el.offsetTop;
963
 
964
                el = el.offsetParent;
965
            }
966
 
967
            x = e.pageX - x;
968
            y = e.pageY - y;
969
        }
970
 
971
        return [x, y];
972
    }*/
973
 
974
 
975
    RGraph.getMouseXY = function(e)
976
    {
977
        var el      = e.target;
978
        var ca      = el;
979
        var caStyle = ca.style;
980
        var offsetX = 0;
981
        var offsetY = 0;
982
        var x;
983
        var y;
984
        var ISFIXED     = (ca.style.position == 'fixed');
985
        var borderLeft  = parseInt(caStyle.borderLeftWidth) || 0;
986
        var borderTop   = parseInt(caStyle.borderTopWidth) || 0;
987
        var paddingLeft = parseInt(caStyle.paddingLeft) || 0
988
        var paddingTop  = parseInt(caStyle.paddingTop) || 0
989
        var additionalX = borderLeft + paddingLeft;
990
        var additionalY = borderTop + paddingTop;
991
 
992
 
993
        if (typeof(e.offsetX) == 'number' && typeof(e.offsetY) == 'number') {
994
 
995
            if (ISFIXED) {
996
                if (ISOPERA) {
997
                    x = e.offsetX;
998
                    y = e.offsetY;
999
 
1000
                } else if (ISWEBKIT) {
1001
                    x = e.offsetX - paddingLeft - borderLeft;
1002
                    y = e.offsetY - paddingTop - borderTop;
1003
 
1004
                } else if (ISIE) {
1005
                    x = e.offsetX - paddingLeft;
1006
                    y = e.offsetY - paddingTop;
1007
 
1008
                } else {
1009
                    x = e.offsetX;
1010
                    y = e.offsetY;
1011
                }
1012
 
1013
 
1014
 
1015
 
1016
            } else {
1017
 
1018
 
1019
 
1020
 
1021
                if (!ISIE && !ISOPERA) {
1022
                    x = e.offsetX - borderLeft - paddingLeft;
1023
                    y = e.offsetY - borderTop - paddingTop;
1024
 
1025
                } else if (ISIE) {
1026
                    x = e.offsetX - paddingLeft;
1027
                    y = e.offsetY - paddingTop;
1028
 
1029
                } else {
1030
                    x = e.offsetX;
1031
                    y = e.offsetY;
1032
                }
1033
            }
1034
 
1035
        } else {
1036
 
1037
            if (typeof(el.offsetParent) != 'undefined') {
1038
                do {
1039
                    offsetX += el.offsetLeft;
1040
                    offsetY += el.offsetTop;
1041
                } while ((el = el.offsetParent));
1042
            }
1043
 
1044
            x = e.pageX - offsetX - additionalX;
1045
            y = e.pageY - offsetY - additionalY;
1046
 
1047
            x -= (2 * (parseInt(document.body.style.borderLeftWidth) || 0));
1048
            y -= (2 * (parseInt(document.body.style.borderTopWidth) || 0));
1049
 
1050
            //x += (parseInt(caStyle.borderLeftWidth) || 0);
1051
            //y += (parseInt(caStyle.borderTopWidth) || 0);
1052
        }
1053
 
1054
        // We return a javascript array with x and y defined
1055
        return [x, y];
1056
    }
1057
 
1058
 
1059
 
1060
 
1061
    /**
1062
    * This function returns a two element array of the canvas x/y position in
1063
    * relation to the page
1064
    *
1065
    * @param object canvas
1066
    */
1067
    RGraph.getCanvasXY = function (canvas)
1068
    {
1069
        var x  = 0;
1070
        var y  = 0;
1071
        var el = canvas; // !!!
1072
 
1073
        do {
1074
 
1075
            x += el.offsetLeft;
1076
            y += el.offsetTop;
1077
 
1078
            // ACCOUNT FOR TABLES IN wEBkIT
1079
            if (el.tagName.toLowerCase() == 'table' && (ISCHROME || ISSAFARI)) {
1080
                x += parseInt(el.border) || 0;
1081
                y += parseInt(el.border) || 0;
1082
            }
1083
 
1084
            el = el.offsetParent;
1085
 
1086
        } while (el && el.tagName.toLowerCase() != 'body');
1087
 
1088
 
1089
        var paddingLeft = canvas.style.paddingLeft ? parseInt(canvas.style.paddingLeft) : 0;
1090
        var paddingTop  = canvas.style.paddingTop ? parseInt(canvas.style.paddingTop) : 0;
1091
        var borderLeft  = canvas.style.borderLeftWidth ? parseInt(canvas.style.borderLeftWidth) : 0;
1092
        var borderTop   = canvas.style.borderTopWidth  ? parseInt(canvas.style.borderTopWidth) : 0;
1093
 
1094
        if (navigator.userAgent.indexOf('Firefox') > 0) {
1095
            x += parseInt(document.body.style.borderLeftWidth) || 0;
1096
            y += parseInt(document.body.style.borderTopWidth) || 0;
1097
        }
1098
 
1099
        return [x + paddingLeft + borderLeft, y + paddingTop + borderTop];
1100
    }
1101
 
1102
 
1103
 
1104
 
1105
    /**
1106
    * This function determines whther a canvas is fixed (CSS positioning) or not. If not it returns
1107
    * false. If it is then the element that is fixed is returned (it may be a parent of the canvas).
1108
    *
1109
    * @return Either false or the fixed positioned element
1110
    */
1111
    RGraph.isFixed = function (canvas)
1112
    {
1113
        var obj = canvas;
1114
        var i = 0;
1115
 
1116
        while (obj && obj.tagName.toLowerCase() != 'body' && i < 99) {
1117
 
1118
            if (obj.style.position == 'fixed') {
1119
                return obj;
1120
            }
1121
 
1122
            obj = obj.offsetParent;
1123
        }
1124
 
1125
        return false;
1126
    }
1127
 
1128
 
1129
 
1130
 
1131
    /**
1132
    * Registers a graph object (used when the canvas is redrawn)
1133
    *
1134
    * @param object obj The object to be registered
1135
    */
1136
    RGraph.Register = function (obj)
1137
    {
1138
        // Checking this property ensures the object is only registered once
1139
        if (!obj.Get('chart.noregister')) {
1140
            // As of 21st/1/2012 the object registry is now used
1141
            RGraph.ObjectRegistry.Add(obj);
1142
            obj.Set('chart.noregister', true);
1143
        }
1144
    }
1145
 
1146
 
1147
 
1148
 
1149
    /**
1150
    * Causes all registered objects to be redrawn
1151
    *
1152
    * @param string An optional color to use to clear the canvas
1153
    */
1154
    RGraph.Redraw = function ()
1155
    {
1156
        var objectRegistry = RGraph.ObjectRegistry.objects.byCanvasID;
1157
 
1158
        // Get all of the canvas tags on the page
1159
        var tags = document.getElementsByTagName('canvas');
1160
 
1161
        for (var i=0,len=tags.length; i<len; ++i) {
1162
            if (tags[i].__object__ && tags[i].__object__.isRGraph) {
1163
 
1164
                // Only clear the canvas if it's not Trace'ing - this applies to the Line/Scatter Trace effects
1165
                if (!tags[i].noclear) {
1166
                    RGraph.Clear(tags[i], arguments[0] ? arguments[0] : null);
1167
                }
1168
            }
1169
        }
1170
 
1171
        // Go through the object registry and redraw *all* of the canvas'es that have been registered
1172
        for (var i=0,len=objectRegistry.length; i<len; ++i) {
1173
            if (objectRegistry[i]) {
1174
                var id = objectRegistry[i][0];
1175
                objectRegistry[i][1].Draw();
1176
            }
1177
        }
1178
    }
1179
 
1180
 
1181
 
1182
 
1183
    /**
1184
    * Causes all registered objects ON THE GIVEN CANVAS to be redrawn
1185
    *
1186
    * @param canvas object The canvas object to redraw
1187
    * @param        bool   Optional boolean which defaults to true and determines whether to clear the canvas
1188
    */
1189
    RGraph.RedrawCanvas = function (canvas)
1190
    {
1191
        var objects = RGraph.ObjectRegistry.getObjectsByCanvasID(canvas.id);
1192
 
1193
        /**
1194
        * First clear the canvas
1195
        */
1196
        if (!arguments[1] || (typeof(arguments[1]) == 'boolean' && !arguments[1] == false) ) {
1197
 
1198
            // TODO This function should really support passing a color as the second optional argument - which is then used in the below
1199
            // call
1200
            RGraph.Clear(canvas);
1201
        }
1202
 
1203
        /**
1204
        * Now redraw all the charts associated with that canvas
1205
        */
1206
        for (var i=0,len=objects.length; i<len; ++i) {
1207
            if (objects[i]) {
1208
                if (objects[i] && objects[i].isRGraph) { // Is it an RGraph object ??
1209
                    objects[i].Draw();
1210
                }
1211
            }
1212
        }
1213
    }
1214
 
1215
 
1216
 
1217
 
1218
    /**
1219
    * This function draws the background for the bar chart, line chart and scatter chart.
1220
    *
1221
    * @param  object obj The graph object
1222
    */
1223
    RGraph.background.Draw = function (obj)
1224
    {
1225
        var RG           = RGraph;
1226
        var ca = canvas  = obj.canvas;
1227
        var co = context = obj.context;
1228
        var prop         = obj.properties;
1229
 
1230
        var height       = 0;
1231
        var gutterLeft   = obj.gutterLeft;
1232
        var gutterRight  = obj.gutterRight;
1233
        var gutterTop    = obj.gutterTop;
1234
        var gutterBottom = obj.gutterBottom;
1235
        var variant      = prop['chart.variant'];
1236
 
1237
        co.fillStyle = prop['chart.text.color'];
1238
 
1239
        // If it's a bar and 3D variant, translate
1240
        if (variant == '3d') {
1241
            co.save();
1242
            co.translate(10, -5);
1243
        }
1244
 
1245
        // X axis title
1246
        if (typeof(prop['chart.title.xaxis']) == 'string' && prop['chart.title.xaxis'].length) {
1247
 
1248
            var size = prop['chart.text.size'] + 2;
1249
            var font = prop['chart.text.font'];
1250
            var bold = prop['chart.title.xaxis.bold'];
1251
 
1252
            if (typeof(prop['chart.title.xaxis.size']) == 'number') {
1253
                size = prop['chart.title.xaxis.size'];
1254
            }
1255
 
1256
            if (typeof(prop['chart.title.xaxis.font']) == 'string') {
1257
                font = prop['chart.title.xaxis.font'];
1258
            }
1259
 
1260
            var hpos = ((ca.width - gutterLeft - gutterRight) / 2) + gutterLeft;
1261
            var vpos = ca.height - gutterBottom + 25;
1262
 
1263
            if (typeof(prop['chart.title.xaxis.pos']) == 'number') {
1264
                vpos = ca.height - (gutterBottom * prop['chart.title.xaxis.pos']);
1265
            }
1266
 
1267
 
1268
 
1269
 
1270
            // Specifically specified X/Y positions
1271
            if (typeof prop['chart.title.xaxis.x'] == 'number') {
1272
                hpos = prop['chart.title.xaxis.x'];
1273
            }
1274
 
1275
            if (typeof prop['chart.title.xaxis.y'] == 'number') {
1276
                vpos = prop['chart.title.xaxis.y'];
1277
            }
1278
 
1279
 
1280
 
1281
 
1282
            RG.Text2(obj, {'font':font,
1283
                           'size':size,
1284
                           'x':hpos,
1285
                           'y':vpos,
1286
                           'text':prop['chart.title.xaxis'],
1287
                           'halign':'center',
1288
                           'valign':'center',
1289
                           'bold':bold,
1290
                           'tag': 'title xaxis'
1291
                          });
1292
        }
1293
 
1294
        // Y axis title
1295
        if (typeof(prop['chart.title.yaxis']) == 'string' && prop['chart.title.yaxis'].length) {
1296
 
1297
            var size  = prop['chart.text.size'] + 2;
1298
            var font  = prop['chart.text.font'];
1299
            var angle = 270;
1300
            var bold  = prop['chart.title.yaxis.bold'];
1301
            var color = prop['chart.title.yaxis.color'];
1302
 
1303
            if (typeof(prop['chart.title.yaxis.pos']) == 'number') {
1304
                var yaxis_title_pos = prop['chart.title.yaxis.pos'] * gutterLeft;
1305
            } else {
1306
                var yaxis_title_pos = ((gutterLeft - 25) / gutterLeft) * gutterLeft;
1307
            }
1308
 
1309
            if (typeof(prop['chart.title.yaxis.size']) == 'number') {
1310
                size = prop['chart.title.yaxis.size'];
1311
            }
1312
 
1313
            if (typeof(prop['chart.title.yaxis.font']) == 'string') {
1314
                font = prop['chart.title.yaxis.font'];
1315
            }
1316
 
1317
            if (prop['chart.title.yaxis.align'] == 'right' || prop['chart.title.yaxis.position'] == 'right') {
1318
                angle = 90;
1319
                yaxis_title_pos = prop['chart.title.yaxis.pos'] ? (ca.width - gutterRight) + (prop['chart.title.yaxis.pos'] * gutterRight) :
1320
                                                                   ca.width - gutterRight + prop['chart.text.size'] + 5;
1321
            } else {
1322
                yaxis_title_pos = yaxis_title_pos;
1323
            }
1324
 
1325
            var y = ((ca.height - gutterTop - gutterBottom) / 2) + gutterTop;
1326
 
1327
            // Specifically specified X/Y positions
1328
            if (typeof prop['chart.title.yaxis.x'] == 'number') {
1329
                yaxis_title_pos = prop['chart.title.yaxis.x'];
1330
            }
1331
 
1332
            if (typeof prop['chart.title.yaxis.y'] == 'number') {
1333
                y = prop['chart.title.yaxis.y'];
1334
            }
1335
 
1336
            co.fillStyle = color;
1337
            RG.Text2(obj, {'font':font,
1338
                           'size':size,
1339
                           'x':yaxis_title_pos,
1340
                           'y':y,
1341
                           'valign':'center',
1342
                           'halign':'center',
1343
                           'angle':angle,
1344
                           'bold':bold,
1345
                           'text':prop['chart.title.yaxis'],
1346
                           'tag':'title yaxis'
1347
                          });
1348
        }
1349
 
1350
        /**
1351
        * If the background color is spec ified - draw that. It's a rectangle that fills the
1352
        * entire are within the gutters
1353
        */
1354
        var bgcolor = prop['chart.background.color'];
1355
        if (bgcolor) {
1356
            co.fillStyle = bgcolor;
1357
            co.fillRect(gutterLeft, gutterTop, ca.width - gutterLeft - gutterRight, ca.height - gutterTop - gutterBottom);
1358
        }
1359
 
1360
        /**
1361
        * Draw horizontal background bars
1362
        */
1363
        co.beginPath(); // Necessary?
1364
 
1365
        co.fillStyle   = prop['chart.background.barcolor1'];
1366
        co.strokeStyle = co.fillStyle;
1367
        height = (ca.height - gutterBottom);
1368
 
1369
        for (var i=gutterTop; i<height ; i+=80) {
1370
            co.fillRect(gutterLeft, i, ca.width - gutterLeft - gutterRight, Math.min(40, ca.height - gutterBottom - i) );
1371
        }
1372
 
1373
        co.fillStyle   = prop['chart.background.barcolor2'];
1374
        co.strokeStyle = co.fillStyle;
1375
        height = (ca.height - gutterBottom);
1376
 
1377
        for (var i= (40 + gutterTop); i<height; i+=80) {
1378
            co.fillRect(gutterLeft, i, ca.width - gutterLeft - gutterRight, i + 40 > (ca.height - gutterBottom) ? ca.height - (gutterBottom + i) : 40);
1379
        }
1380
 
1381
        //context.stroke();
1382
        co.beginPath();
1383
 
1384
 
1385
        // Draw the background grid
1386
        if (prop['chart.background.grid']) {
1387
 
1388
            // If autofit is specified, use the .numhlines and .numvlines along with the width to work
1389
            // out the hsize and vsize
1390
            if (prop['chart.background.grid.autofit']) {
1391
 
1392
                /**
1393
                * Align the grid to the tickmarks
1394
                */
1395
                if (prop['chart.background.grid.autofit.align']) {
1396
 
1397
                    // Align the horizontal lines
1398
                    obj.Set('chart.background.grid.autofit.numhlines', prop['chart.ylabels.count']);
1399
 
1400
                    // Align the vertical lines for the line
1401
                    if (obj.type == 'line') {
1402
                        if (prop['chart.labels'] && prop['chart.labels'].length) {
1403
                            obj.Set('chart.background.grid.autofit.numvlines', prop['chart.labels'].length - 1);
1404
                        } else {
1405
                            obj.Set('chart.background.grid.autofit.numvlines', obj.data[0].length - 1);
1406
                        }
1407
 
1408
                    // Align the vertical lines for the bar
1409
                    } else if (obj.type == 'bar' && prop['chart.labels'] && prop['chart.labels'].length) {
1410
                        obj.Set('chart.background.grid.autofit.numvlines', prop['chart.labels'].length);
1411
                    }
1412
                }
1413
 
1414
                var vsize = ((ca.width - gutterLeft - gutterRight)) / prop['chart.background.grid.autofit.numvlines'];
1415
                var hsize = (ca.height - gutterTop - gutterBottom) / prop['chart.background.grid.autofit.numhlines'];
1416
 
1417
                obj.Set('chart.background.grid.vsize', vsize);
1418
                obj.Set('chart.background.grid.hsize', hsize);
1419
            }
1420
 
1421
            co.beginPath();
1422
            co.lineWidth   = prop['chart.background.grid.width'] ? prop['chart.background.grid.width'] : 1;
1423
            co.strokeStyle = prop['chart.background.grid.color'];
1424
 
1425
            // Dashed background grid
1426
            if (prop['chart.background.grid.dashed'] && typeof co.setLineDash == 'function') {
1427
                co.setLineDash([3,2]);
1428
            }
1429
 
1430
            // Dotted background grid
1431
            if (prop['chart.background.grid.dotted'] && typeof co.setLineDash == 'function') {
1432
                co.setLineDash([1,2]);
1433
            }
1434
 
1435
 
1436
            // Draw the horizontal lines
1437
            if (prop['chart.background.grid.hlines']) {
1438
                height = (ca.height - gutterBottom)
1439
                for (y=gutterTop; y<height; y+=prop['chart.background.grid.hsize']) {
1440
                    context.moveTo(gutterLeft, Math.round(y));
1441
                    context.lineTo(ca.width - gutterRight, Math.round(y));
1442
                }
1443
            }
1444
 
1445
            if (prop['chart.background.grid.vlines']) {
1446
                // Draw the vertical lines
1447
                var width = (ca.width - gutterRight)
1448
                for (x=gutterLeft; x<=width; x+=prop['chart.background.grid.vsize']) {
1449
                    co.moveTo(Math.round(x), gutterTop);
1450
                    co.lineTo(Math.round(x), ca.height - gutterBottom);
1451
                }
1452
            }
1453
 
1454
            if (prop['chart.background.grid.border']) {
1455
                // Make sure a rectangle, the same colour as the grid goes around the graph
1456
                co.strokeStyle = prop['chart.background.grid.color'];
1457
                co.strokeRect(Math.round(gutterLeft), Math.round(gutterTop), ca.width - gutterLeft - gutterRight, ca.height - gutterTop - gutterBottom);
1458
            }
1459
        }
1460
 
1461
        context.stroke();
1462
 
1463
        // Reset the line dash
1464
        if (typeof co.setLineDash == 'function') {
1465
            co.setLineDash([1,0]);
1466
        }
1467
 
1468
        // If it's a bar and 3D variant, translate
1469
        if (variant == '3d') {
1470
            co.restore();
1471
        }
1472
 
1473
        // Draw the title if one is set
1474
        if ( typeof(prop['chart.title']) == 'string') {
1475
 
1476
            if (obj.type == 'gantt') {
1477
                gutterTop -= 10;
1478
            }
1479
 
1480
            RG.DrawTitle(obj,
1481
                         prop['chart.title'],
1482
                         gutterTop,
1483
                         null,
1484
                         prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2);
1485
        }
1486
 
1487
        co.stroke();
1488
    }
1489
 
1490
 
1491
 
1492
 
1493
    /**
1494
    * Makes a clone of an object
1495
    *
1496
    * @param obj val The object to clone
1497
    */
1498
    RGraph.array_clone = function (obj)
1499
    {
1500
        var RG = RGraph;
1501
 
1502
        if(obj == null || typeof(obj) != 'object') {
1503
            return obj;
1504
        }
1505
 
1506
        var temp = [];
1507
 
1508
        for (var i=0,len=obj.length;i<len; ++i) {
1509
 
1510
            if (typeof(obj[i]) == 'number') {
1511
                temp[i] = (function (arg) {return Number(arg);})(obj[i]);
1512
            } else if (typeof(obj[i]) == 'string') {
1513
                temp[i] = (function (arg) {return String(arg);})(obj[i]);
1514
            } else if (typeof(obj[i]) == 'function') {
1515
                temp[i] = obj[i];
1516
 
1517
            } else {
1518
                temp[i] = RG.array_clone(obj[i]);
1519
            }
1520
        }
1521
 
1522
        return temp;
1523
    }
1524
 
1525
 
1526
 
1527
 
1528
    /**
1529
    * Formats a number with thousand seperators so it's easier to read
1530
    *
1531
    * @param  integer obj The chart object
1532
    * @param  integer num The number to format
1533
    * @param  string      The (optional) string to prepend to the string
1534
    * @param  string      The (optional) string to append to the string
1535
    * @return string      The formatted number
1536
    */
1537
    RGraph.number_format = function (obj, num)
1538
    {
1539
        var RG   = RGraph;
1540
        var ca   = obj.canvas;
1541
        var co   = obj.context;
1542
        var prop = obj.properties;
1543
 
1544
        var i;
1545
        var prepend = arguments[2] ? String(arguments[2]) : '';
1546
        var append  = arguments[3] ? String(arguments[3]) : '';
1547
        var output  = '';
1548
        var decimal = '';
1549
        var decimal_seperator  = typeof(prop['chart.scale.point']) == 'string' ? prop['chart.scale.point'] : '.';
1550
        var thousand_seperator = typeof(prop['chart.scale.thousand']) == 'string' ? prop['chart.scale.thousand'] : ',';
1551
        RegExp.$1   = '';
1552
        var i,j;
1553
 
1554
        if (typeof(prop['chart.scale.formatter']) == 'function') {
1555
            return prop['chart.scale.formatter'](obj, num);
1556
        }
1557
 
1558
        // Ignore the preformatted version of "1e-2"
1559
        if (String(num).indexOf('e') > 0) {
1560
            return String(prepend + String(num) + append);
1561
        }
1562
 
1563
        // We need then number as a string
1564
        num = String(num);
1565
 
1566
        // Take off the decimal part - we re-append it later
1567
        if (num.indexOf('.') > 0) {
1568
            var tmp = num;
1569
            num     = num.replace(/\.(.*)/, ''); // The front part of the number
1570
            decimal = tmp.replace(/(.*)\.(.*)/, '$2'); // The decimal part of the number
1571
        }
1572
 
1573
        // Thousand seperator
1574
        //var seperator = arguments[1] ? String(arguments[1]) : ',';
1575
        var seperator = thousand_seperator;
1576
 
1577
        /**
1578
        * Work backwards adding the thousand seperators
1579
        */
1580
        var foundPoint;
1581
        for (i=(num.length - 1),j=0; i>=0; j++,i--) {
1582
            var character = num.charAt(i);
1583
 
1584
            if ( j % 3 == 0 && j != 0) {
1585
                output += seperator;
1586
            }
1587
 
1588
            /**
1589
            * Build the output
1590
            */
1591
            output += character;
1592
        }
1593
 
1594
        /**
1595
        * Now need to reverse the string
1596
        */
1597
        var rev = output;
1598
        output = '';
1599
        for (i=(rev.length - 1); i>=0; i--) {
1600
            output += rev.charAt(i);
1601
        }
1602
 
1603
        // Tidy up
1604
        //output = output.replace(/^-,/, '-');
1605
        if (output.indexOf('-' + prop['chart.scale.thousand']) == 0) {
1606
            output = '-' + output.substr(('-' + prop['chart.scale.thousand']).length);
1607
        }
1608
 
1609
        // Reappend the decimal
1610
        if (decimal.length) {
1611
            output =  output + decimal_seperator + decimal;
1612
            decimal = '';
1613
            RegExp.$1 = '';
1614
        }
1615
 
1616
        // Minor bugette
1617
        if (output.charAt(0) == '-') {
1618
            output = output.replace(/-/, '');
1619
            prepend = '-' + prepend;
1620
        }
1621
 
1622
        return prepend + output + append;
1623
    }
1624
 
1625
 
1626
 
1627
 
1628
    /**
1629
    * Draws horizontal coloured bars on something like the bar, line or scatter
1630
    */
1631
    RGraph.DrawBars = function (obj)
1632
    {
1633
        var prop  = obj.properties;
1634
        var co    = obj.context;
1635
        var ca    = obj.canvas;
1636
        var RG    = RGraph;
1637
        var hbars = prop['chart.background.hbars'];
1638
 
1639
        if (hbars === null) {
1640
            return;
1641
        }
1642
 
1643
        /**
1644
        * Draws a horizontal bar
1645
        */
1646
        co.beginPath();
1647
 
1648
        for (i=0,len=hbars.length; i<len; ++i) {
1649
 
1650
            var start  = hbars[i][0];
1651
            var length = hbars[i][1];
1652
            var color  = hbars[i][2];
1653
 
1654
 
1655
            // Perform some bounds checking
1656
            if(RG.is_null(start))start = obj.scale2.max
1657
            if (start > obj.scale2.max) start = obj.scale2.max;
1658
            if (RG.is_null(length)) length = obj.scale2.max - start;
1659
            if (start + length > obj.scale2.max) length = obj.scale2.max - start;
1660
            if (start + length < (-1 * obj.scale2.max) ) length = (-1 * obj.scale2.max) - start;
1661
 
1662
            if (prop['chart.xaxispos'] == 'center' && start == obj.scale2.max && length < (obj.scale2.max * -2)) {
1663
                length = obj.scale2.max * -2;
1664
            }
1665
 
1666
 
1667
            /**
1668
            * Draw the bar
1669
            */
1670
            var x = prop['chart.gutter.left'];
1671
            var y = obj.getYCoord(start);
1672
            var w = ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'];
1673
            var h = obj.getYCoord(start + length) - y;
1674
 
1675
            // Accommodate Opera :-/
1676
            if (ISOPERA != -1 && prop['chart.xaxispos'] == 'center' && h < 0) {
1677
                h *= -1;
1678
                y = y - h;
1679
            }
1680
 
1681
            /**
1682
            * Account for X axis at the top
1683
            */
1684
            if (prop['chart.xaxispos'] == 'top') {
1685
                y  = ca.height - y;
1686
                h *= -1;
1687
            }
1688
 
1689
            co.fillStyle = color;
1690
            co.fillRect(x, y, w, h);
1691
        }
1692
/*
1693
 
1694
 
1695
 
1696
 
1697
 
1698
            // If the X axis is at the bottom, and a negative max is given, warn the user
1699
            if (obj.Get('chart.xaxispos') == 'bottom' && (hbars[i][0] < 0 || (hbars[i][1] + hbars[i][1] < 0)) ) {
1700
                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');
1701
            }
1702
 
1703
            var ystart = (obj.grapharea - (((hbars[i][0] - obj.scale2.min) / (obj.scale2.max - obj.scale2.min)) * obj.grapharea));
1704
            //var height = (Math.min(hbars[i][1], obj.max - hbars[i][0]) / (obj.scale2.max - obj.scale2.min)) * obj.grapharea;
1705
            var height = obj.getYCoord(hbars[i][0]) - obj.getYCoord(hbars[i][1]);
1706
 
1707
            // Account for the X axis being in the center
1708
            if (obj.Get('chart.xaxispos') == 'center') {
1709
                ystart /= 2;
1710
                //height /= 2;
1711
            }
1712
 
1713
            ystart += obj.Get('chart.gutter.top')
1714
 
1715
            var x = obj.Get('chart.gutter.left');
1716
            var y = ystart - height;
1717
            var w = obj.canvas.width - obj.Get('chart.gutter.left') - obj.Get('chart.gutter.right');
1718
            var h = height;
1719
 
1720
            // Accommodate Opera :-/
1721
            if (navigator.userAgent.indexOf('Opera') != -1 && obj.Get('chart.xaxispos') == 'center' && h < 0) {
1722
                h *= -1;
1723
                y = y - h;
1724
            }
1725
 
1726
            /**
1727
            * Account for X axis at the top
1728
            */
1729
            //if (obj.Get('chart.xaxispos') == 'top') {
1730
            //    y  = obj.canvas.height - y;
1731
            //    h *= -1;
1732
            //}
1733
 
1734
            //obj.context.fillStyle = hbars[i][2];
1735
            //obj.context.fillRect(x, y, w, h);
1736
        //}
1737
    }
1738
 
1739
 
1740
 
1741
 
1742
    /**
1743
    * Draws in-graph labels.
1744
    *
1745
    * @param object obj The graph object
1746
    */
1747
    RGraph.DrawInGraphLabels = function (obj)
1748
    {
1749
        var RG      = RGraph;
1750
        var ca      = obj.canvas;
1751
        var co      = obj.context;
1752
        var prop    = obj.properties;
1753
        var labels  = prop['chart.labels.ingraph'];
1754
        var labels_processed = [];
1755
 
1756
        // Defaults
1757
        var fgcolor   = 'black';
1758
        var bgcolor   = 'white';
1759
        var direction = 1;
1760
 
1761
        if (!labels) {
1762
            return;
1763
        }
1764
 
1765
        /**
1766
        * Preprocess the labels array. Numbers are expanded
1767
        */
1768
        for (var i=0,len=labels.length; i<len; i+=1) {
1769
            if (typeof(labels[i]) == 'number') {
1770
                for (var j=0; j<labels[i]; ++j) {
1771
                    labels_processed.push(null);
1772
                }
1773
            } else if (typeof(labels[i]) == 'string' || typeof(labels[i]) == 'object') {
1774
                labels_processed.push(labels[i]);
1775
 
1776
            } else {
1777
                labels_processed.push('');
1778
            }
1779
        }
1780
 
1781
        /**
1782
        * Turn off any shadow
1783
        */
1784
        RG.NoShadow(obj);
1785
 
1786
        if (labels_processed && labels_processed.length > 0) {
1787
 
1788
            for (var i=0,len=labels_processed.length; i<len; ++i) {
1789
                if (labels_processed[i]) {
1790
                    var coords = obj.coords[i];
1791
 
1792
                    if (coords && coords.length > 0) {
1793
                        var x      = (obj.type == 'bar' ? coords[0] + (coords[2] / 2) : coords[0]);
1794
                        var y      = (obj.type == 'bar' ? coords[1] + (coords[3] / 2) : coords[1]);
1795
                        var length = typeof(labels_processed[i][4]) == 'number' ? labels_processed[i][4] : 25;
1796
 
1797
                        co.beginPath();
1798
                        co.fillStyle   = 'black';
1799
                        co.strokeStyle = 'black';
1800
 
1801
 
1802
                        if (obj.type == 'bar') {
1803
 
1804
                            /**
1805
                            * X axis at the top
1806
                            */
1807
                            if (obj.Get('chart.xaxispos') == 'top') {
1808
                                length *= -1;
1809
                            }
1810
 
1811
                            if (prop['chart.variant'] == 'dot') {
1812
                                co.moveTo(Math.round(x), obj.coords[i][1] - 5);
1813
                                co.lineTo(Math.round(x), obj.coords[i][1] - 5 - length);
1814
 
1815
                                var text_x = Math.round(x);
1816
                                var text_y = obj.coords[i][1] - 5 - length;
1817
 
1818
                            } else if (prop['chart.variant'] == 'arrow') {
1819
                                co.moveTo(Math.round(x), obj.coords[i][1] - 5);
1820
                                co.lineTo(Math.round(x), obj.coords[i][1] - 5 - length);
1821
 
1822
                                var text_x = Math.round(x);
1823
                                var text_y = obj.coords[i][1] - 5 - length;
1824
 
1825
                            } else {
1826
 
1827
                                co.arc(Math.round(x), y, 2.5, 0, 6.28, 0);
1828
                                co.moveTo(Math.round(x), y);
1829
                                co.lineTo(Math.round(x), y - length);
1830
 
1831
                                var text_x = Math.round(x);
1832
                                var text_y = y - length;
1833
                            }
1834
 
1835
                            co.stroke();
1836
                            co.fill();
1837
 
1838
 
1839
                        } else if (obj.type == 'line') {
1840
 
1841
                            if (
1842
                                typeof(labels_processed[i]) == 'object' &&
1843
                                typeof(labels_processed[i][3]) == 'number' &&
1844
                                labels_processed[i][3] == -1
1845
                               ) {
1846
 
1847
                                co.moveTo(Math.round(x), y + 5);
1848
                                co.lineTo(Math.round(x), y + 5 + length);
1849
 
1850
                                co.stroke();
1851
                                co.beginPath();
1852
 
1853
                                // This draws the arrow
1854
                                co.moveTo(Math.round(x), y + 5);
1855
                                co.lineTo(Math.round(x) - 3, y + 10);
1856
                                co.lineTo(Math.round(x) + 3, y + 10);
1857
                                co.closePath();
1858
 
1859
                                var text_x = x;
1860
                                var text_y = y + 5 + length;
1861
 
1862
                            } else {
1863
 
1864
                                var text_x = x;
1865
                                var text_y = y - 5 - length;
1866
 
1867
                                co.moveTo(Math.round(x), y - 5);
1868
                                co.lineTo(Math.round(x), y - 5 - length);
1869
 
1870
                                co.stroke();
1871
                                co.beginPath();
1872
 
1873
                                // This draws the arrow
1874
                                co.moveTo(Math.round(x), y - 5);
1875
                                co.lineTo(Math.round(x) - 3, y - 10);
1876
                                co.lineTo(Math.round(x) + 3, y - 10);
1877
                                co.closePath();
1878
                            }
1879
 
1880
                            co.fill();
1881
                        }
1882
 
1883
                        // Taken out on the 10th Nov 2010 - unnecessary
1884
                        //var width = context.measureText(labels[i]).width;
1885
 
1886
                        co.beginPath();
1887
 
1888
                            // Fore ground color
1889
                            co.fillStyle = (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][1]) == 'string') ? labels_processed[i][1] : 'black';
1890
 
1891
                            RG.Text2(obj,{'font':prop['chart.text.font'],
1892
                                          'size':prop['chart.text.size'],
1893
                                          'x':text_x,
1894
                                          'y':text_y,
1895
                                          'text': (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][0]) == 'string') ? labels_processed[i][0] : labels_processed[i],
1896
                                          'valign': 'bottom',
1897
                                          'halign':'center',
1898
                                          'bounding':true,
1899
                                          'bounding.fill': (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][2]) == 'string') ? labels_processed[i][2] : 'white',
1900
                                          'tag':'labels ingraph'
1901
                                         });
1902
                        co.fill();
1903
                    }
1904
                }
1905
            }
1906
        }
1907
    }
1908
 
1909
 
1910
 
1911
 
1912
    /**
1913
    * This function "fills in" key missing properties that various implementations lack
1914
    *
1915
    * @param object e The event object
1916
    */
1917
    RGraph.FixEventObject = function (e)
1918
    {
1919
        if (ISOLD) {
1920
            var e = event;
1921
 
1922
            e.pageX  = (event.clientX + document.body.scrollLeft);
1923
            e.pageY  = (event.clientY + document.body.scrollTop);
1924
            e.target = event.srcElement;
1925
 
1926
            if (!document.body.scrollTop && document.documentElement.scrollTop) {
1927
                e.pageX += parseInt(document.documentElement.scrollLeft);
1928
                e.pageY += parseInt(document.documentElement.scrollTop);
1929
            }
1930
        }
1931
 
1932
 
1933
        // Any browser that doesn't implement stopPropagation() (MSIE)
1934
        if (!e.stopPropagation) {
1935
            e.stopPropagation = function () {window.event.cancelBubble = true;}
1936
        }
1937
 
1938
        return e;
1939
    }
1940
 
1941
 
1942
 
1943
 
1944
    /**
1945
    * Thisz function hides the crosshairs coordinates
1946
    */
1947
    RGraph.HideCrosshairCoords = function ()
1948
    {
1949
        var RG  = RGraph;
1950
        var div = RG.Registry.Get('chart.coordinates.coords.div');
1951
 
1952
        if (   div
1953
            && div.style.opacity == 1
1954
            && div.__object__.Get('chart.crosshairs.coords.fadeout')
1955
           ) {
1956
 
1957
            var style = RG.Registry.Get('chart.coordinates.coords.div').style;
1958
 
1959
            setTimeout(function() {style.opacity = 0.9;}, 25);
1960
            setTimeout(function() {style.opacity = 0.8;}, 50);
1961
            setTimeout(function() {style.opacity = 0.7;}, 75);
1962
            setTimeout(function() {style.opacity = 0.6;}, 100);
1963
            setTimeout(function() {style.opacity = 0.5;}, 125);
1964
            setTimeout(function() {style.opacity = 0.4;}, 150);
1965
            setTimeout(function() {style.opacity = 0.3;}, 175);
1966
            setTimeout(function() {style.opacity = 0.2;}, 200);
1967
            setTimeout(function() {style.opacity = 0.1;}, 225);
1968
            setTimeout(function() {style.opacity = 0;}, 250);
1969
            setTimeout(function() {style.display = 'none';}, 275);
1970
        }
1971
    }
1972
 
1973
 
1974
 
1975
 
1976
    /**
1977
    * Draws the3D axes/background
1978
    */
1979
    RGraph.Draw3DAxes = function (obj)
1980
    {
1981
        var prop = obj.properties;
1982
        var co   = obj.context;
1983
        var ca   = obj.canvas;
1984
 
1985
        var gutterLeft    = prop['chart.gutter.left'];
1986
        var gutterRight   = prop['chart.gutter.right'];
1987
        var gutterTop     = prop['chart.gutter.top'];
1988
        var gutterBottom  = prop['chart.gutter.bottom'];
1989
 
1990
 
1991
        co.strokeStyle = '#aaa';
1992
        co.fillStyle = '#ddd';
1993
 
1994
        // Draw the vertical left side
1995
        co.beginPath();
1996
            co.moveTo(gutterLeft, gutterTop);
1997
            co.lineTo(gutterLeft + 10, gutterTop - 5);
1998
            co.lineTo(gutterLeft + 10, ca.height - gutterBottom - 5);
1999
            co.lineTo(gutterLeft, ca.height - gutterBottom);
2000
        co.closePath();
2001
 
2002
        co.stroke();
2003
        co.fill();
2004
 
2005
        // Draw the bottom floor
2006
        co.beginPath();
2007
            co.moveTo(gutterLeft, ca.height - gutterBottom);
2008
            co.lineTo(gutterLeft + 10, ca.height - gutterBottom - 5);
2009
            co.lineTo(ca.width - gutterRight + 10,  ca.height - gutterBottom - 5);
2010
            co.lineTo(ca.width - gutterRight, ca.height - gutterBottom);
2011
        co.closePath();
2012
 
2013
        co.stroke();
2014
        co.fill();
2015
    }
2016
 
2017
 
2018
 
2019
 
2020
 
2021
    /**
2022
    * This function attempts to "fill in" missing functions from the canvas
2023
    * context object. Only two at the moment - measureText() nd fillText().
2024
    *
2025
    * @param object context The canvas 2D context
2026
    */
2027
    RGraph.OldBrowserCompat = function (co)
2028
    {
2029
        if (!co) {
2030
            return;
2031
        }
2032
 
2033
        if (!co.measureText) {
2034
 
2035
            // This emulates the measureText() function
2036
            co.measureText = function (text)
2037
            {
2038
                var textObj = document.createElement('DIV');
2039
                textObj.innerHTML = text;
2040
                textObj.style.position = 'absolute';
2041
                textObj.style.top = '-100px';
2042
                textObj.style.left = 0;
2043
                document.body.appendChild(textObj);
2044
 
2045
                var width = {width: textObj.offsetWidth};
2046
 
2047
                textObj.style.display = 'none';
2048
 
2049
                return width;
2050
            }
2051
        }
2052
 
2053
        if (!co.fillText) {
2054
            // This emulates the fillText() method
2055
            co.fillText    = function (text, targetX, targetY)
2056
            {
2057
                return false;
2058
            }
2059
        }
2060
 
2061
        // If IE8, add addEventListener()
2062
        if (!co.canvas.addEventListener) {
2063
            window.addEventListener = function (ev, func, bubble)
2064
            {
2065
                return this.attachEvent('on' + ev, func);
2066
            }
2067
 
2068
            co.canvas.addEventListener = function (ev, func, bubble)
2069
            {
2070
                return this.attachEvent('on' + ev, func);
2071
            }
2072
        }
2073
    }
2074
 
2075
 
2076
 
2077
 
2078
    /**
2079
    * Draws a rectangle with curvy corners
2080
    *
2081
    * @param co object The context
2082
    * @param x number The X coordinate (top left of the square)
2083
    * @param y number The Y coordinate (top left of the square)
2084
    * @param w number The width of the rectangle
2085
    * @param h number The height of the rectangle
2086
    * @param   number The radius of the curved corners
2087
    * @param   boolean Whether the top left corner is curvy
2088
    * @param   boolean Whether the top right corner is curvy
2089
    * @param   boolean Whether the bottom right corner is curvy
2090
    * @param   boolean Whether the bottom left corner is curvy
2091
    */
2092
    RGraph.strokedCurvyRect = function (co, x, y, w, h)
2093
    {
2094
        // The corner radius
2095
        var r = arguments[5] ? arguments[5] : 3;
2096
 
2097
        // The corners
2098
        var corner_tl = (arguments[6] || arguments[6] == null) ? true : false;
2099
        var corner_tr = (arguments[7] || arguments[7] == null) ? true : false;
2100
        var corner_br = (arguments[8] || arguments[8] == null) ? true : false;
2101
        var corner_bl = (arguments[9] || arguments[9] == null) ? true : false;
2102
 
2103
        co.beginPath();
2104
 
2105
            // Top left side
2106
            co.moveTo(x + (corner_tl ? r : 0), y);
2107
            co.lineTo(x + w - (corner_tr ? r : 0), y);
2108
 
2109
            // Top right corner
2110
            if (corner_tr) {
2111
                co.arc(x + w - r, y + r, r, PI + HALFPI, TWOPI, false);
2112
            }
2113
 
2114
            // Top right side
2115
            co.lineTo(x + w, y + h - (corner_br ? r : 0) );
2116
 
2117
            // Bottom right corner
2118
            if (corner_br) {
2119
                co.arc(x + w - r, y - r + h, r, TWOPI, HALFPI, false);
2120
            }
2121
 
2122
            // Bottom right side
2123
            co.lineTo(x + (corner_bl ? r : 0), y + h);
2124
 
2125
            // Bottom left corner
2126
            if (corner_bl) {
2127
                co.arc(x + r, y - r + h, r, HALFPI, PI, false);
2128
            }
2129
 
2130
            // Bottom left side
2131
            co.lineTo(x, y + (corner_tl ? r : 0) );
2132
 
2133
            // Top left corner
2134
            if (corner_tl) {
2135
                co.arc(x + r, y + r, r, PI, PI + HALFPI, false);
2136
            }
2137
 
2138
        co.stroke();
2139
    }
2140
 
2141
 
2142
 
2143
 
2144
    /**
2145
    * Draws a filled rectangle with curvy corners
2146
    *
2147
    * @param context object The context
2148
    * @param x       number The X coordinate (top left of the square)
2149
    * @param y       number The Y coordinate (top left of the square)
2150
    * @param w       number The width of the rectangle
2151
    * @param h       number The height of the rectangle
2152
    * @param         number The radius of the curved corners
2153
    * @param         boolean Whether the top left corner is curvy
2154
    * @param         boolean Whether the top right corner is curvy
2155
    * @param         boolean Whether the bottom right corner is curvy
2156
    * @param         boolean Whether the bottom left corner is curvy
2157
    */
2158
    RGraph.filledCurvyRect = function (co, x, y, w, h)
2159
    {
2160
        // The corner radius
2161
        var r = arguments[5] ? arguments[5] : 3;
2162
 
2163
        // The corners
2164
        var corner_tl = (arguments[6] || arguments[6] == null) ? true : false;
2165
        var corner_tr = (arguments[7] || arguments[7] == null) ? true : false;
2166
        var corner_br = (arguments[8] || arguments[8] == null) ? true : false;
2167
        var corner_bl = (arguments[9] || arguments[9] == null) ? true : false;
2168
 
2169
        co.beginPath();
2170
 
2171
            // First draw the corners
2172
 
2173
            // Top left corner
2174
            if (corner_tl) {
2175
                co.moveTo(x + r, y + r);
2176
                co.arc(x + r, y + r, r, PI, PI + HALFPI, false);
2177
            } else {
2178
                co.fillRect(x, y, r, r);
2179
            }
2180
 
2181
            // Top right corner
2182
            if (corner_tr) {
2183
                co.moveTo(x + w - r, y + r);
2184
                co.arc(x + w - r, y + r, r, PI + HALFPI, 0, false);
2185
            } else {
2186
                co.moveTo(x + w - r, y);
2187
                co.fillRect(x + w - r, y, r, r);
2188
            }
2189
 
2190
 
2191
            // Bottom right corner
2192
            if (corner_br) {
2193
                co.moveTo(x + w - r, y + h - r);
2194
                co.arc(x + w - r, y - r + h, r, 0, HALFPI, false);
2195
            } else {
2196
                co.moveTo(x + w - r, y + h - r);
2197
                co.fillRect(x + w - r, y + h - r, r, r);
2198
            }
2199
 
2200
            // Bottom left corner
2201
            if (corner_bl) {
2202
                co.moveTo(x + r, y + h - r);
2203
                co.arc(x + r, y - r + h, r, HALFPI, PI, false);
2204
            } else {
2205
                co.moveTo(x, y + h - r);
2206
                co.fillRect(x, y + h - r, r, r);
2207
            }
2208
 
2209
            // Now fill it in
2210
            co.fillRect(x + r, y, w - r - r, h);
2211
            co.fillRect(x, y + r, r + 1, h - r - r);
2212
            co.fillRect(x + w - r - 1, y + r, r + 1, h - r - r);
2213
 
2214
        co.fill();
2215
    }
2216
 
2217
 
2218
 
2219
 
2220
    /**
2221
    * Hides the zoomed canvas
2222
    */
2223
    RGraph.HideZoomedCanvas = function ()
2224
    {
2225
        var interval = 15;
2226
        var frames   = 10;
2227
 
2228
        if (typeof(__zoomedimage__) == 'object') {
2229
            var obj  = __zoomedimage__.obj;
2230
            var prop = obj.properties;
2231
        } else {
2232
            return;
2233
        }
2234
 
2235
        if (prop['chart.zoom.fade.out']) {
2236
            for (var i=frames,j=1; i>=0; --i, ++j) {
2237
                if (typeof(__zoomedimage__) == 'object') {
2238
                    setTimeout("__zoomedimage__.style.opacity = " + String(i / 10), j * interval);
2239
                }
2240
            }
2241
 
2242
            if (typeof(__zoomedbackground__) == 'object') {
2243
                setTimeout("__zoomedbackground__.style.opacity = " + String(i / frames), j * interval);
2244
            }
2245
        }
2246
 
2247
        if (typeof(__zoomedimage__) == 'object') {
2248
            setTimeout("__zoomedimage__.style.display = 'none'", prop['chart.zoom.fade.out'] ? (frames * interval) + 10 : 0);
2249
        }
2250
 
2251
        if (typeof(__zoomedbackground__) == 'object') {
2252
            setTimeout("__zoomedbackground__.style.display = 'none'", prop['chart.zoom.fade.out'] ? (frames * interval) + 10 : 0);
2253
        }
2254
    }
2255
 
2256
 
2257
 
2258
 
2259
    /**
2260
    * Adds an event handler
2261
    *
2262
    * @param object obj   The graph object
2263
    * @param string event The name of the event, eg ontooltip
2264
    * @param object func  The callback function
2265
    */
2266
    RGraph.AddCustomEventListener = function (obj, name, func)
2267
    {
2268
        var RG = RGraph;
2269
 
2270
        if (typeof(RG.events[obj.uid]) == 'undefined') {
2271
            RG.events[obj.uid] = [];
2272
        }
2273
 
2274
        RG.events[obj.uid].push([obj, name, func]);
2275
 
2276
        return RG.events[obj.uid].length - 1;
2277
    }
2278
 
2279
 
2280
 
2281
 
2282
    /**
2283
    * Used to fire one of the RGraph custom events
2284
    *
2285
    * @param object obj   The graph object that fires the event
2286
    * @param string event The name of the event to fire
2287
    */
2288
    RGraph.FireCustomEvent = function (obj, name)
2289
    {
2290
        var RG = RGraph;
2291
 
2292
        if (obj && obj.isRGraph) {
2293
 
2294
            // New style of adding custom events
2295
            if (obj[name]) {
2296
                (obj[name])(obj);
2297
            }
2298
 
2299
            var uid = obj.uid;
2300
 
2301
            if (   typeof(uid) == 'string'
2302
                && typeof(RG.events) == 'object'
2303
                && typeof(RG.events[uid]) == 'object'
2304
                && RG.events[uid].length > 0) {
2305
 
2306
                for(var j=0; j<RG.events[uid].length; ++j) {
2307
                    if (RG.events[uid][j] && RG.events[uid][j][1] == name) {
2308
                        RG.events[uid][j][2](obj);
2309
                    }
2310
                }
2311
            }
2312
        }
2313
    }
2314
 
2315
 
2316
 
2317
 
2318
    /**
2319
    * If you prefer, you can use the SetConfig() method to set the configuration information
2320
    * for your chart. You may find that setting the configuration this way eases reuse.
2321
    *
2322
    * @param object obj    The graph object
2323
    * @param object config The graph configuration information
2324
    */
2325
    RGraph.SetConfig = function (obj, config)
2326
    {
2327
        for (i in config) {
2328
            if (typeof(i) == 'string') {
2329
                obj.Set(i, config[i]);
2330
            }
2331
        }
2332
 
2333
        return obj;
2334
    }
2335
 
2336
 
2337
 
2338
 
2339
    /**
2340
    * Clears all the custom event listeners that have been registered
2341
    *
2342
    * @param    string Limits the clearing to this object ID
2343
    */
2344
    RGraph.RemoveAllCustomEventListeners = function ()
2345
    {
2346
        var RG = RGraph;
2347
        var id = arguments[0];
2348
 
2349
        if (id && RG.events[id]) {
2350
            RG.events[id] = [];
2351
        } else {
2352
            RG.events = [];
2353
        }
2354
    }
2355
 
2356
 
2357
 
2358
 
2359
    /**
2360
    * Clears a particular custom event listener
2361
    *
2362
    * @param object obj The graph object
2363
    * @param number i   This is the index that is return by .AddCustomEventListener()
2364
    */
2365
    RGraph.RemoveCustomEventListener = function (obj, i)
2366
    {
2367
        var RG = RGraph;
2368
 
2369
        if (   typeof(RG.events) == 'object'
2370
            && typeof(RG.events[obj.id]) == 'object'
2371
            && typeof(RG.events[obj.id][i]) == 'object') {
2372
 
2373
            RG.events[obj.id][i] = null;
2374
        }
2375
    }
2376
 
2377
 
2378
 
2379
 
2380
    /**
2381
    * This draws the background
2382
    *
2383
    * @param object obj The graph object
2384
    */
2385
    RGraph.DrawBackgroundImage = function (obj)
2386
    {
2387
        var prop = obj.properties;
2388
        var ca   = obj.canvas;
2389
        var co   = obj.context;
2390
        var RG   = RGraph;
2391
 
2392
        if (typeof(prop['chart.background.image']) == 'string') {
2393
            if (typeof(ca.__rgraph_background_image__) == 'undefined') {
2394
                var img = new Image();
2395
                img.__object__  = obj;
2396
                img.__canvas__  = ca;
2397
                img.__context__ = co;
2398
                img.src         = obj.Get('chart.background.image');
2399
 
2400
                ca.__rgraph_background_image__ = img;
2401
            } else {
2402
                img = ca.__rgraph_background_image__;
2403
            }
2404
 
2405
            // When the image has loaded - redraw the canvas
2406
            img.onload = function ()
2407
            {
2408
                obj.__rgraph_background_image_loaded__ = true;
2409
                RG.Clear(ca);
2410
                RG.RedrawCanvas(ca);
2411
            }
2412
 
2413
            var gutterLeft   = obj.gutterLeft;
2414
            var gutterRight  = obj.gutterRight;
2415
            var gutterTop    = obj.gutterTop;
2416
            var gutterBottom = obj.gutterBottom;
2417
            var stretch      = prop['chart.background.image.stretch'];
2418
            var align        = prop['chart.background.image.align'];
2419
 
2420
            // Handle chart.background.image.align
2421
            if (typeof(align) == 'string') {
2422
                if (align.indexOf('right') != -1) {
2423
                    var x = ca.width - img.width - gutterRight;
2424
                } else {
2425
                    var x = gutterLeft;
2426
                }
2427
 
2428
                if (align.indexOf('bottom') != -1) {
2429
                    var y = ca.height - img.height - gutterBottom;
2430
                } else {
2431
                    var y = gutterTop;
2432
                }
2433
            } else {
2434
                var x = gutterLeft || 25;
2435
                var y = gutterTop || 25;
2436
            }
2437
 
2438
            // X/Y coords take precedence over the align
2439
            var x = typeof(prop['chart.background.image.x']) == 'number' ? prop['chart.background.image.x'] : x;
2440
            var y = typeof(prop['chart.background.image.y']) == 'number' ? prop['chart.background.image.y'] : y;
2441
            var w = stretch ? ca.width - gutterLeft - gutterRight : img.width;
2442
            var h = stretch ? ca.height - gutterTop - gutterBottom : img.height;
2443
 
2444
            /**
2445
            * You can now specify the width and height of the image
2446
            */
2447
            if (typeof(prop['chart.background.image.w']) == 'number') w  = prop['chart.background.image.w'];
2448
            if (typeof(prop['chart.background.image.h']) == 'number') h = prop['chart.background.image.h'];
2449
 
2450
            co.drawImage(img,x,y,w, h);
2451
        }
2452
    }
2453
 
2454
 
2455
 
2456
 
2457
    /**
2458
    * This function determines wshether an object has tooltips or not
2459
    *
2460
    * @param object obj The chart object
2461
    */
2462
    RGraph.hasTooltips = function (obj)
2463
    {
2464
        var prop = obj.properties;
2465
 
2466
        if (typeof(prop['chart.tooltips']) == 'object' && prop['chart.tooltips']) {
2467
            for (var i=0,len=prop['chart.tooltips'].length; i<len; ++i) {
2468
                if (!RGraph.is_null(obj.Get('chart.tooltips')[i])) {
2469
                    return true;
2470
                }
2471
            }
2472
        } else if (typeof(prop['chart.tooltips']) == 'function') {
2473
            return true;
2474
        }
2475
 
2476
        return false;
2477
    }
2478
 
2479
 
2480
 
2481
 
2482
    /**
2483
    * This function creates a (G)UID which can be used to identify objects.
2484
    *
2485
    * @return string (g)uid The (G)UID
2486
    */
2487
    RGraph.CreateUID = function ()
2488
    {
2489
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
2490
        {
2491
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
2492
            return v.toString(16);
2493
        });
2494
    }
2495
 
2496
 
2497
 
2498
    /**
2499
    * This is the new object registry, used to facilitate multiple objects per canvas.
2500
    *
2501
    * @param object obj The object to register
2502
    */
2503
    RGraph.ObjectRegistry.Add = function (obj)
2504
    {
2505
        var uid = obj.uid;
2506
        var id  = obj.canvas.id;
2507
        var RG = RGraph;
2508
 
2509
        /**
2510
        * Index the objects by UID
2511
        */
2512
        RG.ObjectRegistry.objects.byUID.push([uid, obj]);
2513
 
2514
        /**
2515
        * Index the objects by the canvas that they're drawn on
2516
        */
2517
        RG.ObjectRegistry.objects.byCanvasID.push([id, obj]);
2518
    }
2519
 
2520
 
2521
 
2522
 
2523
    /**
2524
    * Remove an object from the object registry
2525
    *
2526
    * @param object obj The object to remove.
2527
    */
2528
    RGraph.ObjectRegistry.Remove = function (obj)
2529
    {
2530
        var id  = obj.id;
2531
        var uid = obj.uid;
2532
        var RG  = RGraph;
2533
 
2534
        for (var i=0; i<RG.ObjectRegistry.objects.byUID.length; ++i) {
2535
            if (RG.ObjectRegistry.objects.byUID[i] && RG.ObjectRegistry.objects.byUID[i][1].uid == uid) {
2536
                RG.ObjectRegistry.objects.byUID[i] = null;
2537
            }
2538
        }
2539
 
2540
 
2541
        for (var i=0; i<RG.ObjectRegistry.objects.byCanvasID.length; ++i) {
2542
            if (   RG.ObjectRegistry.objects.byCanvasID[i]
2543
                && RG.ObjectRegistry.objects.byCanvasID[i][1]
2544
                && RG.ObjectRegistry.objects.byCanvasID[i][1].uid == uid) {
2545
 
2546
                RG.ObjectRegistry.objects.byCanvasID[i] = null;
2547
            }
2548
        }
2549
 
2550
    }
2551
 
2552
 
2553
 
2554
 
2555
    /**
2556
    * Removes all objects from the ObjectRegistry. If either the ID of a canvas is supplied,
2557
    * or the canvas itself, then only objects pertaining to that canvas are cleared.
2558
    *
2559
    * @param mixed   Either a canvas object (as returned by document.getElementById()
2560
    *                or the ID of a canvas (ie a string)
2561
    */
2562
    RGraph.ObjectRegistry.Clear = function ()
2563
    {
2564
        var RG = RGraph;
2565
 
2566
        // If an ID is supplied restrict the learing to that
2567
        if (arguments[0]) {
2568
            var id      = (typeof(arguments[0]) == 'object' ? arguments[0].id : arguments[0]);
2569
            var objects = RG.ObjectRegistry.getObjectsByCanvasID(id);
2570
 
2571
            for (var i=0; i<objects.length; ++i) {
2572
                RG.ObjectRegistry.Remove(objects[i]);
2573
            }
2574
 
2575
        } else {
2576
 
2577
            RG.ObjectRegistry.objects            = {};
2578
            RG.ObjectRegistry.objects.byUID      = [];
2579
            RG.ObjectRegistry.objects.byCanvasID = [];
2580
        }
2581
    }
2582
 
2583
 
2584
 
2585
 
2586
    /**
2587
    * Lists all objects in the ObjectRegistry
2588
    *
2589
    * @param boolean ret Whether to return the list or alert() it
2590
    */
2591
    RGraph.ObjectRegistry.List = function ()
2592
    {
2593
        var list = [];
2594
        var RG   = RGraph;
2595
 
2596
        for (var i=0,len=RG.ObjectRegistry.objects.byUID.length; i<len; ++i) {
2597
            if (RG.ObjectRegistry.objects.byUID[i]) {
2598
                list.push(RG.ObjectRegistry.objects.byUID[i][1].type);
2599
            }
2600
        }
2601
 
2602
        if (arguments[0]) {
2603
            return list;
2604
        } else {
2605
            p(list);
2606
        }
2607
    }
2608
 
2609
 
2610
 
2611
 
2612
    /**
2613
    * Clears the ObjectRegistry of objects that are of a certain given type
2614
    *
2615
    * @param type string The type to clear
2616
    */
2617
    RGraph.ObjectRegistry.ClearByType = function (type)
2618
    {
2619
        var RG      = RGraph;
2620
        var objects = RG.ObjectRegistry.objects.byUID;
2621
 
2622
        for (var i=0; i<objects.length; ++i) {
2623
            if (objects[i]) {
2624
                var uid = objects[i][0];
2625
                var obj = objects[i][1];
2626
 
2627
                if (obj && obj.type == type) {
2628
                    RG.ObjectRegistry.Remove(obj);
2629
                }
2630
            }
2631
        }
2632
    }
2633
 
2634
 
2635
 
2636
 
2637
    /**
2638
    * This function provides an easy way to go through all of the objects that are held in the
2639
    * Registry
2640
    *
2641
    * @param func function This function is run for every object. Its passed the object as an argument
2642
    * @param string type Optionally, you can pass a type of object to look for
2643
    */
2644
    RGraph.ObjectRegistry.Iterate = function (func)
2645
    {
2646
        var objects = RGraph.ObjectRegistry.objects.byUID;
2647
 
2648
        for (var i=0; i<objects.length; ++i) {
2649
 
2650
            if (typeof arguments[1] == 'string') {
2651
 
2652
                var types = arguments[1].split(/,/);
2653
 
2654
                for (var j=0; j<types.length; ++j) {
2655
                    if (types[j] == objects[i][1].type) {
2656
                        func(objects[i][1]);
2657
                    }
2658
                }
2659
            } else {
2660
                func(objects[i][1]);
2661
            }
2662
        }
2663
    }
2664
 
2665
 
2666
 
2667
 
2668
    /**
2669
    * Retrieves all objects for a given canvas id
2670
    *
2671
    * @patarm id string The canvas ID to get objects for.
2672
    */
2673
    RGraph.ObjectRegistry.getObjectsByCanvasID = function (id)
2674
    {
2675
        var store = RGraph.ObjectRegistry.objects.byCanvasID;
2676
        var ret = [];
2677
 
2678
        // Loop through all of the objects and return the appropriate ones
2679
        for (var i=0; i<store.length; ++i) {
2680
            if (store[i] && store[i][0] == id ) {
2681
                ret.push(store[i][1]);
2682
            }
2683
        }
2684
 
2685
        return ret;
2686
    }
2687
 
2688
 
2689
 
2690
 
2691
    /**
2692
    * Retrieves the relevant object based on the X/Y position.
2693
    *
2694
    * @param  object e The event object
2695
    * @return object   The applicable (if any) object
2696
    */
2697
    RGraph.ObjectRegistry.getFirstObjectByXY =
2698
    RGraph.ObjectRegistry.getObjectByXY = function (e)
2699
    {
2700
        var canvas  = e.target;
2701
        var ret     = null;
2702
        var objects = RGraph.ObjectRegistry.getObjectsByCanvasID(canvas.id);
2703
 
2704
        for (var i=(objects.length - 1); i>=0; --i) {
2705
 
2706
            var obj = objects[i].getObjectByXY(e);
2707
 
2708
            if (obj) {
2709
                return obj;
2710
            }
2711
        }
2712
    }
2713
 
2714
 
2715
 
2716
 
2717
    /**
2718
    * Retrieves the relevant objects based on the X/Y position.
2719
    * NOTE This function returns an array of objects
2720
    *
2721
    * @param  object e The event object
2722
    * @return          An array of pertinent objects. Note the there may be only one object
2723
    */
2724
    RGraph.ObjectRegistry.getObjectsByXY = function (e)
2725
    {
2726
        var canvas  = e.target;
2727
        var ret     = [];
2728
        var objects = RGraph.ObjectRegistry.getObjectsByCanvasID(canvas.id);
2729
 
2730
        // Retrieve objects "front to back"
2731
        for (var i=(objects.length - 1); i>=0; --i) {
2732
 
2733
            var obj = objects[i].getObjectByXY(e);
2734
 
2735
            if (obj) {
2736
                ret.push(obj);
2737
            }
2738
        }
2739
 
2740
        return ret;
2741
    }
2742
 
2743
 
2744
 
2745
 
2746
    /**
2747
    * Retrieves the object with the corresponding UID
2748
    *
2749
    * @param string uid The UID to get the relevant object for
2750
    */
2751
    RGraph.ObjectRegistry.getObjectByUID = function (uid)
2752
    {
2753
        var objects = RGraph.ObjectRegistry.objects.byUID;
2754
 
2755
        for (var i=0; i<objects.length; ++i) {
2756
            if (objects[i] && objects[i][1].uid == uid) {
2757
                return objects[i][1];
2758
            }
2759
        }
2760
    }
2761
 
2762
 
2763
 
2764
 
2765
    /**
2766
    * Brings a chart to the front of the ObjectRegistry by
2767
    * removing it and then readding it at the end and then
2768
    * redrawing the canvas
2769
    *
2770
    * @param object  obj    The object to bring to the front
2771
    * @param boolean redraw Whether to redraw the canvas after the
2772
    *                       object has been moved
2773
    */
2774
    RGraph.ObjectRegistry.bringToFront = function (obj)
2775
    {
2776
        var redraw = typeof arguments[1] == 'undefined' ? true : arguments[1];
2777
 
2778
        RGraph.ObjectRegistry.Remove(obj);
2779
        RGraph.ObjectRegistry.Add(obj);
2780
 
2781
        if (redraw) {
2782
            RGraph.RedrawCanvas(obj.canvas);
2783
        }
2784
    }
2785
 
2786
 
2787
 
2788
 
2789
    /**
2790
    * Retrieves the objects that are the given type
2791
    *
2792
    * @param  mixed canvas  The canvas to check. It can either be the canvas object itself or just the ID
2793
    * @param  string type   The type to look for
2794
    * @return array         An array of one or more objects
2795
    */
2796
    RGraph.ObjectRegistry.getObjectsByType = function (type)
2797
    {
2798
        var objects = RGraph.ObjectRegistry.objects.byUID;
2799
        var ret     = [];
2800
 
2801
        for (var i=0; i<objects.length; ++i) {
2802
 
2803
            if (objects[i] && objects[i][1] && objects[i][1].type && objects[i][1].type && objects[i][1].type == type) {
2804
                ret.push(objects[i][1]);
2805
            }
2806
        }
2807
 
2808
        return ret;
2809
    }
2810
 
2811
 
2812
 
2813
 
2814
    /**
2815
    * Retrieves the FIRST object that matches the given type
2816
    *
2817
    * @param  string type   The type of object to look for
2818
    * @return object        The FIRST object that matches the given type
2819
    */
2820
    RGraph.ObjectRegistry.getFirstObjectByType = function (type)
2821
    {
2822
        var objects = RGraph.ObjectRegistry.objects.byUID;
2823
 
2824
        for (var i=0; i<objects.length; ++i) {
2825
            if (objects[i] && objects[i][1] && objects[i][1].type == type) {
2826
                return objects[i][1];
2827
            }
2828
        }
2829
 
2830
        return null;
2831
    }
2832
 
2833
 
2834
 
2835
 
2836
    /**
2837
    * This takes centerx, centery, x and y coordinates and returns the
2838
    * appropriate angle relative to the canvas angle system. Remember
2839
    * that the canvas angle system starts at the EAST axis
2840
    *
2841
    * @param  number cx  The centerx coordinate
2842
    * @param  number cy  The centery coordinate
2843
    * @param  number x   The X coordinate (eg the mouseX if coming from a click)
2844
    * @param  number y   The Y coordinate (eg the mouseY if coming from a click)
2845
    * @return number     The relevant angle (measured in in RADIANS)
2846
    */
2847
    RGraph.getAngleByXY = function (cx, cy, x, y)
2848
    {
2849
        var angle = Math.atan((y - cy) / (x - cx));
2850
            angle = Math.abs(angle)
2851
 
2852
        if (x >= cx && y >= cy) {
2853
            angle += TWOPI;
2854
 
2855
        } else if (x >= cx && y < cy) {
2856
            angle = (HALFPI - angle) + (PI + HALFPI);
2857
 
2858
        } else if (x < cx && y < cy) {
2859
            angle += PI;
2860
 
2861
        } else {
2862
            angle = PI - angle;
2863
        }
2864
 
2865
        /**
2866
        * Upper and lower limit checking
2867
        */
2868
        if (angle > TWOPI) {
2869
            angle -= TWOPI;
2870
        }
2871
 
2872
        return angle;
2873
    }
2874
 
2875
 
2876
 
2877
 
2878
    /**
2879
    * This function returns the distance between two points. In effect the
2880
    * radius of an imaginary circle that is centered on x1 and y1. The name
2881
    * of this function is derived from the word "Hypoteneuse", which in
2882
    * trigonmetry is the longest side of a triangle
2883
    *
2884
    * @param number x1 The original X coordinate
2885
    * @param number y1 The original Y coordinate
2886
    * @param number x2 The target X coordinate
2887
    * @param number y2 The target Y  coordinate
2888
    */
2889
    RGraph.getHypLength = function (x1, y1, x2, y2)
2890
    {
2891
        var ret = Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
2892
 
2893
        return ret;
2894
    }
2895
 
2896
 
2897
 
2898
 
2899
    /**
2900
    * This function gets the end point (X/Y coordinates) of a given radius.
2901
    * You pass it the center X/Y and the radius and this function will return
2902
    * the endpoint X/Y coordinates.
2903
    *
2904
    * @param number cx The center X coord
2905
    * @param number cy The center Y coord
2906
    * @param number r  The lrngth of the radius
2907
    */
2908
    RGraph.getRadiusEndPoint = function (cx, cy, angle, radius)
2909
    {
2910
        var x = cx + (Math.cos(angle) * radius);
2911
        var y = cy + (Math.sin(angle) * radius);
2912
 
2913
        return [x, y];
2914
    }
2915
 
2916
 
2917
 
2918
 
2919
    /**
2920
    * This installs all of the event listeners
2921
    *
2922
    * @param object obj The chart object
2923
    */
2924
    RGraph.InstallEventListeners = function (obj)
2925
    {
2926
        var RG   = RGraph;
2927
        var prop = obj.properties;
2928
 
2929
        /**
2930
        * Don't attempt to install event listeners for older versions of MSIE
2931
        */
2932
        if (ISOLD) {
2933
            return;
2934
        }
2935
 
2936
        /**
2937
        * If this function exists, then the dynamic file has been included.
2938
        */
2939
        if (RG.InstallCanvasClickListener) {
2940
 
2941
            RG.InstallWindowMousedownListener(obj);
2942
            RG.InstallWindowMouseupListener(obj);
2943
            RG.InstallCanvasMousemoveListener(obj);
2944
            RG.InstallCanvasMouseupListener(obj);
2945
            RG.InstallCanvasMousedownListener(obj);
2946
            RG.InstallCanvasClickListener(obj);
2947
 
2948
        } else if (   RG.hasTooltips(obj)
2949
                   || prop['chart.adjustable']
2950
                   || prop['chart.annotatable']
2951
                   || prop['chart.contextmenu']
2952
                   || prop['chart.resizable']
2953
                   || prop['chart.key.interactive']
2954
                   || prop['chart.events.click']
2955
                   || prop['chart.events.mousemove']
2956
                   || typeof obj.onclick == 'function'
2957
                   || typeof obj.onmousemove == 'function'
2958
                  ) {
2959
 
2960
            alert('[RGRAPH] You appear to have used dynamic features but not included the file: RGraph.common.dynamic.js');
2961
        }
2962
    }
2963
 
2964
 
2965
 
2966
 
2967
    /**
2968
    * Loosly mimicks the PHP function print_r();
2969
    */
2970
    RGraph.pr = function (obj)
2971
    {
2972
        var indent = (arguments[2] ? arguments[2] : '    ');
2973
        var str    = '';
2974
 
2975
        var counter = typeof arguments[3] == 'number' ? arguments[3] : 0;
2976
 
2977
        if (counter >= 5) {
2978
            return '';
2979
        }
2980
 
2981
        switch (typeof obj) {
2982
 
2983
            case 'string':    str += obj + ' (' + (typeof obj) + ', ' + obj.length + ')'; break;
2984
            case 'number':    str += obj + ' (' + (typeof obj) + ')'; break;
2985
            case 'boolean':   str += obj + ' (' + (typeof obj) + ')'; break;
2986
            case 'function':  str += 'function () {}'; break;
2987
            case 'undefined': str += 'undefined'; break;
2988
            case 'null':      str += 'null'; break;
2989
 
2990
            case 'object':
2991
                // In case of null
2992
                if (RGraph.is_null(obj)) {
2993
                    str += indent + 'null\n';
2994
                } else {
2995
                    str += indent + 'Object {' + '\n'
2996
                    for (j in obj) {
2997
                        str += indent + '    ' + j + ' => ' + RGraph.pr(obj[j], true, indent + '    ', counter + 1) + '\n';
2998
                    }
2999
                    str += indent + '}';
3000
                }
3001
                break;
3002
 
3003
 
3004
            default:
3005
                str += 'Unknown type: ' + typeof obj + '';
3006
                break;
3007
        }
3008
 
3009
 
3010
        /**
3011
        * Finished, now either return if we're in a recursed call, or alert()
3012
        * if we're not.
3013
        */
3014
        if (!arguments[1]) {
3015
            alert(str);
3016
        }
3017
 
3018
        return str;
3019
    }
3020
 
3021
 
3022
 
3023
 
3024
    /**
3025
    * Produces a dashed line
3026
    *
3027
    * @param object co The 2D context
3028
    * @param number x1 The start X coordinate
3029
    * @param number y1 The start Y coordinate
3030
    * @param number x2 The end X coordinate
3031
    * @param number y2 The end Y coordinate
3032
    */
3033
    RGraph.DashedLine = function(co, x1, y1, x2, y2)
3034
    {
3035
        /**
3036
        * This is the size of the dashes
3037
        */
3038
        var size = 5;
3039
 
3040
        /**
3041
        * The optional fifth argument can be the size of the dashes
3042
        */
3043
        if (typeof(arguments[5]) == 'number') {
3044
            size = arguments[5];
3045
        }
3046
 
3047
        var dx  = x2 - x1;
3048
        var dy  = y2 - y1;
3049
        var num = Math.floor(Math.sqrt((dx * dx) + (dy * dy)) / size);
3050
 
3051
        var xLen = dx / num;
3052
        var yLen = dy / num;
3053
 
3054
        var count = 0;
3055
 
3056
        do {
3057
            (count % 2 == 0 && count > 0) ? co.lineTo(x1, y1) : co.moveTo(x1, y1);
3058
 
3059
            x1 += xLen;
3060
            y1 += yLen;
3061
        } while(count++ <= num);
3062
    }
3063
 
3064
 
3065
 
3066
 
3067
    /**
3068
    * Makes an AJAX call. It calls the given callback (a function) when ready
3069
    *
3070
    * @param string   url      The URL to retrieve
3071
    * @param function callback A function that is called when the response is ready, there's an example below
3072
    *                          called "myCallback".
3073
    */
3074
    RGraph.AJAX = function (url, callback)
3075
    {
3076
        // Mozilla, Safari, ...
3077
        if (window.XMLHttpRequest) {
3078
            var httpRequest = new XMLHttpRequest();
3079
 
3080
        // MSIE
3081
        } else if (window.ActiveXObject) {
3082
            var httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
3083
        }
3084
 
3085
        httpRequest.onreadystatechange = function ()
3086
        {
3087
            if (this.readyState == 4 && this.status == 200) {
3088
                this.__user_callback__ = callback;
3089
                this.__user_callback__(this.responseText);
3090
            }
3091
        }
3092
 
3093
        httpRequest.open('GET', url, true);
3094
        httpRequest.send();
3095
    }
3096
 
3097
 
3098
 
3099
 
3100
    /**
3101
    * Makes an AJAX POST request. It calls the given callback (a function) when ready
3102
    *
3103
    * @param string   url      The URL to retrieve
3104
    * @param object   data     The POST data
3105
    * @param function callback A function that is called when the response is ready, there's an example below
3106
    *                          called "myCallback".
3107
    */
3108
    RGraph.AJAX.POST = function (url, data, callback)
3109
    {
3110
        // Used when building the POST string
3111
        var crumbs = [];
3112
 
3113
        // Mozilla, Safari, ...
3114
        if (window.XMLHttpRequest) {
3115
            var httpRequest = new XMLHttpRequest();
3116
 
3117
        // MSIE
3118
        } else if (window.ActiveXObject) {
3119
            var httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
3120
        }
3121
 
3122
        httpRequest.onreadystatechange = function ()
3123
        {
3124
            if (this.readyState == 4 && this.status == 200) {
3125
                this.__user_callback__ = callback;
3126
                this.__user_callback__(this.responseText);
3127
            }
3128
        }
3129
 
3130
        httpRequest.open('POST', url, true);
3131
        httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
3132
 
3133
        for (i in data) {
3134
            if (typeof i == 'string') {
3135
                crumbs.push(i + '=' + encodeURIComponent(data[i]));
3136
            }
3137
        }
3138
 
3139
        httpRequest.send(crumbs.join('&'));
3140
    }
3141
 
3142
 
3143
 
3144
 
3145
    /**
3146
    * Uses the above function but calls the call back passing a number as its argument
3147
    *
3148
    * @param url string The URL to fetch
3149
    * @param callback function Your callback function (which is passed the number as an argument)
3150
    */
3151
    RGraph.AJAX.getNumber = function (url, callback)
3152
    {
3153
        RGraph.AJAX(url, function ()
3154
        {
3155
            var num = parseFloat(this.responseText);
3156
 
3157
            callback(num);
3158
        });
3159
    }
3160
 
3161
 
3162
 
3163
 
3164
    /**
3165
    * Uses the above function but calls the call back passing a string as its argument
3166
    *
3167
    * @param url string The URL to fetch
3168
    * @param callback function Your callback function (which is passed the string as an argument)
3169
    */
3170
    RGraph.AJAX.getString = function (url, callback)
3171
    {
3172
        RGraph.AJAX(url, function ()
3173
        {
3174
            var str = String(this.responseText);
3175
 
3176
            callback(str);
3177
        });
3178
    }
3179
 
3180
 
3181
 
3182
 
3183
    /**
3184
    * Uses the above function but calls the call back passing JSON (ie a JavaScript object ) as its argument
3185
    *
3186
    * @param url string The URL to fetch
3187
    * @param callback function Your callback function (which is passed the JSON object as an argument)
3188
    */
3189
    RGraph.AJAX.getJSON = function (url, callback)
3190
    {
3191
        RGraph.AJAX(url, function ()
3192
        {
3193
 
3194
            var json = eval('(' + this.responseText + ')');
3195
 
3196
            callback(json);
3197
        });
3198
    }
3199
 
3200
 
3201
 
3202
 
3203
    /**
3204
    * Uses the above RGraph.AJAX function but calls the call back passing an array as its argument.
3205
    * Useful if you're retrieving CSV data
3206
    *
3207
    * @param url string The URL to fetch
3208
    * @param callback function Your callback function (which is passed the CSV/array as an argument)
3209
    */
3210
    RGraph.AJAX.getCSV = function (url, callback)
3211
    {
3212
        var seperator = arguments[2] ? arguments[2] : ',';
3213
 
3214
        RGraph.AJAX(url, function ()
3215
        {
3216
            var regexp = new RegExp(seperator);
3217
            var arr = this.responseText.split(regexp);
3218
 
3219
            // Convert the strings to numbers
3220
            for (var i=0,len=arr.length;i<len;++i) {
3221
                arr[i] = parseFloat(arr[i]);
3222
            }
3223
 
3224
            callback(arr);
3225
        });
3226
    }
3227
 
3228
 
3229
 
3230
 
3231
    /**
3232
    * Rotates the canvas
3233
    *
3234
    * @param object canvas The canvas to rotate
3235
    * @param  int   x      The X coordinate about which to rotate the canvas
3236
    * @param  int   y      The Y coordinate about which to rotate the canvas
3237
    * @param  int   angle  The angle(in RADIANS) to rotate the canvas by
3238
    */
3239
    RGraph.RotateCanvas = function (ca, x, y, angle)
3240
    {
3241
        var co = ca.getContext('2d');
3242
 
3243
        co.translate(x, y);
3244
        co.rotate(angle);
3245
        co.translate(0 - x, 0 - y);
3246
    }
3247
 
3248
 
3249
 
3250
 
3251
    /**
3252
    * Measures text by creating a DIV in the document and adding the relevant text to it.
3253
    * Then checking the .offsetWidth and .offsetHeight.
3254
    *
3255
    * @param  string text   The text to measure
3256
    * @param  bool   bold   Whether the text is bold or not
3257
    * @param  string font   The font to use
3258
    * @param  size   number The size of the text (in pts)
3259
    * @return array         A two element array of the width and height of the text
3260
    */
3261
    RGraph.MeasureText = function (text, bold, font, size)
3262
    {
3263
        // Add the sizes to the cache as adding DOM elements is costly and causes slow downs
3264
        if (typeof(__rgraph_measuretext_cache__) == 'undefined') {
3265
            __rgraph_measuretext_cache__ = [];
3266
        }
3267
 
3268
        var str = text + ':' + bold + ':' + font + ':' + size;
3269
        if (typeof(__rgraph_measuretext_cache__) == 'object' && __rgraph_measuretext_cache__[str]) {
3270
            return __rgraph_measuretext_cache__[str];
3271
        }
3272
 
3273
        if (!__rgraph_measuretext_cache__['text-div']) {
3274
            var div = document.createElement('DIV');
3275
                div.style.position = 'absolute';
3276
                div.style.top = '-100px';
3277
                div.style.left = '-100px';
3278
            document.body.appendChild(div);
3279
 
3280
            // Now store the newly created DIV
3281
            __rgraph_measuretext_cache__['text-div'] = div;
3282
 
3283
        } else if (__rgraph_measuretext_cache__['text-div']) {
3284
            var div = __rgraph_measuretext_cache__['text-div'];
3285
        }
3286
 
3287
        div.innerHTML = text.replace(/\r\n/g, '<br />');
3288
        div.style.fontFamily = font;
3289
        div.style.fontWeight = bold ? 'bold' : 'normal';
3290
        div.style.fontSize = (size || 12) + 'pt';
3291
 
3292
        var size = [div.offsetWidth, div.offsetHeight];
3293
 
3294
        //document.body.removeChild(div);
3295
        __rgraph_measuretext_cache__[str] = size;
3296
 
3297
        return size;
3298
    }
3299
 
3300
 
3301
 
3302
 
3303
    /* New text function. Accepts two arguments:
3304
    *  o obj - The chart object
3305
    *  o opt - An object/hash/map of properties. This can consist of:
3306
    *          x                The X coordinate (REQUIRED)
3307
    *          y                The Y coordinate (REQUIRED)
3308
    *          text             The text to show (REQUIRED)
3309
    *          font             The font to use
3310
    *          size             The size of the text (in pt)
3311
    *          bold             Whether the text shouldd be bold or not
3312
    *          marker           Whether to show a marker that indicates the X/Y coordinates
3313
    *          valign           The vertical alignment
3314
    *          halign           The horizontal alignment
3315
    *          bounding         Whether to draw a bounding box for the text
3316
    *          boundingStroke   The strokeStyle of the bounding box
3317
    *          boundingFill     The fillStyle of the bounding box
3318
    */
3319
    RGraph.Text2 = function (obj, opt)
3320
    {
3321
        /**
3322
        * An RGraph object can be given, or a string or the 2D rendering context
3323
        * The coords are placed on the obj.coordsText variable ONLY if it's an RGraph object. The function
3324
        * still returns the cooords though in all cases.
3325
        */
3326
        if (obj && obj.isRGraph) {
3327
            var co = obj.context;
3328
            var ca = obj.canvas;
3329
        } else if (typeof obj == 'string') {
3330
            var ca = document.getElementById(obj);
3331
            var co = ca.getContext('2d');
3332
        } else if (typeof obj.getContext == 'function') {
3333
            var ca = obj;
3334
            var co = ca.getContext('2d');
3335
        } else if (obj.toString().indexOf('CanvasRenderingContext2D') != -1) {
3336
            var co = obj;
3337
            var ca = obj.context;
3338
        }
3339
 
3340
        var x              = opt.x;
3341
        var y              = opt.y;
3342
        var originalX      = x;
3343
        var originalY      = y;
3344
        var text           = opt.text;
3345
        var text_multiline = text.split(/\r?\n/g);
3346
        var numlines       = text_multiline.length;
3347
        var font           = opt.font ? opt.font : 'Arial';
3348
        var size           = opt.size ? opt.size : 10;
3349
        var size_pixels    = size * 1.5;
3350
        var bold           = opt.bold;
3351
        var halign         = opt.halign ? opt.halign : 'left';
3352
        var valign         = opt.valign ? opt.valign : 'bottom';
3353
        var tag            = typeof opt.tag == 'string' && opt.tag.length > 0 ? opt.tag : '';
3354
        var marker         = opt.marker;
3355
        var angle          = opt.angle || 0;
3356
 
3357
        /**
3358
        * Changed the name of boundingFill/boundingStroke - this allows you to still use those names
3359
        */
3360
        if (typeof opt.boundingFill == 'string')   opt['bounding.fill']   = opt.boundingFill;
3361
        if (typeof opt.boundingStroke == 'string') opt['bounding.stroke'] = opt.boundingStroke;
3362
 
3363
        var bounding                = opt.bounding;
3364
        var bounding_stroke         = opt['bounding.stroke'] ? opt['bounding.stroke'] : 'black';
3365
        var bounding_fill           = opt['bounding.fill'] ? opt['bounding.fill'] : 'rgba(255,255,255,0.7)';
3366
        var bounding_shadow         = opt['bounding.shadow'];
3367
        var bounding_shadow_color   = opt['bounding.shadow.color'] || '#ccc';
3368
        var bounding_shadow_blur    = opt['bounding.shadow.blur'] || 3;
3369
        var bounding_shadow_offsetx = opt['bounding.shadow.offsetx'] || 3;
3370
        var bounding_shadow_offsety = opt['bounding.shadow.offsety'] || 3;
3371
        var bounding_linewidth      = opt['bounding.linewidth'] || 1;
3372
 
3373
 
3374
 
3375
        /**
3376
        * Initialize the return value to an empty object
3377
        */
3378
        var ret = {};
3379
 
3380
 
3381
 
3382
        /**
3383
        * The text arg must be a string or a number
3384
        */
3385
        if (typeof text == 'number') {
3386
            text = String(text);
3387
        }
3388
 
3389
        if (typeof text != 'string') {
3390
            alert('[RGRAPH TEXT] The text given must a string or a number');
3391
            return;
3392
        }
3393
 
3394
 
3395
 
3396
        /**
3397
        * This facilitates vertical text
3398
        */
3399
        if (angle != 0) {
3400
            co.save();
3401
            co.translate(x, y);
3402
            co.rotate((Math.PI / 180) * angle)
3403
            x = 0;
3404
            y = 0;
3405
        }
3406
 
3407
 
3408
 
3409
        /**
3410
        * Set the font
3411
        */
3412
        co.font = (opt.bold ? 'bold ' : '') + size + 'pt ' + font;
3413
 
3414
 
3415
 
3416
        /**
3417
        * Measure the width/height. This must be done AFTER the font has been set
3418
        */
3419
        var width=0;
3420
        for (var i=0; i<numlines; ++i) {
3421
            width = Math.max(width, co.measureText(text_multiline[i]).width);
3422
        }
3423
        var height = size_pixels * numlines;
3424
 
3425
 
3426
 
3427
 
3428
        /**
3429
        * Accommodate old MSIE 7/8
3430
        */
3431
        //if (document.all && ISOLD) {
3432
            //y += 2;
3433
        //}
3434
 
3435
 
3436
 
3437
        /**
3438
        * If marker is specified draw a marker at the X/Y coordinates
3439
        */
3440
        if (opt.marker) {
3441
            var marker_size = 10;
3442
            var strokestyle = co.strokeStyle;
3443
            co.beginPath();
3444
                co.strokeStyle = 'red';
3445
                co.moveTo(x, y - marker_size);
3446
                co.lineTo(x, y + marker_size);
3447
                co.moveTo(x - marker_size, y);
3448
                co.lineTo(x + marker_size, y);
3449
            co.stroke();
3450
            co.strokeStyle = strokestyle;
3451
        }
3452
 
3453
 
3454
 
3455
        /**
3456
        * Set the horizontal alignment
3457
        */
3458
        if (halign == 'center') {
3459
            co.textAlign = 'center';
3460
            var boundingX = x - 2 - (width / 2);
3461
        } else if (halign == 'right') {
3462
            co.textAlign = 'right';
3463
            var boundingX = x - 2 - width;
3464
        } else {
3465
            co.textAlign = 'left';
3466
            var boundingX = x - 2;
3467
        }
3468
 
3469
 
3470
        /**
3471
        * Set the vertical alignment
3472
        */
3473
        if (valign == 'center') {
3474
 
3475
            co.textBaseline = 'middle';
3476
            // Move the text slightly
3477
            y -= 1;
3478
 
3479
            y -= ((numlines - 1) / 2) * size_pixels;
3480
            var boundingY = y - (size_pixels / 2) - 2;
3481
 
3482
        } else if (valign == 'top') {
3483
            co.textBaseline = 'top';
3484
 
3485
            var boundingY = y - 2;
3486
 
3487
        } else {
3488
 
3489
            co.textBaseline = 'bottom';
3490
 
3491
            // Move the Y coord if multiline text
3492
            if (numlines > 1) {
3493
                y -= ((numlines - 1) * size_pixels);
3494
            }
3495
 
3496
            var boundingY = y - size_pixels - 2;
3497
        }
3498
 
3499
        var boundingW = width + 4;
3500
        var boundingH = height + 4;
3501
 
3502
 
3503
 
3504
        /**
3505
        * Draw a bounding box if required
3506
        */
3507
        if (bounding) {
3508
 
3509
            var pre_bounding_linewidth     = co.lineWidth;
3510
            var pre_bounding_strokestyle   = co.strokeStyle;
3511
            var pre_bounding_fillstyle     = co.fillStyle;
3512
            var pre_bounding_shadowcolor   = co.shadowColor;
3513
            var pre_bounding_shadowblur    = co.shadowBlur;
3514
            var pre_bounding_shadowoffsetx = co.shadowOffsetX;
3515
            var pre_bounding_shadowoffsety = co.shadowOffsetY;
3516
 
3517
            co.lineWidth   = bounding_linewidth;
3518
            co.strokeStyle = bounding_stroke;
3519
            co.fillStyle   = bounding_fill;
3520
 
3521
            if (bounding_shadow) {
3522
                co.shadowColor   = bounding_shadow_color;
3523
                co.shadowBlur    = bounding_shadow_blur;
3524
                co.shadowOffsetX = bounding_shadow_offsetx;
3525
                co.shadowOffsetY = bounding_shadow_offsety;
3526
            }
3527
 
3528
            //obj.context.strokeRect(boundingX, boundingY, width + 6, (size_pixels * numlines) + 4);
3529
            //obj.context.fillRect(boundingX, boundingY, width + 6, (size_pixels * numlines) + 4);
3530
            co.strokeRect(boundingX, boundingY, boundingW, boundingH);
3531
            co.fillRect(boundingX, boundingY, boundingW, boundingH);
3532
 
3533
            // Reset the linewidth,colors and shadow to it's original setting
3534
            co.lineWidth     = pre_bounding_linewidth;
3535
            co.strokeStyle   = pre_bounding_strokestyle;
3536
            co.fillStyle     = pre_bounding_fillstyle;
3537
            co.shadowColor   = pre_bounding_shadowcolor
3538
            co.shadowBlur    = pre_bounding_shadowblur
3539
            co.shadowOffsetX = pre_bounding_shadowoffsetx
3540
            co.shadowOffsetY = pre_bounding_shadowoffsety
3541
        }
3542
 
3543
 
3544
 
3545
        /**
3546
        * Draw the text
3547
        */
3548
        if (numlines > 1) {
3549
            for (var i=0; i<numlines; ++i) {
3550
                co.fillText(text_multiline[i], x, y + (size_pixels * i));
3551
            }
3552
        } else {
3553
            co.fillText(text, x, y);
3554
        }
3555
 
3556
 
3557
 
3558
        /**
3559
        * If the text is at 90 degrees restore() the canvas - getting rid of the rotation
3560
        * and the translate that we did
3561
        */
3562
        if (angle != 0) {
3563
            if (angle == 90) {
3564
                if (halign == 'left') {
3565
                    if (valign == 'bottom') {boundingX = originalX - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3566
                    if (valign == 'center') {boundingX = originalX - (height / 2) - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3567
                    if (valign == 'top')    {boundingX = originalX - height - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3568
 
3569
                } else if (halign == 'center') {
3570
                    if (valign == 'bottom') {boundingX = originalX - 2; boundingY = originalY - (width / 2) - 2; boundingW = height + 4; boundingH = width + 4;}
3571
                    if (valign == 'center') {boundingX = originalX - (height / 2) -  2; boundingY = originalY - (width / 2) - 2; boundingW = height + 4; boundingH = width + 4;}
3572
                    if (valign == 'top')    {boundingX = originalX - height -  2; boundingY = originalY - (width / 2) - 2; boundingW = height + 4; boundingH = width + 4;}
3573
 
3574
                } else if (halign == 'right') {
3575
                    if (valign == 'bottom') {boundingX = originalX - 2; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3576
                    if (valign == 'center') {boundingX = originalX - (height / 2) - 2; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3577
                    if (valign == 'top')    {boundingX = originalX - height - 2; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3578
                }
3579
 
3580
            } else if (angle == 180) {
3581
 
3582
                if (halign == 'left') {
3583
                    if (valign == 'bottom') {boundingX = originalX - width - 2; boundingY = originalY - 2; boundingW = width + 4; boundingH = height + 4;}
3584
                    if (valign == 'center') {boundingX = originalX - width - 2; boundingY = originalY - (height / 2) - 2; boundingW = width + 4; boundingH = height + 4;}
3585
                    if (valign == 'top')    {boundingX = originalX - width - 2; boundingY = originalY - height - 2; boundingW = width + 4; boundingH = height + 4;}
3586
 
3587
                } else if (halign == 'center') {
3588
                    if (valign == 'bottom') {boundingX = originalX - (width / 2) - 2; boundingY = originalY - 2; boundingW = width + 4; boundingH = height + 4;}
3589
                    if (valign == 'center') {boundingX = originalX - (width / 2) - 2; boundingY = originalY - (height / 2) - 2; boundingW = width + 4; boundingH = height + 4;}
3590
                    if (valign == 'top')    {boundingX = originalX - (width / 2) - 2; boundingY = originalY - height - 2; boundingW = width + 4; boundingH = height + 4;}
3591
 
3592
                } else if (halign == 'right') {
3593
                    if (valign == 'bottom') {boundingX = originalX - 2; boundingY = originalY - 2; boundingW = width + 4; boundingH = height + 4;}
3594
                    if (valign == 'center') {boundingX = originalX - 2; boundingY = originalY - (height / 2) - 2; boundingW = width + 4; boundingH = height + 4;}
3595
                    if (valign == 'top')    {boundingX = originalX - 2; boundingY = originalY - height - 2; boundingW = width + 4; boundingH = height + 4;}
3596
                }
3597
 
3598
            } else if (angle == 270) {
3599
 
3600
                if (halign == 'left') {
3601
                    if (valign == 'bottom') {boundingX = originalX - height - 2; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3602
                    if (valign == 'center') {boundingX = originalX - (height / 2) - 4; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3603
                    if (valign == 'top')    {boundingX = originalX - 2; boundingY = originalY - width - 2; boundingW = height + 4; boundingH = width + 4;}
3604
 
3605
                } else if (halign == 'center') {
3606
                    if (valign == 'bottom') {boundingX = originalX - height - 2; boundingY = originalY - (width/2) - 2; boundingW = height + 4; boundingH = width + 4;}
3607
                    if (valign == 'center') {boundingX = originalX - (height/2) - 4; boundingY = originalY - (width/2) - 2; boundingW = height + 4; boundingH = width + 4;}
3608
                    if (valign == 'top')    {boundingX = originalX - 2; boundingY = originalY - (width/2) - 2; boundingW = height + 4; boundingH = width + 4;}
3609
 
3610
                } else if (halign == 'right') {
3611
                    if (valign == 'bottom') {boundingX = originalX - height - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3612
                    if (valign == 'center') {boundingX = originalX - (height/2) - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3613
                    if (valign == 'top')    {boundingX = originalX - 2; boundingY = originalY - 2; boundingW = height + 4; boundingH = width + 4;}
3614
                }
3615
            }
3616
 
3617
            co.restore();
3618
        }
3619
 
3620
 
3621
 
3622
 
3623
        /**
3624
        * Reset the text alignment so that text rendered
3625
        */
3626
        co.textBaseline = 'alphabetic';
3627
        co.textAlign    = 'left';
3628
 
3629
 
3630
 
3631
 
3632
 
3633
        /**
3634
        * Fill the ret variable with details of the text
3635
        */
3636
        ret.x      = boundingX;
3637
        ret.y      = boundingY;
3638
        ret.width  = boundingW;
3639
        ret.height = boundingH
3640
        ret.object = obj;
3641
        ret.text   = text;
3642
        ret.tag    = tag;
3643
 
3644
 
3645
 
3646
        /**
3647
        * Save and then return the details of the text (but oly
3648
        * if it's an RGraph object that was given)
3649
        */
3650
        if (obj && obj.isRGraph && obj.coordsText) {
3651
            obj.coordsText.push(ret);
3652
        }
3653
 
3654
        return ret;
3655
    }
3656
 
3657
 
3658
 
3659
 
3660
    /**
3661
    * Takes a sequential index abd returns the group/index variation of it. Eg if you have a
3662
    * sequential index from a grouped bar chart this function can be used to convert that into
3663
    * an appropriate group/index combination
3664
    *
3665
    * @param nindex number The sequential index
3666
    * @param data   array  The original data (which is grouped)
3667
    * @return              The group/index information
3668
    */
3669
    RGraph.sequentialIndexToGrouped = function (index, data)
3670
    {
3671
        var group         = 0;
3672
        var grouped_index = 0;
3673
 
3674
        while (--index >= 0) {
3675
 
3676
            if (RGraph.is_null(data[group])) {
3677
                group++;
3678
                grouped_index = 0;
3679
                continue;
3680
            }
3681
 
3682
            // Allow for numbers as well as arrays in the dataset
3683
            if (typeof data[group] == 'number') {
3684
                group++
3685
                grouped_index = 0;
3686
                continue;
3687
            }
3688
 
3689
 
3690
            grouped_index++;
3691
 
3692
            if (grouped_index >= data[group].length) {
3693
                group++;
3694
                grouped_index = 0;
3695
            }
3696
        }
3697
 
3698
        return [group, grouped_index];
3699
    }
3700
 
3701
 
3702
 
3703
 
3704
    /**
3705
    * Similar to the jQuery each() function - this lets you iterate easily over an array. The 'this' variable is set]
3706
    * to the array in the callback function.
3707
    *
3708
    * @param array    arr The array
3709
    * @param function func The function to call
3710
    * @param object        Optionally you can specify the object that the "this" variable is set to
3711
    */
3712
    RGraph.each = function (arr, func)
3713
    {
3714
        for(var i=0, len=arr.length; i<len; i+=1) {
3715
 
3716
            if (typeof arguments[2] !== 'undefined') {
3717
                var ret = func.call(arguments[2], i, arr[i]);
3718
            } else {
3719
                var ret = func.call(arr, i, arr[i]);
3720
            }
3721
 
3722
            if (ret === false) {
3723
                return;
3724
            }
3725
        }
3726
    }
3727
 
3728
 
3729
 
3730
 
3731
    /**
3732
    * Checks whether strings or numbers are empty or not. It also
3733
    * handles null or variables set to undefined. If a variable really
3734
    * is undefined - ie it hasn't been declared at all - you need to use
3735
    * "typeof variable" and check the return value - which will be undefined.
3736
    *
3737
    * @param mixed value The variable to check
3738
    */
3739
    function empty (value)
3740
    {
3741
        if (!value || value.length <= 0) {
3742
            return true;
3743
        }
3744
 
3745
        return false;
3746
    }
3747
 
3748
 
3749
 
3750
 
3751
    /**
3752
    * This function highlights a rectangle
3753
    *
3754
    * @param object obj    The chart object
3755
    * @param number shape  The coordinates of the rect to highlight
3756
    */
3757
    RGraph.Highlight.Rect = function (obj, shape)
3758
    {
3759
        var ca   = obj.canvas;
3760
        var co   = obj.context;
3761
        var prop = obj.properties;
3762
 
3763
        if (prop['chart.tooltips.highlight']) {
3764
 
3765
 
3766
            // Safari seems to need this
3767
            co.lineWidth = 1;
3768
 
3769
            /**
3770
            * Draw a rectangle on the canvas to highlight the appropriate area
3771
            */
3772
            co.beginPath();
3773
 
3774
                co.strokeStyle = prop['chart.highlight.stroke'];
3775
                co.fillStyle   = prop['chart.highlight.fill'];
3776
 
3777
                co.strokeRect(shape['x'],shape['y'],shape['width'],shape['height']);
3778
                co.fillRect(shape['x'],shape['y'],shape['width'],shape['height']);
3779
            co.stroke;
3780
            co.fill();
3781
        }
3782
    }
3783
 
3784
 
3785
 
3786
 
3787
    /**
3788
    * This function highlights a point
3789
    *
3790
    * @param object obj    The chart object
3791
    * @param number shape  The coordinates of the rect to highlight
3792
    */
3793
    RGraph.Highlight.Point = function (obj, shape)
3794
    {
3795
        var prop = obj.properties;
3796
        var ca   = obj.canvas;
3797
        var co   = obj.context;
3798
 
3799
        if (prop['chart.tooltips.highlight']) {
3800
 
3801
            /**
3802
            * Draw a rectangle on the canvas to highlight the appropriate area
3803
            */
3804
            co.beginPath();
3805
                co.strokeStyle = prop['chart.highlight.stroke'];
3806
                co.fillStyle   = prop['chart.highlight.fill'];
3807
                var radius   = prop['chart.highlight.point.radius'] || 2;
3808
                co.arc(shape['x'],shape['y'],radius, 0, TWOPI, 0);
3809
            co.stroke();
3810
            co.fill();
3811
        }
3812
    }
3813
 
3814
 
3815
 
3816
 
3817
    /**
3818
    * Creates an HTML tag
3819
    *
3820
    * @param string type
3821
    * @param obj    parent
3822
    * @param obj
3823
    * @param obj
3824
    */
3825
    RGraph.HTML.create = function (type, parent)
3826
    {
3827
        var obj = document.createElement(type);
3828
 
3829
 
3830
 
3831
 
3832
        // Add the attributes
3833
        if (arguments[2]) {
3834
            this.attr(obj, arguments[2]);
3835
        }
3836
 
3837
 
3838
 
3839
 
3840
        // Add the styles
3841
        if (arguments[3]) {
3842
            this.css(obj, arguments[3]);
3843
        }
3844
 
3845
 
3846
 
3847
 
3848
        /**
3849
        * Add the tag to the object that has been provided (usually the document)
3850
        */
3851
        parent.appendChild(obj);
3852
 
3853
 
3854
        return obj;
3855
    }
3856
 
3857
 
3858
 
3859
 
3860
    /**
3861
    * Sets attributes on a HTML object
3862
    *
3863
    * @param object obj
3864
    * @param object attr
3865
    */
3866
    RGraph.HTML.attr = function (obj, attr)
3867
    {
3868
        for (i in attr) {
3869
            if (typeof i == 'string') {
3870
                obj[i] = attr[i];
3871
            }
3872
        }
3873
    }
3874
 
3875
 
3876
 
3877
 
3878
    /**
3879
    * Sets CSS on a HTML object
3880
    *
3881
    * @param object obj
3882
    * @param object css
3883
    */
3884
    RGraph.HTML.css = function (obj, styles)
3885
    {
3886
        var style = obj.style;
3887
 
3888
        for (i in styles) {
3889
            if (typeof i == 'string') {
3890
                style[i] = styles[i];
3891
            }
3892
        }
3893
    }
3894
 
3895
 
3896
 
3897
 
3898
    /**
3899
    * This is the same as Date.parse - though a little more flexible.
3900
    *
3901
    * @param string str The date string to parse
3902
    * @return Returns the same thing as Date.parse
3903
    */
3904
    RGraph.parseDate = function (str)
3905
    {
3906
        str.trim();
3907
 
3908
        // Allow for: now (just the word "now")
3909
        if (str === 'now') {
3910
            str = (new Date()).toString();
3911
        }
3912
 
3913
        // Allow for: 2013-11-22 12:12:12 or  2013/11/22 12:12:12
3914
        if (str.match(/^(\d\d\d\d)(-|\/)(\d\d)(-|\/)(\d\d)( |T)(\d\d):(\d\d):(\d\d)$/)) {
3915
            str = RegExp.$1 + '-' + RegExp.$3 + '-' + RegExp.$5 + 'T' + RegExp.$7 + ':' + RegExp.$8 + ':' + RegExp.$9;
3916
        }
3917
 
3918
        // Allow for: 2013-11-22
3919
        if (str.match(/^\d\d\d\d-\d\d-\d\d$/)) {
3920
            str = str.replace(/-/, '/');
3921
        }
3922
 
3923
        // Allow for: 12:09:44 (time only using todays date)
3924
        if (str.match(/^\d\d:\d\d:\d\d$/)) {
3925
 
3926
            var dateObj  = new Date();
3927
            var date     = dateObj.getDate();
3928
            var month    = dateObj.getMonth() + 1;
3929
            var year     = dateObj.getFullYear();
3930
 
3931
            str = (year + '-' + month + '-' + date) + ' ' + str;
3932
        }
3933
 
3934
        return Date.parse(str);
3935
    }
3936
 
3937
 
3938
 
3939
 
3940
    // Reset all of the color values to their original values
3941
    RGraph.resetColorsToOriginalValues = function (obj)
3942
    {
3943
        if (obj.original_colors) {
3944
            // Reset the colors to their original values
3945
            for (var j in obj.original_colors) {
3946
                if (typeof j === 'string') {
3947
                    obj.properties[j] = RGraph.array_clone(obj.original_colors[j]);
3948
                }
3949
            }
3950
        }
3951
 
3952
        // Reset the colorsParsed flag so that they're parsed for gradients again
3953
        obj.colorsParsed = false;
3954
    }
3955
 
3956
 
3957
 
3958
 
3959
    /**
3960
    * This function is a short-cut for the canvas path syntax (which can be rather verbose)
3961
    *
3962
    * @param mixed  obj  This can either be the 2D context or an RGraph object
3963
    * @param array  path The path details
3964
    */
3965
    RGraph.Path = function (obj, path)
3966
    {
3967
        /**
3968
        * Allow either the RGraph object or the context to be used as the first argument
3969
        */
3970
        if (obj.isRGraph && typeof obj.type === 'string') {
3971
            var co = obj.context;
3972
        } else if (obj.toString().indexOf('CanvasRenderingContext2D') > 0) {
3973
            var co = obj;
3974
        }
3975
 
3976
        /**
3977
        * If the Path information has been passed as a  string - split it up
3978
        */
3979
        if (typeof path == 'string') {
3980
            path = path.split(/ +/);
3981
        }
3982
 
3983
        /**
3984
        * Go through the path information
3985
        */
3986
        for (var i=0,len=path.length; i<len; i+=1) {
3987
 
3988
            var op = path[i];
3989
 
3990
            // 100,100,50,0,Math.PI * 1.5, false
3991
            switch (op) {
3992
                case 'b':co.beginPath();break;
3993
                case 'c':co.closePath();break;
3994
                case 'm':co.moveTo(parseFloat(path[i+1]),parseFloat(path[i+2]));i+=2;break;
3995
                case 'l':co.lineTo(parseFloat(path[i+1]),parseFloat(path[i+2]));i+=2;break;
3996
                case 's':co.strokeStyle=path[i+1];co.stroke();i+=1;break;
3997
                case 'f':co.fillStyle=path[i+1];co.fill();i+=1;break;
3998
                case 'qc':co.quadraticCurveTo(parseFloat(path[i+1]),parseFloat(path[i+2]),parseFloat(path[i+3]),parseFloat(path[i+4]));i+=4;break;
3999
                case 'bc':co.bezierCurveTo(parseFloat(path[i+1]),parseFloat(path[i+2]),parseFloat(path[i+3]),parseFloat(path[i+4]),parseFloat(path[i+5]),parseFloat(path[i+6]));i+=6;break;
4000
                case 'r':co.rect(parseFloat(path[i+1]),parseFloat(path[i+2]),parseFloat(path[i+3]),parseFloat(path[i+4]));i+=4;break;
4001
                case 'a':co.arc(parseFloat(path[i+1]),parseFloat(path[i+2]),parseFloat(path[i+3]),parseFloat(path[i+4]),parseFloat(path[i+5]),path[i+6]==='true'||path[i+6]===true?true:false);i+=6;break;
4002
                case 'at':co.arcTo(parseFloat(path[i+1]),parseFloat(path[i+2]),parseFloat(path[i+3]),parseFloat(path[i+4]),parseFloat(path[i+5]));i+=5;break;
4003
                case 'lw':co.lineWidth=parseFloat(path[i+1]);i+=1;break;
4004
                case 'lj':co.lineJoin=path[i+1];i+=1;break;
4005
                case 'lc':co.lineCap=path[i+1];i+=1;break;
4006
                case 'sc':co.shadowColor=path[i+1];i+=1;break;
4007
                case 'sb':co.shadowBlur=parseFloat(path[i+1]);i+=1;break;
4008
                case 'sx':co.shadowOffsetX=parseFloat(path[i+1]);i+=1;break;
4009
                case 'sy':co.shadowOffsetY=parseFloat(path[i+1]);i+=1;break;
4010
                case 'fu':(path[i+1])(obj);i+=1;break;
4011
            }
4012
        }
4013
    }
4014
 
4015
 
4016
 
4017
// Some other functions. Because they're rarely changed - they're hand minified
4018
RGraph.LinearGradient=function(obj,x1,y1,x2,y2,color1,color2){var gradient=obj.context.createLinearGradient(x1,y1,x2,y2);var numColors=arguments.length-5;for (var i=5;i<arguments.length;++i){var color=arguments[i];var stop=(i-5)/(numColors-1);gradient.addColorStop(stop,color);}return gradient;}
4019
RGraph.RadialGradient=function(obj,x1,y1,r1,x2,y2,r2,color1,color2){var gradient=obj.context.createRadialGradient(x1,y1,r1,x2,y2,r2);var numColors=arguments.length-7;for(var i=7;i<arguments.length; ++i){var color=arguments[i];var stop=(i-7)/(numColors-1);gradient.addColorStop(stop,color);}return gradient;}
4020
RGraph.array_shift=function(arr){var ret=[];for(var i=1;i<arr.length;++i){ret.push(arr[i]);}return ret;}
4021
RGraph.AddEventListener=function(id,e,func){var type=arguments[3]?arguments[3]:'unknown';RGraph.Registry.Get('chart.event.handlers').push([id,e,func,type]);}
4022
RGraph.ClearEventListeners=function(id){if(id&&id=='window'){window.removeEventListener('mousedown',window.__rgraph_mousedown_event_listener_installed__,false);window.removeEventListener('mouseup',window.__rgraph_mouseup_event_listener_installed__,false);}else{var canvas = document.getElementById(id);canvas.removeEventListener('mouseup',canvas.__rgraph_mouseup_event_listener_installed__,false);canvas.removeEventListener('mousemove',canvas.__rgraph_mousemove_event_listener_installed__,false);canvas.removeEventListener('mousedown',canvas.__rgraph_mousedown_event_listener_installed__,false);canvas.removeEventListener('click',canvas.__rgraph_click_event_listener_installed__,false);}}
4023
RGraph.HidePalette=function(){var div=RGraph.Registry.Get('palette');if(typeof(div)=='object'&&div){div.style.visibility='hidden';div.style.display='none';RGraph.Registry.Set('palette',null);}}
4024
RGraph.random=function(min,max){var dp=arguments[2]?arguments[2]:0;var r=Math.random();return Number((((max - min) * r) + min).toFixed(dp));}
4025
RGraph.random.array=function(num,min,max){var arr = [];for(var i=0;i<num;i++)arr.push(RGraph.random(min,max));return arr;}
4026
RGraph.NoShadow=function(obj){obj.context.shadowColor='rgba(0,0,0,0)';obj.context.shadowBlur=0;obj.context.shadowOffsetX=0;obj.context.shadowOffsetY=0;}
4027
RGraph.SetShadow=function(obj,color,offsetx,offsety,blur){obj.context.shadowColor=color;obj.context.shadowOffsetX=offsetx;obj.context.shadowOffsetY=offsety;obj.context.shadowBlur=blur;}
4028
RGraph.array_reverse=function(arr){var newarr=[];for(var i=arr.length-1;i>=0;i--){newarr.push(arr[i]);}return newarr;}
4029
RGraph.Registry.Set=function(name,value){RGraph.Registry.store[name]=value;return value;}
4030
RGraph.Registry.Get=function(name){return RGraph.Registry.store[name];}
4031
RGraph.degrees2Radians=function(degrees){return degrees*(PI/180);}
4032
RGraph.log=(function(n,base){var log=Math.log;return function(n,base){return log(n)/(base?log(base):1);};})();
4033
RGraph.is_array=function(obj){return obj!=null&&obj.constructor.toString().indexOf('Array')!=-1;}
4034
RGraph.trim=function(str){return RGraph.ltrim(RGraph.rtrim(str));}
4035
RGraph.ltrim=function(str){return str.replace(/^(\s|\0)+/, '');}
4036
RGraph.rtrim=function(str){return str.replace(/(\s|\0)+$/, '');}
4037
RGraph.GetHeight=function(obj){return obj.canvas.height;}
4038
RGraph.GetWidth=function(obj){return obj.canvas.width;}
4039
RGraph.is_null=function(arg){if(arg==null||(typeof(arg))=='object'&&!arg){return true;}return false;}
4040
RGraph.Timer=function(label){if(typeof(RGraph.TIMER_LAST_CHECKPOINT)=='undefined'){RGraph.TIMER_LAST_CHECKPOINT=Date.now();}var now=Date.now();console.log(label+': '+(now-RGraph.TIMER_LAST_CHECKPOINT).toString());RGraph.TIMER_LAST_CHECKPOINT=now;}
4041
RGraph.Async=function(func){return setTimeout(func,arguments[1]?arguments[1]:1);}
4042
RGraph.isIE=function(){return navigator.userAgent.indexOf('Trident')>0||navigator.userAgent.indexOf('MSIE')>0;};ISIE=RGraph.isIE();
4043
RGraph.isIE6=function(){return navigator.userAgent.indexOf('MSIE 6')>0;};ISIE6=RGraph.isIE6();
4044
RGraph.isIE7=function(){return navigator.userAgent.indexOf('MSIE 7')>0;};ISIE7=RGraph.isIE7();
4045
RGraph.isIE8=function(){return navigator.userAgent.indexOf('MSIE 8')>0;};ISIE8=RGraph.isIE8();
4046
RGraph.isIE9=function(){return navigator.userAgent.indexOf('MSIE 9')>0;};ISIE9=RGraph.isIE9();
4047
RGraph.isIE10=function(){return navigator.userAgent.indexOf('MSIE 10')>0;};ISIE10=RGraph.isIE10();
4048
RGraph.isIE11=function(){return navigator.userAgent.indexOf('MSIE')==-1&&navigator.userAgent.indexOf('Trident')>0;};ISIE11=RGraph.isIE11();
4049
RGraph.isIE9up=function(){return ISIE9||ISIE10||ISIE11;};ISIE9UP=RGraph.isIE9up();
4050
RGraph.isIE10up=function(){return ISIE10||ISIE11};ISIE10UP=RGraph.isIE10up();
4051
RGraph.isIE11up=function(){return ISIE11};ISIE11UP=RGraph.isIE11up();
4052
RGraph.isOld=function(){return ISIE6||ISIE7||ISIE8;};ISOLD=RGraph.isOld();
4053
RGraph.Reset=function(canvas){canvas.width=canvas.width;RGraph.ObjectRegistry.Clear(canvas);canvas.__rgraph_aa_translated__=false;}
4054
function pd(variable){RGraph.pr(variable);}
4055
function p(variable){RGraph.pr(arguments[0],arguments[1],arguments[3]);}
4056
function a(variable){alert(variable);}
4057
function cl(variable){return console.log(variable);}