initial commit
[home-automation.git] / libraries / RGraph.tradar.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 traditional radar chart constructor
19     * 
20     * @param string id   The ID of the canvas
21     * @param array  data An array of data to represent
22     */
23     RGraph.Tradar = function (id, data)
24     {
25         this.id                = id;
26         this.canvas            = document.getElementById(id);
27         this.context           = this.canvas.getContext('2d');
28         this.canvas.__object__ = this;
29         this.size              = null;// Set in the .Draw() method
30         this.data              = data;
31         this.max               = RGraph.array_max(this.data);
32         this.type              = 'tradar';
33         this.coords            = [];
34         this.isRGraph          = true;
35
36
37         /**
38         * Compatibility with older browsers
39         */
40         RGraph.OldBrowserCompat(this.context);
41
42         
43         this.properties = {
44             'chart.gutter':                25,
45             'chart.linewidth':             1,
46             'chart.color':                 'red',
47             'chart.circle':                0,
48             'chart.circle.fill':           'red',
49             'chart.circle.stroke':         'black',
50             'chart.labels':                [],
51             'chart.labels.offsetx':        10,
52             'chart.labels.offsety':        10,
53             'chart.background.circles':    true,
54             'chart.text.size':             10,
55             'chart.text.font':             'Verdana',
56             'chart.text.color':            'black',
57             'chart.title':                 '',
58             'chart.title.background':      null,
59             'chart.title.hpos':            null,
60             'chart.title.vpos':            null,
61             'chart.title.color':           'black',
62             'chart.linewidth':             1,
63             'chart.key':                   null,
64             'chart.key.background':        'white',
65             'chart.key.position':          'gutter',
66             'chart.key.shadow':            false,
67             'chart.key.shadow.color':       '#666',
68             'chart.key.shadow.blur':        3,
69             'chart.key.shadow.offsetx':     2,
70             'chart.key.shadow.offsety':     2,
71             'chart.key':                    null,
72             'chart.key.background':         'white',
73             'chart.key.position':           'gutter',
74             'chart.key.shadow':             false,
75             'chart.key.shadow.color':       '#666',
76             'chart.key.shadow.blur':        3,
77             'chart.key.shadow.offsetx':     2,
78             'chart.key.shadow.offsety':     2,
79             'chart.key.position.gutter.boxed': true,
80             'chart.key.position.x':         null,
81             'chart.key.position.y':         null,
82             'chart.key.color.shape':        'square',
83             'chart.key.rounded':            true,
84             'chart.contextmenu':           null,
85             'chart.annotatable':           false,
86             'chart.annotate.color':        'black',
87             'chart.zoom.factor':           1.5,
88             'chart.zoom.fade.in':          true,
89             'chart.zoom.fade.out':         true,
90             'chart.zoom.hdir':             'right',
91             'chart.zoom.vdir':             'down',
92             'chart.zoom.frames':           10,
93             'chart.zoom.delay':            50,
94             'chart.zoom.shadow':           true,
95             'chart.zoom.mode':             'canvas',
96             'chart.zoom.thumbnail.width':  75,
97             'chart.zoom.thumbnail.height': 75,
98             'chart.zoom.background':        true,
99             'chart.zoom.action':            'zoom',
100             'chart.tooltips.effect':        'fade',
101             'chart.tooltips.css.class':      'RGraph_tooltip',
102             'chart.tooltips.highlight':     true,
103             'chart.resizable':              false,
104             'chart.labels.axes':            'nsew',
105             'chart.ymax':                   null
106         }
107         
108         // Must have at least 3 points
109         if (this.data.length < 3) {
110             alert('[TRADAR] You must specify at least 3 data points');
111             return;
112         }
113         
114         // Check the common library has been included
115         if (typeof(RGraph) == 'undefined') {
116             alert('[TRADAR] Fatal error: The RGraph common library does not appear to have been included');
117         }
118     }
119
120
121     /**
122     * A simple setter
123     * 
124     * @param string name  The name of the property to set
125     * @param string value The value of the property
126     */
127     RGraph.Tradar.prototype.Set = function (name, value)
128     {
129         this.properties[name] = value;
130
131         /**
132         * If the name is chart.color, set chart.colors too
133         */
134         if (name == 'chart.color') {
135             this.properties['chart.colors'] = [value];
136         }
137     }
138
139
140     /**
141     * A simple hetter
142     * 
143     * @param string name  The name of the property to get
144     */
145     RGraph.Tradar.prototype.Get = function (name)
146     {
147         return this.properties[name];
148     }
149
150
151     /**
152     * The draw method which does all the brunt of the work
153     */
154     RGraph.Tradar.prototype.Draw = function ()
155     {
156         /**
157         * Fire the onbeforedraw event
158         */
159         RGraph.FireCustomEvent(this, 'onbeforedraw');
160
161         /**
162         * Clear all of this canvases event handlers (the ones installed by RGraph)
163         */
164         RGraph.ClearEventListeners(this.id);
165
166         this.centerx  = this.canvas.width / 2;
167         this.centery  = this.canvas.height / 2;
168         this.size     = Math.min(this.canvas.width, this.canvas.height) - (2 * this.Get('chart.gutter'));
169     
170         // Work out the maximum value and the sum
171         if (!this.Get('chart.ymax')) {
172             this.scale = RGraph.getScale(RGraph.array_max(this.data), this);
173             this.max = this.scale[4];
174         } else {
175             var ymax = this.Get('chart.ymax');
176
177             this.scale = [
178                           ymax * 0.2,
179                           ymax * 0.4,
180                           ymax * 0.6,
181                           ymax * 0.8,
182                           ymax * 1
183                          ];
184             this.max = this.scale[4];
185         }
186
187         this.DrawBackground();
188         this.DrawAxes();
189         this.DrawCircle();
190         this.DrawAxisLabels();
191         this.DrawChart();
192         this.DrawLabels();
193         
194         // Draw the title
195         if (this.Get('chart.title')) {
196             RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'))
197         }
198
199         // Draw the key if necessary
200         // obj, key, colors
201         if (this.Get('chart.key')) {
202             RGraph.DrawKey(this, this.Get('chart.key'), [this.Get('chart.color'), this.Get('chart.circle.fill')]);
203         }
204
205         /**
206         * Show the context menu
207         */
208         if (this.Get('chart.contextmenu')) {
209             RGraph.ShowContext(this);
210         }
211
212         /**
213         * If the canvas is annotatable, do install the event handlers
214         */
215         if (this.Get('chart.annotatable')) {
216             RGraph.Annotate(this);
217         }
218
219         /**
220         * This bit shows the mini zoom window if requested
221         */
222         if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
223             RGraph.ShowZoomWindow(this);
224         }
225
226         
227         /**
228         * This function enables resizing
229         */
230         if (this.Get('chart.resizable')) {
231             RGraph.AllowResizing(this);
232         }
233
234
235         /**
236         * This function enables adjusting
237         */
238         if (this.Get('chart.adjustable')) {
239             RGraph.AllowAdjusting(this);
240         }
241         
242         /**
243         * Fire the RGraph ondraw event
244         */
245         RGraph.FireCustomEvent(this, 'ondraw');
246     }
247
248
249     /**
250     * Draws the background circles
251     */
252     RGraph.Tradar.prototype.DrawBackground = function ()
253     {
254         var color = '#ddd';
255
256         /**
257         * Draws the background circles
258         */
259         if (this.Get('chart.background.circles')) {
260
261            this.context.strokeStyle = color;
262            this.context.beginPath();
263
264            for (var r=5; r<(this.size / 2); r+=15) {
265
266                 this.context.moveTo(this.centerx, this.centery);
267                 this.context.arc(this.centerx, this.centery,r, 0, 6.28, 0);
268             }
269             
270             this.context.stroke();
271         }
272         
273         
274         /**
275         * Draw diagonals
276         */
277         this.context.strokeStyle = color;
278         for (var i=0; i<360; i+=15) {
279             this.context.beginPath();
280             this.context.arc(this.centerx, this.centery, this.size / 2, (i / 360) * (2 * Math.PI), ((i+0.01) / 360) * (2 * Math.PI), 0); // The 0.01 avoids a bug in Chrome 6
281             this.context.lineTo(this.centerx, this.centery);
282             this.context.stroke();
283         }
284     }
285
286
287     /**
288     * Draws the axes
289     */
290     RGraph.Tradar.prototype.DrawAxes = function ()
291     {
292         this.context.strokeStyle = 'black';
293
294         var halfsize = this.size / 2;
295
296         this.context.beginPath();
297
298         /**
299         * The Y axis
300         */
301             this.context.moveTo(this.centerx, this.centery + halfsize);
302             this.context.lineTo(this.centerx, this.centery - halfsize);
303             
304     
305             // Draw the bits at either end of the Y axis
306             this.context.moveTo(this.centerx - 5, this.centery + halfsize);
307             this.context.lineTo(this.centerx + 5, this.centery + halfsize);
308             this.context.moveTo(this.centerx - 5, this.centery - halfsize);
309             this.context.lineTo(this.centerx + 5, this.centery - halfsize);
310             
311             // Draw X axis tick marks
312             for (var y=(this.centery - halfsize); y<(this.centery + halfsize); y+=15) {
313                 this.context.moveTo(this.centerx - 3, y);
314                 this.context.lineTo(this.centerx + 3, y);
315             }
316
317         /**
318         * The X axis
319         */
320             this.context.moveTo(this.centerx - halfsize, this.centery);
321             this.context.lineTo(this.centerx + halfsize, this.centery);
322     
323             // Draw the bits at the end of the X axis
324             this.context.moveTo(this.centerx - halfsize, this.centery - 5);
325             this.context.lineTo(this.centerx - halfsize, this.centery + 5);
326             this.context.moveTo(this.centerx + halfsize, this.centery - 5);
327             this.context.lineTo(this.centerx + halfsize, this.centery + 5);
328
329             // Draw X axis tick marks
330             for (var x=(this.centerx - halfsize); x<(this.centerx + halfsize); x+=15) {
331                 this.context.moveTo(x, this.centery - 3);
332                 this.context.lineTo(x, this.centery + 3);
333             }
334
335         /**
336         * Finally draw it to the canvas
337         */
338         this.context.stroke();
339     }
340
341
342     /**
343     * The function which actually draws the radar chart
344     */
345     RGraph.Tradar.prototype.DrawChart = function ()
346     {
347         for (var i=0; i<this.data.length; ++i) {
348             this.coords[i] = this.GetCoordinates(i);
349         }
350
351         /**
352         * Now go through the coords and draw the chart itself
353         */
354         this.context.strokeStyle = this.Get('chart.strokestyle');
355         this.context.fillStyle = this.Get('chart.color');
356         this.context.lineWidth = this.Get('chart.linewidth');
357         this.context.beginPath();
358
359         for (i=0; i<this.coords.length; ++i) {
360             if (i == 0) {
361                 this.context.moveTo(this.coords[i][0], this.coords[i][1]);
362             } else {
363                 this.context.lineTo(this.coords[i][0], this.coords[i][1]);
364             }
365         }
366         
367         this.context.closePath();
368
369         this.context.fill();
370         this.context.stroke();
371         
372         /**
373         * Can now handletooltips
374         */
375         if (this.Get('chart.tooltips')) {
376             
377             RGraph.Register(this);
378             
379             var canvas_onmousemove_func = function (e)
380             {
381                 e = RGraph.FixEventObject(e);
382                 
383                 var canvas      = document.getElementById(this.id);
384                 var obj         = canvas.__object__;
385                 var x           = e.offsetX;
386                 var y           = e.offsetY;
387                 var overHotspot = false;
388
389                 for (var i=0; i<obj.coords.length; ++i) {
390                 
391                     var xCoord   = obj.coords[i][0];
392                     var yCoord   = obj.coords[i][1];
393                     var tooltips = obj.Get('chart.tooltips');
394                     var idx      = Number(i);
395
396                     if (
397                         (tooltips[i] || tooltips) // The order here is important due to short circuiting
398                         && x < (xCoord + 5)
399                         && x > (xCoord - 5)
400                         && y > (yCoord - 5)
401                         && y < (yCoord + 5)
402                        ) {
403
404                         if (!RGraph.Registry.Get('chart.tooltip') || RGraph.Registry.Get('chart.tooltip').__index__ != idx) {
405
406                             /**
407                             * Get the tooltip text
408                             */
409                             if (typeof(obj.Get('chart.tooltips')) == 'function') {
410                                 var text = String(obj.Get('chart.tooltips')(i));
411
412                             } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[i]) == 'function') {
413                                 var text = String(obj.Get('chart.tooltips')[i](i));
414                             
415                             } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[i]) == 'string') {
416                                 var text = String(obj.Get('chart.tooltips')[i]);
417
418                             } else {
419                                 var text = null;
420                             }
421
422                             if (typeof(text) == 'string' && text.length) {
423                        
424                                 overHotspot = true;
425                                 obj.canvas.style.cursor = 'pointer';
426
427                                 RGraph.Clear(obj.canvas);
428                                 obj.Draw();
429                                 
430                                 if (obj.Get('chart.tooltips.highlight')) {
431                                     obj.context.beginPath();
432                                     obj.context.strokeStyle = 'gray';
433                                     obj.context.fillStyle   = 'white';
434                                     obj.context.arc(xCoord, yCoord, 2, 0, 6.28, 0);
435                                     obj.context.fill();
436                                     obj.context.stroke();
437                                 }
438                                 
439                                 RGraph.Tooltip(obj.canvas, text, e.pageX, e.pageY, idx);
440                             }
441                         } else if (RGraph.Registry.Get('chart.tooltip') && RGraph.Registry.Get('chart.tooltip').__index__ == idx) {
442                             overHotspot = true;
443                             obj.canvas.style.cursor = 'pointer';
444                         }
445                     }
446                 }
447
448                 if (!overHotspot) {
449                     obj.canvas.style.cursor = 'default';
450                 }
451             }
452             this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
453             RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
454         }
455     }
456
457
458     /**
459     * Gets the coordinates for a particular mark
460     * 
461     * @param  number i The index of the data (ie which one it is)
462     * @return array    A two element array of the coordinates
463     */
464     RGraph.Tradar.prototype.GetCoordinates = function (i)
465     {
466         // The number  of data points
467         var len = this.data.length;
468
469         // The magnitude of the data (NOT the x/y coords)
470         var mag = (this.data[i] / this.max) * (this.size / 2);
471
472         /**
473         * Get the angle
474         */
475         var angle = (6.28 / len) * i; // In radians
476
477         /**
478         * Work out the X/Y coordinates
479         */
480         var x = Math.cos(angle) * mag;
481         var y = Math.sin(angle) * mag;
482
483         /**
484         * Put the coordinate in the right quadrant
485         */
486         x = this.centerx + x;
487         y = this.centery + (i == 0 ? 0 : y);
488         
489         return [x,y];
490     }
491     
492     
493     /**
494     * This function adds the labels to the chart
495     */
496     RGraph.Tradar.prototype.DrawLabels = function ()
497     {
498         var labels = this.Get('chart.labels');
499
500         if (labels && labels.length > 0) {
501
502             this.context.lineWidth = 1;
503             this.context.fillStyle = this.Get('chart.text.color');
504             
505             var offsetx = this.Get('chart.labels.offsetx'); // Not used yet
506             var offsety = this.Get('chart.labels.offsety'); // Not used yet
507
508             for (var i=0; i<labels.length; ++i) {
509
510                 var x        = this.coords[i][0];
511                 var y        = this.coords[i][1];
512                 var text     = labels[i];
513                 var hAlign   = 'center';
514                 var vAlign   = 'center';
515                 var quartile = (i / this.coords.length);
516                 var offsetx  = this.Get('chart.labels.offsetx');
517                 var offsety  = this.Get('chart.labels.offsety');
518
519                 // ~Manually do labels on the right middle axis
520                 if (i == 0) {
521                     hAlign = 'left';
522                     vAlign = 'center';
523                     x += offsetx;
524
525                 } else {
526
527                     hAlign = (x < this.centerx) ? 'right' : 'left';
528                     vAlign = (y < this.centery) ? 'bottom' : 'top';
529                     x     += (x < this.centerx) ? (-1 * offsetx) : offsetx;
530                     y     += (y < this.centery) ? (-1 * offsety) : offsety;
531                     
532                     if (i / this.data.length == 0.25) { x -= offsetx; hAlign = 'center';
533                     } else if (i / this.data.length == 0.5) { y -= offsety; vAlign = 'center';
534                     } else if (i / this.data.length == 0.75) { x += offsetx; hAlign = 'center'; }
535                 }
536
537                 // context, font, size, x, y, text
538                 RGraph.Text(this.context, this.Get('chart.text.font'), this.Get('chart.text.size'), x, y, text, vAlign, hAlign, true, null, 'white');
539             }
540         }
541     }
542
543
544     /**
545     * Draws the circle. No arguments as it gets the information from the object properties.
546     */
547     RGraph.Tradar.prototype.DrawCircle = function ()
548     {
549         var circle   = {};
550         circle.limit = this.Get('chart.circle');
551         circle.fill  = this.Get('chart.circle.fill');
552         circle.stroke  = this.Get('chart.circle.stroke');
553
554         if (circle.limit) {
555
556             var r = (circle.limit / this.max) * (this.size / 2);
557             
558             this.context.fillStyle = circle.fill;
559             this.context.strokeStyle = circle.stroke;
560
561             this.context.beginPath();
562             this.context.arc(this.centerx, this.centery, r, 0, 6.28, 0);
563             this.context.fill();
564             this.context.stroke();
565         }
566     }
567
568
569     /**
570     * Unsuprisingly, draws the labels
571     */
572     RGraph.Tradar.prototype.DrawAxisLabels = function ()
573     {
574         this.context.lineWidth = 1;
575         
576         // Set the color to black
577         this.context.fillStyle = 'black';
578         this.context.strokeStyle = 'black';
579
580         var r         = (this.size/ 2);
581         var font_face = this.Get('chart.text.font');
582         var font_size = this.Get('chart.text.size');
583         var context   = this.context;
584         var axes      = this.Get('chart.labels.axes').toLowerCase();
585         var color     = 'rgba(255,255,255,0.8)';
586
587         // The "North" axis labels
588         if (axes.indexOf('n') > -1) {
589             RGraph.Text(context,font_face,font_size,this.centerx,this.centery - (r * 0.2),String(this.scale[0]),'center','center',true,false,color);
590             RGraph.Text(context, font_face, font_size, this.centerx, this.centery - (r * 0.4), String(this.scale[1]), 'center', 'center', true, false, color);
591             RGraph.Text(context, font_face, font_size, this.centerx, this.centery - (r * 0.6), String(this.scale[2]), 'center', 'center', true, false, color);
592             RGraph.Text(context, font_face, font_size, this.centerx, this.centery - (r * 0.8), String(this.scale[3]), 'center', 'center', true, false, color);
593             RGraph.Text(context, font_face, font_size, this.centerx, this.centery - r, String(this.scale[4]), 'center', 'center', true, false, color);
594         }
595
596         // The "South" axis labels
597         if (axes.indexOf('s') > -1) {
598             RGraph.Text(context, font_face, font_size, this.centerx, this.centery + (r * 0.2), String(this.scale[0]), 'center', 'center', true, false, color);
599             RGraph.Text(context, font_face, font_size, this.centerx, this.centery + (r * 0.4), String(this.scale[1]), 'center', 'center', true, false, color);
600             RGraph.Text(context, font_face, font_size, this.centerx, this.centery + (r * 0.6), String(this.scale[2]), 'center', 'center', true, false, color);
601             RGraph.Text(context, font_face, font_size, this.centerx, this.centery + (r * 0.8), String(this.scale[3]), 'center', 'center', true, false, color);
602             RGraph.Text(context, font_face, font_size, this.centerx, this.centery + r, String(this.scale[4]), 'center', 'center', true, false, color);
603         }
604         
605         // The "East" axis labels
606         if (axes.indexOf('e') > -1) {
607             RGraph.Text(context, font_face, font_size, this.centerx + (r * 0.2), this.centery, String(this.scale[0]), 'center', 'center', true, false, color);
608             RGraph.Text(context, font_face, font_size, this.centerx + (r * 0.4), this.centery, String(this.scale[1]), 'center', 'center', true, false, color);
609             RGraph.Text(context, font_face, font_size, this.centerx + (r * 0.6), this.centery, String(this.scale[2]), 'center', 'center', true, false, color);
610             RGraph.Text(context, font_face, font_size, this.centerx + (r * 0.8), this.centery, String(this.scale[3]), 'center', 'center', true, false, color);
611             RGraph.Text(context, font_face, font_size, this.centerx + r, this.centery, String(this.scale[4]), 'center', 'center', true, false, color);
612         }
613
614         // The "West" axis labels
615         if (axes.indexOf('w') > -1) {
616             RGraph.Text(context, font_face, font_size, this.centerx - (r * 0.2), this.centery, String(this.scale[0]), 'center', 'center', true, false, color);
617             RGraph.Text(context, font_face, font_size, this.centerx - (r * 0.4), this.centery, String(this.scale[1]), 'center', 'center', true, false, color);
618             RGraph.Text(context, font_face, font_size, this.centerx - (r * 0.6), this.centery, String(this.scale[2]), 'center', 'center', true, false, color);
619             RGraph.Text(context, font_face, font_size, this.centerx - (r * 0.8), this.centery, String(this.scale[3]), 'center', 'center', true, false, color);
620             RGraph.Text(context, font_face, font_size, this.centerx - r, this.centery, String(this.scale[4]), 'center', 'center', true, false, color);
621         }
622
623         RGraph.Text(context, font_face, font_size, this.centerx,  this.centery, '0', 'center', 'center', true, false, color);
624     }