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