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