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
    if (typeof(RGraph) == 'undefined') RGraph = {};
12
 
13
    /**
14
    * The bi-polar/age frequency constructor.
15
    *
16
    * @param string id The id of the canvas
17
    * @param array  left  The left set of data points
18
    * @param array  right The right set of data points
19
    *
20
    * REMEMBER If ymin is implemented you need to update the .getValue() method
21
    */
22
    RGraph.Bipolar = function (id, left, right)
23
    {
24
        // Get the canvas and context objects
25
        this.id                = id;
26
        this.canvas            = document.getElementById(typeof id === 'object' ? id.id : id);
27
        this.context           = this.canvas.getContext('2d');
28
        this.canvas.__object__ = this;
29
        this.type              = 'bipolar';
30
        this.coords            = [];
31
        this.coordsLeft        = [];
32
        this.coordsRight       = [];
33
        this.max               = 0;
34
        this.isRGraph          = true;
35
        this.uid               = RGraph.CreateUID();
36
        this.canvas.uid        = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
37
        this.coordsText        = [];
38
 
39
 
40
        /**
41
        * Compatibility with older browsers
42
        */
43
        RGraph.OldBrowserCompat(this.context);
44
 
45
 
46
        // The left and right data respectively
47
        this.left       = left;
48
        this.right      = right;
49
        this.data       = [left, right];
50
 
51
        this.properties = {
52
            'chart.margin':                 2,
53
            'chart.xtickinterval':          null,
54
            'chart.labels':                 [],
55
            'chart.labels.above':           false,
56
            'chart.text.size':              10,
57
            'chart.text.color':             'black', // (Simple) gradients are not supported
58
            'chart.text.font':              'Arial',
59
            'chart.title.left':             '',
60
            'chart.title.right':            '',
61
            'chart.gutter.center':          60,
62
            'chart.gutter.left':            25,
63
            'chart.gutter.right':           25,
64
            'chart.gutter.top':             25,
65
            'chart.gutter.bottom':          25,
66
            'chart.title':                  '',
67
            'chart.title.background':       null,
68
            'chart.title.hpos':             null,
69
            'chart.title.vpos':             null,
70
            'chart.title.bold':             true,
71
            'chart.title.font':             null,
72
            'chart.title.x':                null,
73
            'chart.title.y':                null,
74
            'chart.title.halign':           null,
75
            'chart.title.valign':           null,
76
            'chart.colors':                 ['#0f0'],
77
            'chart.contextmenu':            null,
78
            'chart.tooltips':               null,
79
            'chart.tooltips.effect':         'fade',
80
            'chart.tooltips.css.class':      'RGraph_tooltip',
81
            'chart.tooltips.highlight':     true,
82
            'chart.tooltips.event':         'onclick',
83
            'chart.highlight.stroke':       'rgba(0,0,0,0)',
84
            'chart.highlight.fill':         'rgba(255,255,255,0.7)',
85
            'chart.units.pre':              '',
86
            'chart.units.post':             '',
87
            'chart.shadow':                 false,
88
            'chart.shadow.color':           '#666',
89
            'chart.shadow.offsetx':         3,
90
            'chart.shadow.offsety':         3,
91
            'chart.shadow.blur':            3,
92
            'chart.annotatable':            false,
93
            'chart.annotate.color':         'black',
94
            'chart.xmax':                   null,
95
            'chart.xmin':                   0,
96
            'chart.scale.decimals':         null,
97
            'chart.scale.point':            '.',
98
            'chart.scale.thousand':         ',',
99
            'chart.axis.color':             'black',
100
            'chart.zoom.factor':            1.5,
101
            'chart.zoom.fade.in':           true,
102
            'chart.zoom.fade.out':          true,
103
            'chart.zoom.hdir':              'right',
104
            'chart.zoom.vdir':              'down',
105
            'chart.zoom.frames':            25,
106
            'chart.zoom.delay':             16.666,
107
            'chart.zoom.shadow':            true,
108
            'chart.zoom.background':        true,
109
            'chart.zoom.action':            'zoom',
110
            'chart.resizable':              false,
111
            'chart.resize.handle.background': null,
112
            'chart.strokestyle':            'rgba(0,0,0,0)',
113
            'chart.events.mousemove':       null,
114
            'chart.events.click':           null,
115
            'chart.linewidth':              1,
116
            'chart.noaxes':                 false,
117
            'chart.xlabels':                true,
118
            'chart.numyticks':              null,
119
            'chart.numxticks':              5,
120
            'chart.axis.linewidth':         1,
121
            'chart.labels.count':           5
122
        }
123
 
124
        // Pad the arrays so they're the same size
125
        while (this.left.length < this.right.length) this.left.push(0);
126
        while (this.left.length > this.right.length) this.right.push(0);
127
 
128
        /**
129
        * Set the default for the number of Y tickmarks
130
        */
131
        this.properties['chart.numyticks'] = this.left.length;
132
 
133
 
134
 
135
 
136
        /**
137
        * Create the dollar objects so that functions can be added to them
138
        */
139
        var linear_data = RGraph.array_linearize(this.left, this.right);
140
        for (var i=0; i<linear_data.length; ++i) {
141
            this['$' + i] = {};
142
        }
143
 
144
 
145
        /**
146
        * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
147
        * done already
148
        */
149
        if (!this.canvas.__rgraph_aa_translated__) {
150
            this.context.translate(0.5,0.5);
151
 
152
            this.canvas.__rgraph_aa_translated__ = true;
153
        }
154
 
155
 
156
 
157
 
158
        ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
159
 
160
 
161
 
162
 
163
        var RG   = RGraph;
164
        var ca   = this.canvas;
165
        var co   = ca.getContext('2d');
166
        var prop = this.properties;
167
        //var $jq  = jQuery;
168
 
169
 
170
 
171
 
172
        //////////////////////////////////// METHODS ///////////////////////////////////////
173
 
174
 
175
 
176
 
177
        /**
178
        * The setter
179
        *
180
        * @param name  string The name of the parameter to set
181
        * @param value mixed  The value of the paraneter
182
        */
183
        this.Set = function (name, value)
184
        {
185
            name = name.toLowerCase();
186
 
187
            /**
188
            * This should be done first - prepend the propertyy name with "chart." if necessary
189
            */
190
            if (name.substr(0,6) != 'chart.') {
191
                name = 'chart.' + name;
192
            }
193
 
194
            prop[name] = value;
195
 
196
            return this;
197
        }
198
 
199
 
200
 
201
 
202
        /**
203
        * The getter
204
        *
205
        * @param name string The name of the parameter to get
206
        */
207
        this.Get = function (name)
208
        {
209
            /**
210
            * This should be done first - prepend the property name with "chart." if necessary
211
            */
212
            if (name.substr(0,6) != 'chart.') {
213
                name = 'chart.' + name;
214
            }
215
 
216
            return this.properties[name.toLowerCase()];
217
        }
218
 
219
 
220
 
221
 
222
        /**
223
        * Draws the graph
224
        */
225
        this.Draw = function ()
226
        {
227
            /**
228
            * Fire the onbeforedraw event
229
            */
230
            RG.FireCustomEvent(this, 'onbeforedraw');
231
 
232
 
233
            /**
234
            * Parse the colors. This allows for simple gradient syntax
235
            */
236
            if (!this.colorsParsed) {
237
                this.parseColors();
238
 
239
                // Don't want to do this again
240
                this.colorsParsed = true;
241
            }
242
 
243
 
244
            /**
245
            * This is new in May 2011 and facilitates indiviual gutter settings,
246
            * eg chart.gutter.left
247
            */
248
            this.gutterLeft   = prop['chart.gutter.left'];
249
            this.gutterRight  = prop['chart.gutter.right'];
250
            this.gutterTop    = prop['chart.gutter.top'];
251
            this.gutterBottom = prop['chart.gutter.bottom'];
252
 
253
 
254
 
255
            // Reset the data to what was initially supplied
256
            this.left  = this.data[0];
257
            this.right = this.data[1];
258
 
259
            // Sequential color index
260
            this.sequentialColorIndex = 0;
261
 
262
 
263
            /**
264
            * Reset the coords array
265
            */
266
            this.coords = [];
267
 
268
            this.GetMax();
269
            this.DrawAxes();
270
            this.DrawTicks();
271
            this.DrawLeftBars();
272
            this.DrawRightBars();
273
 
274
            // Redraw the bars so that shadows on not on top
275
            this.RedrawBars();
276
 
277
            this.DrawAxes();
278
 
279
            this.DrawLabels();
280
            this.DrawTitles();
281
 
282
 
283
            /**
284
            * Setup the context menu if required
285
            */
286
            if (prop['chart.contextmenu']) {
287
                RG.ShowContext(this);
288
            }
289
 
290
 
291
            /**
292
            * This function enables resizing
293
            */
294
            if (prop['chart.resizable']) {
295
                RG.AllowResizing(this);
296
            }
297
 
298
 
299
            /**
300
            * This installs the event listeners
301
            */
302
            RG.InstallEventListeners(this);
303
 
304
 
305
            /**
306
            * Fire the RGraph ondraw event
307
            */
308
            RG.FireCustomEvent(this, 'ondraw');
309
 
310
            return this;
311
        }
312
 
313
 
314
 
315
 
316
        /**
317
        * Draws the axes
318
        */
319
        this.DrawAxes = function ()
320
        {
321
            // Set the linewidth
322
            co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
323
 
324
 
325
            // Draw the left set of axes
326
            co.beginPath();
327
            co.strokeStyle = prop['chart.axis.color'];
328
 
329
            this.axisWidth  = (ca.width - prop['chart.gutter.center'] - this.gutterLeft - this.gutterRight) / 2;
330
            this.axisHeight = ca.height - this.gutterTop - this.gutterBottom;
331
 
332
 
333
            // This must be here so that the two above variables are calculated
334
            if (prop['chart.noaxes']) {
335
                return;
336
            }
337
 
338
            co.moveTo(this.gutterLeft, Math.round( ca.height - this.gutterBottom));
339
            co.lineTo(this.gutterLeft + this.axisWidth, Math.round( ca.height - this.gutterBottom));
340
 
341
            co.moveTo(Math.round( this.gutterLeft + this.axisWidth), ca.height - this.gutterBottom);
342
            co.lineTo(Math.round( this.gutterLeft + this.axisWidth), this.gutterTop);
343
 
344
            co.stroke();
345
 
346
 
347
            // Draw the right set of axes
348
            co.beginPath();
349
 
350
            var x = this.gutterLeft + this.axisWidth + prop['chart.gutter.center'];
351
 
352
            co.moveTo(Math.round( x), this.gutterTop);
353
            co.lineTo(Math.round( x), ca.height - this.gutterBottom);
354
 
355
            co.moveTo(Math.round( x), Math.round( ca.height - this.gutterBottom));
356
            co.lineTo(ca.width - this.gutterRight, Math.round( ca.height - this.gutterBottom));
357
 
358
            co.stroke();
359
        }
360
 
361
 
362
 
363
 
364
        /**
365
        * Draws the tick marks on the axes
366
        */
367
        this.DrawTicks = function ()
368
        {
369
            // Set the linewidth
370
            co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
371
 
372
            var numDataPoints = this.left.length;
373
            var barHeight     = ( (ca.height - this.gutterTop - this.gutterBottom)- (this.left.length * (prop['chart.margin'] * 2) )) / numDataPoints;
374
 
375
            // Store this for later
376
            this.barHeight = barHeight;
377
 
378
            // If no axes - no tickmarks
379
            if (prop['chart.noaxes']) {
380
                return;
381
            }
382
 
383
            // Draw the left Y tick marks
384
            if (prop['chart.numyticks'] > 0) {
385
                co.beginPath();
386
                    for (var i=0; i<prop['chart.numyticks']; ++i) {
387
                        var y = prop['chart.gutter.top'] + (((ca.height - this.gutterTop - this.gutterBottom) / prop['chart.numyticks']) * i);
388
                        co.moveTo(this.gutterLeft + this.axisWidth , y);
389
                        co.lineTo(this.gutterLeft + this.axisWidth + 3, y);
390
                    }
391
                co.stroke();
392
 
393
                //Draw the right axis Y tick marks
394
                co.beginPath();
395
                    for (var i=0; i<prop['chart.numyticks']; ++i) {
396
                        var y = prop['chart.gutter.top'] + (((ca.height - this.gutterTop - this.gutterBottom) / prop['chart.numyticks']) * i);
397
                        co.moveTo(this.gutterLeft + this.axisWidth + prop['chart.gutter.center'], y);
398
                        co.lineTo(this.gutterLeft + this.axisWidth + prop['chart.gutter.center'] - 3, y);
399
                    }
400
                co.stroke();
401
            }
402
 
403
 
404
 
405
            /**
406
            * X tickmarks
407
            */
408
            if (prop['chart.numxticks'] > 0) {
409
                var xInterval = this.axisWidth / prop['chart.numxticks'];
410
 
411
                // Is chart.xtickinterval specified ? If so, use that.
412
                if (typeof(prop['chart.xtickinterval']) == 'number') {
413
                    xInterval = prop['chart.xtickinterval'];
414
                }
415
 
416
 
417
                // Draw the left sides X tick marks
418
                for (i=this.gutterLeft; i<(this.gutterLeft + this.axisWidth); i+=xInterval) {
419
                    co.beginPath();
420
                    co.moveTo(Math.round( i), ca.height - this.gutterBottom);
421
                    co.lineTo(Math.round( i), (ca.height - this.gutterBottom) + 4);
422
                    co.closePath();
423
 
424
                    co.stroke();
425
                }
426
 
427
                // Draw the right sides X tick marks
428
                var stoppingPoint = ca.width - this.gutterRight;
429
 
430
                for (i=(this.gutterLeft + this.axisWidth + prop['chart.gutter.center'] + xInterval); i<=stoppingPoint; i+=xInterval) {
431
                    co.beginPath();
432
                        co.moveTo(Math.round(i), ca.height - this.gutterBottom);
433
                        co.lineTo(Math.round(i), (ca.height - this.gutterBottom) + 4);
434
                    co.closePath();
435
 
436
                    co.stroke();
437
                }
438
            }
439
        }
440
 
441
 
442
 
443
 
444
        /**
445
        * Figures out the maximum value, or if defined, uses xmax
446
        */
447
        this.GetMax = function()
448
        {
449
            var dec  = prop['chart.scale.decimals'];
450
 
451
            // chart.xmax defined
452
            if (prop['chart.xmax']) {
453
 
454
                var max = prop['chart.xmax'];
455
                var min = prop['chart.xmin'];
456
 
457
                this.scale2 = RG.getScale2(this, {
458
                                                      'max':max,
459
                                                      'min':min,
460
                                                      'strict': true,
461
                                                      'scale.thousand':prop['chart.scale.thousand'],
462
                                                      'scale.point':prop['chart.scale.point'],
463
                                                      'scale.decimals':prop['chart.scale.decimals'],
464
                                                      'ylabels.count':prop['chart.labels.count'],
465
                                                      'scale.round':prop['chart.scale.round'],
466
                                                      'units.pre': prop['chart.units.pre'],
467
                                                      'units.post': prop['chart.units.post']
468
                                                     });
469
                this.max = this.scale2.max;
470
                this.min = this.scale2.min;
471
 
472
 
473
            /**
474
            * Generate the scale ourselves
475
            */
476
            } else {
477
 
478
                var max = Math.max(RG.array_max(this.left), RG.array_max(this.right));
479
 
480
                this.scale2 = RG.getScale2(this, {
481
                                                      'max':max,
482
                                                      //'strict': true,
483
                                                      'min':prop['chart.xmin'],
484
                                                      'scale.thousand':prop['chart.scale.thousand'],
485
                                                      'scale.point':prop['chart.scale.point'],
486
                                                      'scale.decimals':prop['chart.scale.decimals'],
487
                                                      'ylabels.count':prop['chart.labels.count'],
488
                                                      'scale.round':prop['chart.scale.round'],
489
                                                      'units.pre': prop['chart.units.pre'],
490
                                                      'units.post': prop['chart.units.post']
491
                                                     });
492
 
493
 
494
                this.max = this.scale2.max;
495
                this.min = this.scale2.min;
496
            }
497
 
498
            // Don't need to return it as it is stored in this.max
499
        }
500
 
501
 
502
 
503
 
504
        /**
505
        * Function to draw the left hand bars
506
        */
507
        this.DrawLeftBars = function ()
508
        {
509
            // Set the stroke colour
510
            co.strokeStyle = prop['chart.strokestyle'];
511
 
512
            // Set the linewidth
513
            co.lineWidth = prop['chart.linewidth'];
514
 
515
            for (i=0; i<this.left.length; ++i) {
516
 
517
                /**
518
                * Turn on a shadow if requested
519
                */
520
                if (prop['chart.shadow']) {
521
                    co.shadowColor   = prop['chart.shadow.color'];
522
                    co.shadowBlur    = prop['chart.shadow.blur'];
523
                    co.shadowOffsetX = prop['chart.shadow.offsetx'];
524
                    co.shadowOffsetY = prop['chart.shadow.offsety'];
525
                }
526
 
527
                co.beginPath();
528
 
529
                    // If chart.colors.sequential is specified - handle that
530
                    if (prop['chart.colors.sequential']) {
531
                        co.fillStyle = prop['chart.colors'][this.sequentialColorIndex];
532
                        this.sequentialColorIndex++;
533
 
534
                    } else {
535
                        co.fillStyle = prop['chart.colors'][0];
536
                    }
537
 
538
                    /**
539
                    * Work out the coordinates
540
                    */
541
 
542
                    var width = (( (this.left[i] - this.min) / (this.max - this.min)) *  this.axisWidth);
543
 
544
                    var coords = [Math.round( this.gutterLeft + this.axisWidth - width),
545
                                  Math.round( this.gutterTop + (i * ( this.axisHeight / this.left.length)) + prop['chart.margin']),
546
                                  width,
547
                                  this.barHeight];
548
 
549
                    // Draw the IE shadow if necessary
550
                    if (ISOLD && prop['chart.shadow']) {
551
                        this.DrawIEShadow(coords);
552
                    }
553
 
554
 
555
                    if (this.left[i]) {
556
                        co.strokeRect(coords[0], coords[1], coords[2], coords[3]);
557
                        co.fillRect(coords[0], coords[1], coords[2], coords[3]);
558
                    }
559
 
560
                co.stroke();
561
                co.fill();
562
 
563
                /**
564
                * Add the coordinates to the coords array
565
                */
566
                this.coords.push([coords[0],coords[1],coords[2],coords[3]]);
567
                this.coordsLeft.push([coords[0],coords[1],coords[2],coords[3]]);
568
            }
569
 
570
            /**
571
            * Turn off any shadow
572
            */
573
            RG.NoShadow(this);
574
 
575
            // Reset the linewidth
576
            co.lineWidth = 1;
577
        }
578
 
579
 
580
 
581
 
582
        /**
583
        * Function to draw the right hand bars
584
        */
585
        this.DrawRightBars = function ()
586
        {
587
            // Set the stroke colour
588
            co.strokeStyle = prop['chart.strokestyle'];
589
 
590
            // Set the linewidth
591
            co.lineWidth = prop['chart.linewidth'];
592
 
593
            /**
594
            * Turn on a shadow if requested
595
            */
596
            if (prop['chart.shadow']) {
597
                co.shadowColor   = prop['chart.shadow.color'];
598
                co.shadowBlur    = prop['chart.shadow.blur'];
599
                co.shadowOffsetX = prop['chart.shadow.offsetx'];
600
                co.shadowOffsetY = prop['chart.shadow.offsety'];
601
            }
602
 
603
            for (var i=0; i<this.right.length; ++i) {
604
 
605
                co.beginPath();
606
 
607
                    // If chart.colors.sequential is specified - handle that
608
                    if (prop['chart.colors.sequential']) {
609
                        co.fillStyle = prop['chart.colors'][this.sequentialColorIndex++];
610
                    } else {
611
                        co.fillStyle = prop['chart.colors'][0];
612
                    }
613
 
614
 
615
                    var width = (((this.right[i] - this.min) / (this.max - this.min)) * this.axisWidth);
616
 
617
                    var coords = [
618
                                  Math.round( this.gutterLeft + this.axisWidth + prop['chart.gutter.center']),
619
                                  Math.round( prop['chart.margin'] + (i * (this.axisHeight / this.right.length)) + this.gutterTop),
620
                                  width,
621
                                  this.barHeight
622
                                ];
623
 
624
                        // Draw the IE shadow if necessary
625
                        if (ISOLD && prop['chart.shadow']) {
626
                            this.DrawIEShadow(coords);
627
                        }
628
                    if (this.right[i]) {
629
                        co.strokeRect(Math.round( coords[0]), Math.round( coords[1]), coords[2], coords[3]);
630
                        co.fillRect(Math.round( coords[0]), Math.round( coords[1]), coords[2], coords[3]);
631
                    }
632
 
633
                co.closePath();
634
 
635
                /**
636
                * Add the coordinates to the coords array
637
                */
638
                this.coords.push([coords[0],coords[1],coords[2],coords[3]]);
639
                this.coordsRight.push([coords[0],coords[1],coords[2],coords[3]]);
640
            }
641
 
642
            co.stroke();
643
 
644
            /**
645
            * Turn off any shadow
646
            */
647
            RG.NoShadow(this);
648
 
649
            // Reset the linewidth
650
            co.lineWidth = 1;
651
        }
652
 
653
 
654
 
655
 
656
        /**
657
        * Draws the titles
658
        */
659
        this.DrawLabels = function ()
660
        {
661
            co.fillStyle = prop['chart.text.color'];
662
 
663
            //var labelPoints = new Array();
664
            var font   = prop['chart.text.font'];
665
            var size   = prop['chart.text.size'];
666
            var labels = prop['chart.labels'];
667
            var barAreaHeight = ca.height - this.gutterTop - this.gutterBottom;
668
 
669
            for (var i=0,len=labels.length; i<len; i+=1) {
670
                RG.Text2(this, {'font':font,
671
                                'size':size,
672
                                'x':this.gutterLeft + this.axisWidth + (prop['chart.gutter.center'] / 2),
673
                                'y':this.gutterTop + ((barAreaHeight / labels.length) * (i)) + ((barAreaHeight / labels.length) / 2),
674
                                'text':String(labels[i] ? String(labels[i]) : ''),
675
                                'halign':'center',
676
                                'valign':'center',
677
                                'marker':false,
678
                                'tag': 'labels'
679
                               });
680
            }
681
 
682
/*
683
* OLD STYLE LABELS
684
*
685
            var max = Math.max(this.left.length, this.right.length);
686
 
687
            for (i=0; i<max; ++i) {
688
                var barAreaHeight = ca.height - this.gutterTop - this.gutterBottom;
689
                var barHeight     = barAreaHeight / this.left.length;
690
                var yPos          = (i * barAreaHeight) + this.gutterTop;
691
 
692
                labelPoints.push(this.gutterTop + (i * barHeight) + (barHeight / 2) + 5);
693
            }
694
 
695
            for (i=0; i<labelPoints.length; ++i) {
696
 
697
                RG.Text2(this, {'font':prop['chart.text.font'],
698
                                    'size':prop['chart.text.size'],
699
                                    'x':this.gutterLeft + this.axisWidth + (prop['chart.gutter.center'] / 2),
700
                                    'y':labelPoints[i],
701
                                    'text':String(prop['chart.labels'][i] ? prop['chart.labels'][i] : ''),
702
                                    'halign':'center',
703
                                    'tag': 'labels'
704
                                   });
705
            }
706
*/
707
 
708
 
709
 
710
 
711
            if (prop['chart.xlabels']) {
712
 
713
                var grapharea = (ca.width - prop['chart.gutter.center'] - this.gutterLeft - this.gutterRight) / 2;
714
 
715
                // Now draw the X labels for the left hand side
716
                for (var i=0; i<this.scale2.labels.length; ++i) {
717
                    RG.Text2(this, {'font':font,
718
                                        'size':size,
719
                                        'x':this.gutterLeft + ((grapharea / this.scale2.labels.length) * i),
720
                                        'y':ca.height - this.gutterBottom + 3,
721
                                        'text':this.scale2.labels[this.scale2.labels.length - i - 1],
722
                                        'valign':'top',
723
                                        'halign':'center',
724
                                        'tag': 'scale'
725
                                       });
726
 
727
                    // Draw the scale for the right hand side
728
                    RG.Text2(this, {'font':font,
729
                                        'size':size,
730
                                        'x':this.gutterLeft+ grapharea + prop['chart.gutter.center'] + ((grapharea / this.scale2.labels.length) * (i + 1)),
731
                                        'y':ca.height - this.gutterBottom + 3,
732
                                        'text':this.scale2.labels[i],
733
                                        'valign':'top',
734
                                        'halign':'center',
735
                                        'tag': 'scale'
736
                                       });
737
                }
738
            }
739
 
740
            /**
741
            * Draw above labels
742
            */
743
            if (prop['chart.labels.above']) {
744
 
745
                // Draw the left sides above labels
746
                for (var i=0; i<this.coordsLeft.length; ++i) {
747
 
748
                    if (typeof(this.left[i]) != 'number') {
749
                        continue;
750
                    }
751
 
752
                    var coords = this.coordsLeft[i];
753
                    RG.Text2(this, {'font':font,
754
                                        'size':size,
755
                                        'x':coords[0] - 5,
756
                                        'y':coords[1] + (coords[3] / 2),
757
                                        'text':RG.number_format(this, this.left[i], prop['chart.units.pre'], prop['chart.units.post']),
758
                                        'valign':'center',
759
                                        'halign':'right',
760
                                        'tag':'labels.above'
761
                                       });
762
                }
763
 
764
                // Draw the right sides above labels
765
                for (i=0; i<this.coordsRight.length; ++i) {
766
 
767
                    if (typeof(this.right[i]) != 'number') {
768
                        continue;
769
                    }
770
 
771
                    var coords = this.coordsRight[i];
772
                    RG.Text2(this, {'font':font,
773
                                        'size':size,
774
                                        'x':coords[0] + coords[2] +  5,
775
                                        'y':coords[1] + (coords[3] / 2),
776
                                        'text':RG.number_format(this, this.right[i], prop['chart.units.pre'], prop['chart.units.post']),
777
                                        'valign':'center',
778
                                        'halign':'left',
779
                                        'tag': 'labels.above'
780
                                       });
781
                }
782
            }
783
        }
784
 
785
 
786
 
787
 
788
        /**
789
        * Draws the titles
790
        */
791
        this.DrawTitles = function ()
792
        {
793
            RG.Text2(this, {'font':prop['chart.text.font'],
794
                         'size':prop['chart.text.size'],
795
                         'x':this.gutterLeft + 5,
796
                         'y':this.gutterTop - 5,
797
                         'text':String(prop['chart.title.left']),
798
                         'halign':'left',
799
                         'valign':'bottom',
800
                         'tag': 'title.left'
801
                        });
802
 
803
            RG.Text2(this, {'font':prop['chart.text.font'],
804
                         'size':prop['chart.text.size'],
805
                         'x': ca.width - this.gutterRight - 5,
806
                         'y':this.gutterTop - 5,
807
                         'text':String(prop['chart.title.right']),
808
                         'halign':'right',
809
                         'valign':'bottom',
810
                         'tag': 'title.right'
811
                        });
812
 
813
 
814
 
815
            // Draw the main title for the whole chart
816
            RG.DrawTitle(this, prop['chart.title'], this.gutterTop, null, prop['chart.title.size'] ? prop['chart.title.size'] : null);
817
        }
818
 
819
 
820
 
821
 
822
        /**
823
        * This function is used by MSIE only to manually draw the shadow
824
        *
825
        * @param array coords The coords for the bar
826
        */
827
        this.DrawIEShadow = function (coords)
828
        {
829
            var prevFillStyle = co.fillStyle;
830
            var offsetx = prop['chart.shadow.offsetx'];
831
            var offsety = prop['chart.shadow.offsety'];
832
 
833
            co.lineWidth = prop['chart.linewidth'];
834
            co.fillStyle = prop['chart.shadow.color'];
835
            co.beginPath();
836
 
837
            // Draw shadow here
838
            co.fillRect(coords[0] + offsetx, coords[1] + offsety, coords[2],coords[3]);
839
 
840
            co.fill();
841
 
842
            // Change the fillstyle back to what it was
843
            co.fillStyle = prevFillStyle;
844
        }
845
 
846
 
847
 
848
 
849
        /**
850
        * Returns the appropriate focussed bar coordinates
851
        *
852
        * @param e object The event object
853
        */
854
        this.getShape =
855
        this.getBar = function (e)
856
        {
857
            var canvas      = this.canvas;
858
            var context     = this.context;
859
            var mouseCoords = RG.getMouseXY(e);
860
 
861
            /**
862
            * Loop through the bars determining if the mouse is over a bar
863
            */
864
            for (var i=0; i<this.coords.length; i++) {
865
 
866
                var mouseX = mouseCoords[0];
867
                var mouseY = mouseCoords[1];
868
                var left   = this.coords[i][0];
869
                var top    = this.coords[i][1];
870
                var width  = this.coords[i][2];
871
                var height = this.coords[i][3];
872
 
873
                if (mouseX >= left && mouseX <= (left + width) && mouseY >= top && mouseY <= (top + height) ) {
874
 
875
                    var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
876
 
877
                    return {
878
                            0: this,1: left,2: top,3: width,4: height,5: i,
879
                            'object': this, 'x': left, 'y': top, 'width': width, 'height': height, 'index': i, 'tooltip': tooltip
880
                           };
881
                }
882
            }
883
 
884
            return null;
885
        }
886
 
887
 
888
 
889
 
890
        /**
891
        * Each object type has its own Highlight() function which highlights the appropriate shape
892
        *
893
        * @param object shape The shape to highlight
894
        */
895
        this.Highlight = function (shape)
896
        {
897
            // Add the new highlight
898
            RG.Highlight.Rect(this, shape);
899
        }
900
 
901
 
902
 
903
 
904
        /**
905
        * When you click on the canvas, this will return the relevant value (if any)
906
        *
907
        * REMEMBER This function will need updating if the Bipolar ever gets chart.ymin
908
        *
909
        * @param object e The event object
910
        */
911
        this.getValue = function (e)
912
        {
913
            var obj     = e.target.__object__;
914
            var mouseXY = RG.getMouseXY(e);
915
            var mouseX  = mouseXY[0];
916
 
917
            /**
918
            * Left hand side
919
            */
920
            if (mouseX > this.gutterLeft && mouseX < ( (ca.width / 2) - (prop['chart.gutter.center'] / 2) )) {
921
                var value = (mouseX - prop['chart.gutter.left']) / this.axisWidth;
922
                    value = this.max - (value * this.max);
923
            }
924
 
925
            /**
926
            * Right hand side
927
            */
928
            if (mouseX < (ca.width -  this.gutterRight) && mouseX > ( (ca.width / 2) + (prop['chart.gutter.center'] / 2) )) {
929
                var value = (mouseX - prop['chart.gutter.left'] - this.axisWidth - prop['chart.gutter.center']) / this.axisWidth;
930
                    value = (value * this.max);
931
            }
932
 
933
            return value;
934
        }
935
 
936
 
937
 
938
 
939
        /**
940
        * The getObjectByXY() worker method. Don't call this call:
941
        *
942
        * RGraph.ObjectRegistry.getObjectByXY(e)
943
        *
944
        * @param object e The event object
945
        */
946
        this.getObjectByXY = function (e)
947
        {
948
            var mouseXY = RG.getMouseXY(e);
949
 
950
            if (
951
                   mouseXY[0] > prop['chart.gutter.left']
952
                && mouseXY[0] < (ca.width - prop['chart.gutter.right'])
953
                && mouseXY[1] > prop['chart.gutter.top']
954
                && mouseXY[1] < (ca.height - prop['chart.gutter.bottom'])
955
                ) {
956
 
957
                return this;
958
            }
959
        }
960
 
961
 
962
 
963
 
964
        /**
965
        * This function positions a tooltip when it is displayed
966
        *
967
        * @param obj object    The chart object
968
        * @param int x         The X coordinate specified for the tooltip
969
        * @param int y         The Y coordinate specified for the tooltip
970
        * @param objec tooltip The tooltips DIV element
971
        */
972
        this.positionTooltip = function (obj, x, y, tooltip, idx)
973
        {
974
            var coordX     = obj.coords[tooltip.__index__][0];
975
            var coordY     = obj.coords[tooltip.__index__][1];
976
            var coordW     = obj.coords[tooltip.__index__][2];
977
            var coordH     = obj.coords[tooltip.__index__][3];
978
            var canvasXY   = RG.getCanvasXY(obj.canvas);
979
            var gutterLeft = obj.Get('chart.gutter.left');
980
            var gutterTop  = obj.Get('chart.gutter.top');
981
            var width      = tooltip.offsetWidth;
982
            var height     = tooltip.offsetHeight;
983
 
984
            // Set the top position
985
            tooltip.style.left = 0;
986
            tooltip.style.top  = canvasXY[1] + coordY - height - 7 + 'px';
987
 
988
            // By default any overflow is hidden
989
            tooltip.style.overflow = '';
990
 
991
            // The arrow
992
            var img = new Image();
993
                img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
994
                img.style.position = 'absolute';
995
                img.id = '__rgraph_tooltip_pointer__';
996
                img.style.top = (tooltip.offsetHeight - 2) + 'px';
997
            tooltip.appendChild(img);
998
 
999
            // Reposition the tooltip if at the edges:
1000
 
1001
            // LEFT edge
1002
            if ((canvasXY[0] + coordX + (coordW / 2)- (width / 2)) < 0) {
1003
                tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + (coordW / 2) + 'px';
1004
                img.style.left = ((width * 0.1) - 8.5) + 'px';
1005
 
1006
            // RIGHT edge
1007
            } else if ((canvasXY[0] + coordX + width) > document.body.offsetWidth) {
1008
                tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + (coordW / 2) + 'px';
1009
                img.style.left = ((width * 0.9) - 8.5) + 'px';
1010
 
1011
            // Default positioning - CENTERED
1012
            } else {
1013
                tooltip.style.left = (canvasXY[0] + coordX + (coordW / 2) - (width * 0.5)) + 'px';
1014
                img.style.left = ((width * 0.5) - 8.5) + 'px';
1015
            }
1016
        }
1017
 
1018
 
1019
 
1020
 
1021
        /**
1022
        * Redraw the bar so that the shadow is NOT on top
1023
        */
1024
        this.RedrawBars = function ()
1025
        {
1026
            var coords = this.coords;
1027
            var len    = coords.length;
1028
 
1029
            // Reset the sequentail color index
1030
            this.sequentialColorIndex = 0;
1031
 
1032
            co.beginPath();
1033
 
1034
                // Turn off shadow
1035
                RG.NoShadow(this);
1036
 
1037
                // Set the stroke color
1038
                co.strokeStyle = prop['chart.strokestyle'];
1039
 
1040
                // Set the linewidth
1041
                co.lineWidth = prop['chart.linewidth'];
1042
 
1043
                for (var i=0; i<len; ++i) {
1044
 
1045
                    // No redrawing occurs if there is no value
1046
                    if (coords[i][2] > 0) {
1047
 
1048
                        if (prop['chart.colors.sequential']) {
1049
                            co.fillStyle = prop['chart.colors'][this.sequentialColorIndex++];
1050
                        } else {
1051
                            co.fillStyle = prop['chart.colors'][0];
1052
                        }
1053
 
1054
                        // Draw the bar itself
1055
                        co.strokeRect(coords[i][0], coords[i][1], coords[i][2], coords[i][3]);
1056
                        co.fillRect(coords[i][0], coords[i][1], coords[i][2], coords[i][3]);
1057
 
1058
                    } else {
1059
 
1060
                        // Even if there's no redrawing - the color index needs incrementing
1061
                        this.sequentialColorIndex++
1062
                    }
1063
                }
1064
            co.stroke();
1065
            co.fill();
1066
        }
1067
 
1068
 
1069
 
1070
 
1071
        /**
1072
        * Returns the X coords for a value. Returns two coords because there are... two scales.
1073
        *
1074
        * @param number value The value to get the coord for
1075
        */
1076
        this.getXCoord = function (value)
1077
        {
1078
            if (value > this.max || value < 0) {
1079
                return null;
1080
            }
1081
 
1082
            var ret = [];
1083
 
1084
            // The offset into the graph area
1085
            var offset = ((value / this.max) * this.axisWidth);
1086
 
1087
            // Get the coords (one fo each side)
1088
            ret[0] = (this.gutterLeft + this.axisWidth) - offset;
1089
            ret[1] = (ca.width - this.gutterRight - this.axisWidth) + offset;
1090
 
1091
            return ret;
1092
 
1093
        }
1094
 
1095
 
1096
 
1097
 
1098
        /**
1099
        * This allows for easy specification of gradients
1100
        */
1101
        this.parseColors = function ()
1102
        {
1103
            var props = this.properties;
1104
            var colors = props['chart.colors'];
1105
 
1106
            for (var i=0; i<colors.length; ++i) {
1107
                colors[i] = this.parseSingleColorForGradient(colors[i]);
1108
            }
1109
 
1110
            props['chart.highlight.stroke'] = this.parseSingleColorForGradient(props['chart.highlight.stroke']);
1111
            props['chart.highlight.fill']   = this.parseSingleColorForGradient(props['chart.highlight.fill']);
1112
            props['chart.axis.color']       = this.parseSingleColorForGradient(props['chart.axis.color']);
1113
            props['chart.strokestyle']      = this.parseSingleColorForGradient(props['chart.strokestyle']);
1114
        }
1115
 
1116
 
1117
 
1118
 
1119
        /**
1120
        * This parses a single color value
1121
        */
1122
        this.parseSingleColorForGradient = function (color)
1123
        {
1124
            if (!color || typeof(color) != 'string') {
1125
                return color;
1126
            }
1127
 
1128
            if (color.match(/^gradient\((.*)\)$/i)) {
1129
 
1130
                var parts = RegExp.$1.split(':');
1131
 
1132
                // Create the gradient
1133
                var grad = co.createLinearGradient(prop['chart.gutter.left'],0,ca.width - prop['chart.gutter.right'],0);
1134
 
1135
                var diff = 1 / (parts.length - 1);
1136
 
1137
                grad.addColorStop(0, RG.trim(parts[0]));
1138
 
1139
                for (var j=1; j<parts.length; ++j) {
1140
                    grad.addColorStop(j * diff, RG.trim(parts[j]));
1141
                }
1142
            }
1143
 
1144
            return grad ? grad : color;
1145
        }
1146
 
1147
 
1148
 
1149
 
1150
        /**
1151
        * Objects are now always registered so that when RGraph.Redraw()
1152
        * is called this chart will be redrawn.
1153
        */
1154
        RG.Register(this);
1155
    }