initial commit
[home-automation.git] / libraries / RGraph.pie.js
1     /**
2     * o------------------------------------------------------------------------------o
3     * | This file is part of the RGraph package - you can learn more at:             |
4     * |                                                                              |
5     * |                          http://www.rgraph.net                               |
6     * |                                                                              |
7     * | This package is licensed under the RGraph license. For all kinds of business |
8     * | purposes there is a small one-time licensing fee to pay and for non          |
9     * | commercial  purposes it is free to use. You can read the full license here:  |
10     * |                                                                              |
11     * |                      http://www.rgraph.net/LICENSE.txt                       |
12     * o------------------------------------------------------------------------------o
13     */
14     
15     if (typeof(RGraph) == 'undefined') RGraph = {};
16
17     /**
18     * The pie chart constructor
19     * 
20     * @param data array The data to be represented on the pie chart
21     */
22     RGraph.Pie = function (id, data)
23     {
24         this.id                = id;
25         this.canvas            = document.getElementById(id);
26         this.context           = this.canvas.getContext("2d");
27         this.canvas.__object__ = this;
28         this.total             = 0;
29         this.subTotal          = 0;
30         this.angles            = [];
31         this.data              = data;
32         this.properties        = [];
33         this.type              = 'pie';
34         this.isRGraph          = true;
35
36
37         /**
38         * Compatibility with older browsers
39         */
40         RGraph.OldBrowserCompat(this.context);
41
42         this.properties = {
43             'chart.colors':                 ['rgb(255,0,0)', '#ddd', 'rgb(0,255,0)', 'rgb(0,0,255)', 'pink', 'yellow', 'red', 'rgb(0,255,255)', 'black', 'white'],
44             'chart.strokestyle':            '#999',
45             'chart.linewidth':              1,
46             'chart.labels':                 [],
47             'chart.labels.sticks':          false,
48             'chart.labels.sticks.color':    '#aaa',
49             'chart.segments':               [],
50             'chart.gutter':                 25,
51             'chart.title':                  '',
52             'chart.title.background':       null,
53             'chart.title.hpos':             null,
54             'chart.title.vpos':             null,
55             'chart.shadow':                 false,
56             'chart.shadow.color':           'rgba(0,0,0,0.5)',
57             'chart.shadow.offsetx':         3,
58             'chart.shadow.offsety':         3,
59             'chart.shadow.blur':            3,
60             'chart.text.size':              10,
61             'chart.text.color':             'black',
62             'chart.text.font':              'Verdana',
63             'chart.contextmenu':            null,
64             'chart.tooltips':               [],
65             'chart.tooltips.event':         'onclick',
66             'chart.tooltips.effect':        'fade',
67             'chart.tooltips.css.class':     'RGraph_tooltip',
68             'chart.tooltips.highlight':     true,
69             'chart.radius':                 null,
70             'chart.highlight.style':        '3d',
71             'chart.highlight.style.2d.color': 'rgba(255,255,255,0.5)',
72             'chart.border':                 false,
73             'chart.border.color':           'rgba(255,255,255,0.5)',
74             'chart.key':                    null,
75             'chart.key.background':         'white',
76             'chart.key.position':           'graph',
77             'chart.key.shadow':             false,
78             'chart.key.shadow.color':       '#666',
79             'chart.key.shadow.blur':        3,
80             'chart.key.shadow.offsetx':     2,
81             'chart.key.shadow.offsety':     2,
82             'chart.key.position.gutter.boxed': true,
83             'chart.key.position.x':         null,
84             'chart.key.position.y':         null,
85             'chart.key.color.shape':        'square',
86             'chart.key.rounded':            true,
87             'chart.annotatable':            false,
88             'chart.annotate.color':         'black',
89             'chart.align':                  'center',
90             'chart.zoom.factor':            1.5,
91             'chart.zoom.fade.in':           true,
92             'chart.zoom.fade.out':          true,
93             'chart.zoom.hdir':              'right',
94             'chart.zoom.vdir':              'down',
95             'chart.zoom.frames':            10,
96             'chart.zoom.delay':             50,
97             'chart.zoom.shadow':            true,
98             'chart.zoom.mode':              'canvas',
99             'chart.zoom.thumbnail.width':   75,
100             'chart.zoom.thumbnail.height':  75,
101             'chart.zoom.background':        true,
102             'chart.zoom.action':            'zoom',
103             'chart.resizable':              false,
104             'chart.variant':                'pie'
105         }
106         
107         /**
108         * Calculate the total
109         */
110         for (var i=0,len=data.length; i<len; i++) {
111             this.total += data[i];
112         }
113         
114         // Check the common library has been included
115         if (typeof(RGraph) == 'undefined') {
116             alert('[PIE] Fatal error: The common library does not appear to have been included');
117         }
118     }
119
120
121     /**
122     * A generic setter
123     */
124     RGraph.Pie.prototype.Set = function (name, value)
125     {
126         this.properties[name] = value;
127     }
128
129
130     /**
131     * A generic getter
132     */
133     RGraph.Pie.prototype.Get = function (name)
134     {
135         return this.properties[name];
136     }
137
138
139     /**
140     * This draws the pie chart
141     */
142     RGraph.Pie.prototype.Draw = function ()
143     {
144         /**
145         * Fire the onbeforedraw event
146         */
147         RGraph.FireCustomEvent(this, 'onbeforedraw');
148
149
150         /**
151         * Clear all of this canvases event handlers (the ones installed by RGraph)
152         */
153         RGraph.ClearEventListeners(this.id);
154
155
156         this.diameter    = Math.min(this.canvas.height, this.canvas.width) - (2 * this.Get('chart.gutter'));
157         this.radius      = this.Get('chart.radius') ? this.Get('chart.radius') : this.diameter / 2;
158         // this.centerx now defined below
159         this.centery     = this.canvas.height / 2;
160         this.subTotal    = 0;
161         this.angles      = [];
162         
163         /**
164         * Alignment (Pie is center aligned by default) Only if centerx is not defined - donut defines the centerx
165         */
166         if (this.Get('chart.align') == 'left') {
167             this.centerx = this.radius + this.Get('chart.gutter');
168         
169         } else if (this.Get('chart.align') == 'right') {
170             this.centerx = this.canvas.width - (this.radius + this.Get('chart.gutter'));
171         
172         } else {
173             this.centerx = this.canvas.width / 2;
174         }
175
176         /**
177         * Draw the shadow if required
178         */
179         if (this.Get('chart.shadow')) {
180         
181             var offsetx = document.all ? this.Get('chart.shadow.offsetx') : 0;
182             var offsety = document.all ? this.Get('chart.shadow.offsety') : 0;
183
184             this.context.beginPath();
185             this.context.fillStyle = this.Get('chart.shadow.color');
186
187             this.context.shadowColor   = this.Get('chart.shadow.color');
188             this.context.shadowBlur    = this.Get('chart.shadow.blur');
189             this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
190             this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
191             
192             this.context.arc(this.centerx + offsetx, this.centery + offsety, this.radius, 0, 6.28, 0);
193             
194             this.context.fill();
195             
196             // Now turn off the shadow
197             RGraph.NoShadow(this);
198         }
199
200         /**
201         * The total of the array of values
202         */
203         this.total = RGraph.array_sum(this.data);
204
205         for (var i=0,len=this.data.length; i<len; i++) {
206             var angle = (this.data[i] / this.total) * 360;
207     
208             this.DrawSegment(angle,
209                              this.Get('chart.colors')[i],
210                              i == (this.data.length - 1));
211         }
212
213         /**
214         * Redraw the seperating lines
215         */
216         if (this.Get('chart.linewidth') > 0) {
217             this.context.beginPath();
218             this.context.lineWidth = this.Get('chart.linewidth');
219             this.context.strokeStyle = this.Get('chart.strokestyle');
220
221             for (var i=0,len=this.angles.length; i<len; ++i) {
222                 this.context.moveTo(this.centerx, this.centery);
223                 this.context.arc(this.centerx, this.centery, this.radius, this.angles[i][0] / 57.3, (this.angles[i][0] + 0.01) / 57.3, 0);
224             }
225             
226             this.context.stroke();
227             
228             /**
229             * And finally redraw the border
230             */
231             this.context.beginPath();
232             this.context.moveTo(this.centerx, this.centery);
233             this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, 0);
234             this.context.stroke();
235         }
236
237         /**
238         * Draw label sticks
239         */
240         if (this.Get('chart.labels.sticks')) {
241             this.DrawSticks();
242             
243             // Redraw the border going around the Pie chart if the stroke style is NOT white
244             if (
245                    this.Get('chart.strokestyle') != 'white'
246                 && this.Get('chart.strokestyle') != '#fff'
247                 && this.Get('chart.strokestyle') != '#fffffff'
248                 && this.Get('chart.strokestyle') != 'rgb(255,255,255)'
249                 && this.Get('chart.strokestyle') != 'rgba(255,255,255,0)'
250                ) {
251
252                 this.context.beginPath();
253                     this.context.strokeStyle = this.Get('chart.strokestyle');
254                     this.context.lineWidth = this.Get('chart.linewidth');
255                     this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, false);
256                 this.context.stroke();
257             }
258         }
259
260         /**
261         * Draw the labels
262         */
263         this.DrawLabels();
264
265         /**
266         * Draw the title
267         */
268         if (this.Get('chart.align') == 'left') {
269             var centerx = this.radius + this.Get('chart.gutter');
270
271         } else if (this.Get('chart.align') == 'right') {
272             var centerx = this.canvas.width - (this.radius + this.Get('chart.gutter'));
273
274         } else {
275             var centerx = null;
276         }
277
278         RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'), centerx, this.Get('chart.text.size') + 2);
279         
280         
281         /**
282         * Setup the context menu if required
283         */
284         if (this.Get('chart.contextmenu')) {
285             RGraph.ShowContext(this);
286         }
287
288         /**
289         * Tooltips
290         */
291         if (this.Get('chart.tooltips').length) {
292
293             /**
294             * Register this object for redrawing
295             */
296             RGraph.Register(this);
297         
298             /**
299             * The onclick event
300             */
301             //this.canvas.onclick = function (e)
302             var canvas_onclick_func = function (e)
303             {
304                 RGraph.HideZoomedCanvas();
305
306                 e = RGraph.FixEventObject(e);
307
308                 var mouseCoords = RGraph.getMouseXY(e);
309
310                 var canvas  = e.target;
311                 var context = canvas.getContext('2d');
312                 var obj     = e.target.__object__;
313
314                 var x       = mouseCoords[0] - obj.centerx;
315                 var y       = mouseCoords[1] - obj.centery;
316                 var theta   = Math.atan(y / x); // RADIANS
317                 var hyp     = y / Math.sin(theta);
318
319
320
321                 /**
322                 * If it's actually a donut make sure the hyp is bigger
323                 * than the size of the hole in the middle
324                 */
325                 if (obj.Get('chart.variant') == 'donut' && Math.abs(hyp) < (obj.radius / 2)) {
326                     return;
327                 }
328
329                 /**
330                 * The angles for each segment are stored in "angles",
331                 * so go through that checking if the mouse position corresponds
332                 */
333                 var isDonut = obj.Get('chart.variant') == 'donut';
334                 var hStyle  = obj.Get('chart.highlight.style');
335                 var segment = RGraph.getSegment(e);
336
337                 if (segment) {
338                 
339
340                     if (RGraph.Registry.Get('chart.tooltip') && segment[5] == RGraph.Registry.Get('chart.tooltip').__index__) {
341                         return;
342                     } else {
343                         RGraph.Redraw();
344                     }
345
346                     if (isDonut || hStyle == '2d') {
347                         
348                         context.beginPath();
349
350                         context.fillStyle = obj.Get('chart.highlight.style.2d.color');
351
352                         context.moveTo(obj.centerx, obj.centery);
353                         context.arc(obj.centerx, obj.centery, obj.radius, RGraph.degrees2Radians(obj.angles[segment[5]][0]), RGraph.degrees2Radians(obj.angles[segment[5]][1]), 0);
354                         context.lineTo(obj.centerx, obj.centery);
355                         context.closePath();
356                         
357                         context.fill();
358                         
359                         //Removed 7th December 2010
360                         //context.stroke();
361
362                     } else {
363
364                         context.lineWidth = 2;
365
366                         /**
367                         * Draw a white segment where the one that has been clicked on was
368                         */
369                         context.fillStyle = 'white';
370                         context.strokeStyle = 'white';
371                         context.beginPath();
372                         context.moveTo(obj.centerx, obj.centery);
373                         context.arc(obj.centerx, obj.centery, obj.radius, obj.angles[segment[5]][0] / 57.3, obj.angles[segment[5]][1] / 57.3, 0);
374                         context.stroke();
375                         context.fill();
376                         
377                         context.lineWidth = 1;
378
379                         context.shadowColor   = '#666';
380                         context.shadowBlur    = 3;
381                         context.shadowOffsetX = 3;
382                         context.shadowOffsetY = 3;
383
384                         // Draw the new segment
385                         context.beginPath();
386                             context.fillStyle   = obj.Get('chart.colors')[segment[5]];
387                             context.strokeStyle = 'rgba(0,0,0,0)';
388                             context.moveTo(obj.centerx - 3, obj.centery - 3);
389                             context.arc(obj.centerx - 3, obj.centery - 3, obj.radius, RGraph.degrees2Radians(obj.angles[segment[5]][0]), RGraph.degrees2Radians(obj.angles[segment[5]][1]), 0);
390                             context.lineTo(obj.centerx - 3, obj.centery - 3);
391                         context.closePath();
392                         
393                         context.stroke();
394                         context.fill();
395                         
396                         // Turn off the shadow
397                         RGraph.NoShadow(obj);
398                         
399                         /**
400                         * If a border is defined, redraw that
401                         */
402                         if (obj.Get('chart.border')) {
403                             context.beginPath();
404                             context.strokeStyle = obj.Get('chart.border.color');
405                             context.lineWidth = 5;
406                             context.arc(obj.centerx - 3, obj.centery - 3, obj.radius - 2, RGraph.degrees2Radians(obj.angles[i][0]), RGraph.degrees2Radians(obj.angles[i][1]), 0);
407                             context.stroke();
408                         }
409                     }
410                         
411                     /**
412                     * If a tooltip is defined, show it
413                     */
414
415                     /**
416                     * Get the tooltip text
417                     */
418                     if (typeof(obj.Get('chart.tooltips')) == 'function') {
419                         var text = String(obj.Get('chart.tooltips')(segment[5]));
420
421                     } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[segment[5]]) == 'function') {
422                         var text = String(obj.Get('chart.tooltips')[segment[5]](segment[5]));
423                     
424                     } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
425                         var text = String(obj.Get('chart.tooltips')[segment[5]]);
426
427                     } else {
428                         var text = '';
429                     }
430
431                     if (text) {
432                         RGraph.Tooltip(canvas, text, e.pageX, e.pageY, segment[5]);
433                     }
434
435                     /**
436                     * Need to redraw the key?
437                     */
438                     if (obj.Get('chart.key') && obj.Get('chart.key').length && obj.Get('chart.key.position') == 'graph') {
439                         RGraph.DrawKey(obj, obj.Get('chart.key'), obj.Get('chart.colors'));
440                     }
441
442                     e.stopPropagation();
443
444                     return;
445                 }
446             }
447             var event_name = this.Get('chart.tooltips.event') == 'onmousemove' ? 'mousemove' : 'click';
448
449             this.canvas.addEventListener(event_name, canvas_onclick_func, false);
450             RGraph.AddEventListener(this.id, event_name, canvas_onclick_func);
451
452
453
454
455
456
457
458
459             /**
460             * The onmousemove event for changing the cursor
461             */
462             //this.canvas.onmousemove = function (e)
463             var canvas_onmousemove_func = function (e)
464             {
465                 RGraph.HideZoomedCanvas();
466
467                 e = RGraph.FixEventObject(e);
468                 
469                 var segment = RGraph.getSegment(e);
470
471                 if (segment) {
472                     e.target.style.cursor = 'pointer';
473
474                     return;
475                 }
476
477                 /**
478                 * Put the cursor back to null
479                 */
480                 e.target.style.cursor = 'default';
481             }
482             this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
483             RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
484         }
485
486
487         /**
488         * If a border is pecified, draw it
489         */
490         if (this.Get('chart.border')) {
491             this.context.beginPath();
492             this.context.lineWidth = 5;
493             this.context.strokeStyle = this.Get('chart.border.color');
494
495             this.context.arc(this.centerx,
496                              this.centery,
497                              this.radius - 2,
498                              0,
499                              6.28,
500                              0);
501
502             this.context.stroke();
503         }
504         
505         /**
506         * Draw the kay if desired
507         */
508         if (this.Get('chart.key') != null) {
509             //this.Set('chart.key.position', 'graph');
510             RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
511         }
512
513
514         /**
515         * If this is actually a donut, draw a big circle in the middle
516         */
517         if (this.Get('chart.variant') == 'donut') {
518             this.context.beginPath();
519             this.context.strokeStyle = this.Get('chart.strokestyle');
520             this.context.fillStyle   = 'white';//this.Get('chart.fillstyle');
521             this.context.arc(this.centerx, this.centery, this.radius / 2, 0, 6.28, 0);
522             this.context.stroke();
523             this.context.fill();
524         }
525         
526         RGraph.NoShadow(this);
527         
528         /**
529         * If the canvas is annotatable, do install the event handlers
530         */
531         if (this.Get('chart.annotatable')) {
532             RGraph.Annotate(this);
533         }
534         
535         /**
536         * This bit shows the mini zoom window if requested
537         */
538         if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
539             RGraph.ShowZoomWindow(this);
540         }
541
542         
543         /**
544         * This function enables resizing
545         */
546         if (this.Get('chart.resizable')) {
547             RGraph.AllowResizing(this);
548         }
549         
550         /**
551         * Fire the RGraph ondraw event
552         */
553         RGraph.FireCustomEvent(this, 'ondraw');
554     }
555
556
557     /**
558     * Draws a single segment of the pie chart
559     * 
560     * @param int degrees The number of degrees for this segment
561     */
562     RGraph.Pie.prototype.DrawSegment = function (degrees, color, last)
563     {
564         var context  = this.context;
565         var canvas   = this.canvas;
566         var subTotal = this.subTotal;
567
568         context.beginPath();
569
570             context.fillStyle   = color;
571             context.strokeStyle = this.Get('chart.strokestyle');
572             context.lineWidth   = 0;
573
574             context.arc(this.centerx,
575                         this.centery,
576                         this.radius,
577                         subTotal / 57.3,
578                         (last ? 360 : subTotal + degrees) / 57.3,
579                         0);
580     
581             context.lineTo(this.centerx, this.centery);
582             
583             // Keep hold of the angles
584             this.angles.push([subTotal, subTotal + degrees])
585         this.context.closePath();
586
587         this.context.fill();
588     
589         /**
590         * Calculate the segment angle
591         */
592         this.Get('chart.segments').push([subTotal, subTotal + degrees]);
593         this.subTotal += degrees;
594     }
595
596     /**
597     * Draws the graphs labels
598     */
599     RGraph.Pie.prototype.DrawLabels = function ()
600     {
601         var hAlignment = 'left';
602         var vAlignment = 'center';
603         var labels     = this.Get('chart.labels');
604         var context    = this.context;
605
606         /**
607         * Turn the shadow off
608         */
609         RGraph.NoShadow(this);
610         
611         context.fillStyle = 'black';
612         context.beginPath();
613
614         /**
615         * Draw the key (ie. the labels)
616         */
617         if (labels && labels.length) {
618
619             var text_size = this.Get('chart.text.size');
620
621             for (i=0; i<labels.length; ++i) {
622             
623                 /**
624                 * T|his ensures that if we're given too many labels, that we don't get an error
625                 */
626                 if (typeof(this.Get('chart.segments')[i]) == 'undefined') {
627                     continue;
628                 }
629
630                 // Move to the centre
631                 context.moveTo(this.centerx,this.centery);
632                 
633                 var a = this.Get('chart.segments')[i][0] + ((this.Get('chart.segments')[i][1] - this.Get('chart.segments')[i][0]) / 2);
634
635                 /**
636                 * Alignment
637                 */
638                 if (a < 90) {
639                     hAlignment = 'left';
640                     vAlignment = 'center';
641                 } else if (a < 180) {
642                     hAlignment = 'right';
643                     vAlignment = 'center';
644                 } else if (a < 270) {
645                     hAlignment = 'right';
646                     vAlignment = 'center';
647                 } else if (a < 360) {
648                     hAlignment = 'left';
649                     vAlignment = 'center';
650                 }
651
652                 context.fillStyle = this.Get('chart.text.color');
653
654                 RGraph.Text(context,
655                             this.Get('chart.text.font'),
656                             text_size,
657                             this.centerx + ((this.radius + 10)* Math.cos(a / 57.3)) + (this.Get('chart.labels.sticks') ? (a < 90 || a > 270 ? 2 : -2) : 0),
658                             this.centery + (((this.radius + 10) * Math.sin(a / 57.3))),
659                             labels[i],
660                             vAlignment,
661                             hAlignment);
662             }
663             
664             context.fill();
665         }
666     }
667
668
669     /**
670     * This function draws the pie chart sticks (for the labels)
671     */
672     RGraph.Pie.prototype.DrawSticks = function ()
673     {
674         var context  = this.context;
675         var segments = this.Get('chart.segments');
676         var offset   = this.Get('chart.linewidth') / 2;
677
678         for (var i=0; i<segments.length; ++i) {
679
680             var degrees = segments[i][1] - segments[i][0];
681
682             context.beginPath();
683             context.strokeStyle = this.Get('chart.labels.sticks.color');
684             context.lineWidth   = 1;
685
686             var midpoint = (segments[i][0] + (degrees / 2)) / 57.3;
687
688             context.arc(this.centerx,
689                         this.centery,
690                         this.radius + 7,
691                         midpoint,
692                         midpoint + 0.01,
693                         0);
694             
695             
696             context.arc(this.centerx,
697                         this.centery,
698                         this.radius - offset,
699                         midpoint,
700                         midpoint + 0.01,
701                         0);
702
703             context.stroke();
704         }
705     }