initial commit
[home-automation.git] / libraries / RGraph.funnel.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 bar chart constructor
19     * 
20     * @param object canvas The canvas object
21     * @param array  data   The chart data
22     */
23     RGraph.Funnel = function (id, data)
24     {
25         // Get the canvas and context objects
26         this.id                = id;
27         this.canvas            = document.getElementById(id);
28         this.context           = this.canvas.getContext ? this.canvas.getContext("2d") : null;
29         this.canvas.__object__ = this;
30         this.type              = 'funnel';
31         this.coords            = [];
32         this.isRGraph          = true;
33
34
35         /**
36         * Compatibility with older browsers
37         */
38         RGraph.OldBrowserCompat(this.context);
39
40
41         // Check for support
42         if (!this.canvas) {
43             alert('[FUNNEL] No canvas support');
44             return;
45         }
46         
47         // Check the common library has been included
48         if (typeof(RGraph) == 'undefined') {
49             alert('[FUNNEL] Fatal error: The common library does not appear to have been included');
50         }
51         
52         /**
53         * The funnel charts properties
54         */
55         this.properties = {
56             'chart.strokestyle':           'black',
57             'chart.gutter':                25,
58             'chart.labels':                null,
59             'chart.title':                 '',
60             'chart.title.background':       null,
61             'chart.title.hpos':             null,
62             'chart.title.vpos':            null,
63             'chart.colors':                ['red', 'green', 'gray', 'blue', 'black', 'gray'],
64             'chart.text.size':             10,
65             'chart.text.boxed':            true,
66             'chart.text.halign':           'left',
67             'chart.text.color':            'black',
68             'chart.text.font':             'Verdana',
69             'chart.contextmenu':           null,
70             'chart.shadow':                false,
71             'chart.shadow.color':          '#666',
72             'chart.shadow.blur':           3,
73             'chart.shadow.offsetx':        3,
74             'chart.shadow.offsety':        3,
75
76
77             'chart.key':                    [],
78             'chart.key.background':         'white',
79             'chart.key.position':           'graph',
80             'chart.key.shadow':             false,
81             'chart.key.shadow.color':       '#666',
82             'chart.key.shadow.blur':        3,
83             'chart.key.shadow.offsetx':     2,
84             'chart.key.shadow.offsety':     2,
85             'chart.key.position.gutter.boxed': true,
86             'chart.key.position.x':         null,
87             'chart.key.position.y':         null,
88             'chart.key.color.shape':        'square',
89             'chart.key.rounded':            true,
90
91             'chart.tooltips':              null,
92             'chart.tooltips.effect':        'fade',
93             'chart.tooltips.css.class':     'RGraph_tooltip',
94             'chart.tooltips.highlight':     true,
95             'chart.annotatable':           false,
96             'chart.annotate.color':        'black',
97             'chart.zoom.factor':           1.5,
98             'chart.zoom.fade.in':          true,
99             'chart.zoom.fade.out':         true,
100             'chart.zoom.factor':           1.5,
101             'chart.zoom.fade.in':          true,
102             'chart.zoom.fade.out':         true,
103             'chart.zoom.hdir':             'right',
104             'chart.zoom.vdir':             'down',
105             'chart.zoom.frames':           10,
106             'chart.zoom.delay':            50,
107             'chart.zoom.shadow':           true,
108             'chart.zoom.mode':             'canvas',
109             'chart.zoom.thumbnail.width':  75,
110             'chart.zoom.thumbnail.height': 75,
111             'chart.zoom.background':        true,
112             'chart.zoom.action':            'zoom',
113             'chart.resizable':              false
114         }
115
116         // Store the data
117         this.data = data;
118     }
119
120
121     /**
122     * A setter
123     * 
124     * @param name  string The name of the property to set
125     * @param value mixed  The value of the property
126     */
127     RGraph.Funnel.prototype.Set = function (name, value)
128     {
129         this.properties[name.toLowerCase()] = value;
130     }
131
132
133     /**
134     * A getter
135     * 
136     * @param name  string The name of the property to get
137     */
138     RGraph.Funnel.prototype.Get = function (name)
139     {
140         return this.properties[name.toLowerCase()];
141     }
142
143
144     /**
145     * The function you call to draw the bar chart
146     */
147     RGraph.Funnel.prototype.Draw = function ()
148     {
149         /**
150         * Fire the onbeforedraw event
151         */
152         RGraph.FireCustomEvent(this, 'onbeforedraw');
153
154         /**
155         * Clear all of this canvases event handlers (the ones installed by RGraph)
156         */
157         RGraph.ClearEventListeners(this.id);
158
159         // This stops the coords array from growing
160         this.coords = [];
161
162         RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'), null, this.Get('chart.text.size') + 2);
163         this.DrawFunnel();
164         
165         
166         /**
167         * Setup the context menu if required
168         */
169         if (this.Get('chart.contextmenu')) {
170             RGraph.ShowContext(this);
171         }
172         
173         /**
174         * The tooltip handler
175         */
176         if (this.Get('chart.tooltips')) {
177         
178             RGraph.Register(this);
179
180             var canvas_onclick_func = function (e)
181             {
182                 RGraph.Redraw();
183
184                 var e       = RGraph.FixEventObject(e);
185                 var canvas  = e.target;
186                 var context = canvas.getContext('2d');
187                 var obj     = canvas.__object__;
188
189                 var mouseCoords = RGraph.getMouseXY(e);
190                 var coords = obj.coords;
191                 var x = mouseCoords[0];
192                 var y = mouseCoords[1];
193
194                 for (i=0; i<coords.length; ++i) {
195                     if (
196                            x > coords[i][0]
197                         && x < coords[i][2]
198                         && y > coords[i][1]
199                         && y < coords[i][5]
200                        ) {
201                        
202                         /**
203                         * Handle the right corner
204                         */
205                         if (x > coords[i][4]) {
206                             var w1 = coords[i][2] - coords[i][4];
207                             var h1 = coords[i][5] - coords[i][3];;
208                             var a1 = Math.atan(h1 / w1) * 57.3; // DEGREES
209
210                             var w2 = coords[i][2] - mouseCoords[0];
211                             var h2 = mouseCoords[1] - coords[i][1];
212                             var a2 = Math.atan(h2 / w2) * 57.3; // DEGREES
213
214                             if (a2 > a1) {
215                                 continue;
216                             }
217                         
218                         /**
219                         * Handle the left corner
220                         */
221                         } else if (x < coords[i][6]) {
222                             var w1 = coords[i][6] - coords[i][0];
223                             var h1 = coords[i][7] - coords[i][1];;
224                             var a1 = Math.atan(h1 / w1) * 57.3; // DEGREES
225
226                             var w2 = mouseCoords[0] - coords[i][0];
227                             var h2 = mouseCoords[1] - coords[i][1];
228                             var a2 = Math.atan(h2 / w2) * 57.3; // DEGREES
229                         
230                             if (a2 > a1) {
231                                 continue;
232                             }
233                         }
234
235                         // Is there a tooltip defined?
236                         if (!obj.Get('chart.tooltips')[i] && typeof(obj.Get('chart.tooltips')) != 'function') {
237                             break;
238                         }
239
240                         context.beginPath();
241
242                         RGraph.NoShadow(obj);
243
244                         context.fillStyle = 'rgba(255,255,255,0.5)';
245                         context.moveTo(coords[i][0], coords[i][1]);
246                         context.lineTo(coords[i][2], coords[i][3]);
247                         context.lineTo(coords[i][4], coords[i][5]);
248                         context.lineTo(coords[i][6], coords[i][7]);
249                         context.closePath();
250
251                         context.stroke();
252                         context.fill();
253                     
254                         /**
255                         * Draw the key again for in-graph keys so they don't appear "under" the highlight
256                         */
257                         if (obj.Get('chart.key').length && obj.Get('chart.key.position') == 'graph') {
258                             RGraph.DrawKey(obj, obj.Get('chart.key'), obj.Get('chart.colors'));
259                         }
260                         
261                         /**
262                         * Redraw the labels if necessary
263                         */
264                         if (obj.Get('chart.labels')) {
265                             obj.DrawLabels();
266                         }
267
268                         /**
269                         * Get the tooltip text
270                         */
271                         if (typeof(obj.Get('chart.tooltips')) == 'function') {
272                             var text = obj.Get('chart.tooltips')(i);
273                         
274                         } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[i]) == 'function') {
275                             var text = obj.Get('chart.tooltips')[i](i);
276                         
277                         } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
278                             var text = obj.Get('chart.tooltips')[i];
279
280                         } else {
281                             var text = '';
282                         }
283
284                         // Show the tooltip
285                         RGraph.Tooltip(canvas, text, e.pageX, e.pageY, i);
286         
287                         // Stop the event propagating
288                         e.stopPropagation();
289                         
290                         break;
291                     }
292                 }
293             }
294             this.canvas.addEventListener('click', canvas_onclick_func, false);
295             RGraph.AddEventListener(this.id, 'click', canvas_onclick_func);
296             
297             /**
298             * Onmousemove event handler
299             */
300             var canvas_onmousemove_func = function (e)
301             {
302                 var e = RGraph.FixEventObject(e);
303
304                 var canvas = e.target;
305                 var context = canvas.getContext('2d');
306                 var obj     = canvas.__object__;
307                 var overFunnel = false;
308                 var coords = obj.coords;
309
310                 var mouseCoords = RGraph.getMouseXY(e);
311                 var x = mouseCoords[0];
312                 var y = mouseCoords[1];
313
314                 for (i=0; i<coords.length; ++i) {
315                     if (
316                            x > coords[i][0]
317                         && x < coords[i][2]
318                         && y > coords[i][1]
319                         && y < coords[i][5]
320                        ) {
321                        
322                         /**
323                         * Handle the right corner
324                         */
325                         if (x > coords[i][4]) {
326                             var w1 = coords[i][2] - coords[i][4];
327                             var h1 = coords[i][5] - coords[i][3];;
328                             var a1 = Math.atan(h1 / w1) * 57.3; // DEGREES
329
330                             var w2 = coords[i][2] - mouseCoords[0];
331                             var h2 = mouseCoords[1] - coords[i][1];
332                             var a2 = Math.atan(h2 / w2) * 57.3; // DEGREES
333
334                             if (a2 > a1) {
335                                 continue;
336                             }
337                         
338                         /**
339                         * Handle the left corner
340                         */
341                         } else if (x < coords[i][6]) {
342                             var w1 = coords[i][6] - coords[i][0];
343                             var h1 = coords[i][7] - coords[i][1];;
344                             var a1 = Math.atan(h1 / w1) * 57.3; // DEGREES
345
346                             var w2 = mouseCoords[0] - coords[i][0];
347                             var h2 = mouseCoords[1] - coords[i][1];
348                             var a2 = Math.atan(h2 / w2) * 57.3; // DEGREES
349                         
350                             if (a2 > a1) {
351                                 continue;
352                             }
353                         }
354                         
355                         // Is there a tooltip defined?
356                         if (!obj.Get('chart.tooltips')[i] && typeof(obj.Get('chart.tooltips')) != 'function') {
357                             break;
358                         }
359
360                         overFunnel = true;
361                         canvas.style.cursor = 'pointer';
362         
363                         // Stop the event propagating
364                         e.stopPropagation();
365                         
366                         break;
367                     }
368                 }
369                 
370                 if (!overFunnel) {
371                     canvas.style.cursor = 'default';
372                     canvas.style.cursor = 'default';
373                 }
374             }
375             this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
376             RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
377         }
378
379
380         /**
381         * Draw the labels on the chart
382         */
383         this.DrawLabels();
384         
385         /**
386         * If the canvas is annotatable, do install the event handlers
387         */
388         if (this.Get('chart.annotatable')) {
389             RGraph.Annotate(this);
390         }
391         
392         /**
393         * This bit shows the mini zoom window if requested
394         */
395         if (this.Get('chart.zoom.mode') == 'thumbnail'|| this.Get('chart.zoom.mode') == 'area') {
396             RGraph.ShowZoomWindow(this);
397         }
398
399         
400         /**
401         * This function enables resizing
402         */
403         if (this.Get('chart.resizable')) {
404             RGraph.AllowResizing(this);
405         }
406         
407         /**
408         * Fire the RGraph ondraw event
409         */
410         RGraph.FireCustomEvent(this, 'ondraw');
411     }
412
413
414     /**
415     * This function actually draws the chart
416     */
417     RGraph.Funnel.prototype.DrawFunnel = function ()
418     {
419         var context   = this.context;
420         var canvas    = this.canvas;
421         var width     = this.canvas.width - (2 * this.Get('chart.gutter'));
422         var height    = this.canvas.height - (2 * this.Get('chart.gutter'));
423         var total     = RGraph.array_max(this.data);
424         var accheight = this.Get('chart.gutter');
425
426
427         /**
428         * Loop through each segment to draw
429         */
430         
431         // Set a shadow if it's been requested
432         if (this.Get('chart.shadow')) {
433             context.shadowColor   = this.Get('chart.shadow.color');
434             context.shadowBlur    = this.Get('chart.shadow.blur');
435             context.shadowOffsetX = this.Get('chart.shadow.offsetx');
436             context.shadowOffsetY = this.Get('chart.shadow.offsety');
437         }
438
439         for (i=0; i<this.data.length; ++i) {
440
441             i = Number(i);
442             
443             var curvalue  = this.data[i];
444             var curwidth  = (curvalue / total) * width;
445             var curheight = height / this.data.length;
446             var halfCurWidth = (curwidth / 2);
447             var nextvalue = this.data[i + 1] ?  this.data[i + 1] : 0;
448             var nextwidth = this.data[i + 1] ? (nextvalue / total) * width : 0;
449             var halfNextWidth = (nextwidth / 2);
450             var center    = (canvas.width / 2);
451             var gutter    = this.Get('chart.gutter');
452
453             /**
454             * First segment
455             */
456             if (i == 0) {
457                 var x1 = center - halfCurWidth;
458                 var y1 = gutter;
459                 var x2 = center + halfCurWidth;
460                 var y2 = gutter;
461                 var x3 = center + halfNextWidth;
462                 var y3 = accheight + curheight;
463                 var x4 = center - halfNextWidth;
464                 var y4 = accheight + curheight;
465
466             /**
467             * Subsequent segments
468             */
469             } else {
470                 var x1 = center - halfCurWidth;
471                 var y1 = accheight;
472                 var x2 = center + halfCurWidth;
473                 var y2 = accheight;
474                 var x3 = center + halfNextWidth;
475                 var y3 = accheight + curheight;
476                 var x4 = center - halfNextWidth;
477                 var y4 = accheight + curheight;
478             }
479
480             /**
481             * Set the fill colour. If i is over 0 then don't use an offset
482             */
483             if (document.all && this.Get('chart.shadow')) {
484                 this.DrawIEShadow([x1, y1, x2, y2, x3, y3, x4, y4], i > 0 && this.Get('chart.shadow.offsety') < 0);
485             }
486
487             context.strokeStyle = this.Get('chart.strokestyle');
488             context.fillStyle   = this.Get('chart.colors')[i];
489
490             context.beginPath();
491                 context.moveTo(x1, y1);
492                 context.lineTo(x2, y2);
493                 context.lineTo(x3, y3);
494                 context.lineTo(x4, y4);
495             context.closePath();
496
497             /**
498             * Store the coordinates
499             */
500             this.coords.push([x1, y1, x2, y2, x3, y3, x4, y4]);
501
502             // The redrawing if the shadow is on will do the stroke
503             if (!this.Get('chart.shadow')) {
504                 context.stroke();
505             }
506
507             context.fill();
508
509             accheight += curheight;
510         }
511
512         /**
513         * If the shadow is enabled, redraw every segment, in order to allow for shadows going upwards
514         */
515         if (this.Get('chart.shadow')) {
516         
517             RGraph.NoShadow(this);
518         
519             for (i=0; i<this.coords.length; ++i) {
520             
521                 context.strokeStyle = this.Get('chart.strokestyle');
522                 context.fillStyle = this.Get('chart.colors')[i];
523         
524                 context.beginPath();
525                     context.moveTo(this.coords[i][0], this.coords[i][1]);
526                     context.lineTo(this.coords[i][2], this.coords[i][3]);
527                     context.lineTo(this.coords[i][4], this.coords[i][5]);
528                     context.lineTo(this.coords[i][6], this.coords[i][7]);
529                 context.closePath();
530                 
531                 context.stroke();
532                 context.fill();
533             }
534         }
535         
536         /**
537         * Lastly, draw the key if necessary
538         */
539         if (this.Get('chart.key') && this.Get('chart.key').length) {
540             RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
541         }
542     }
543     
544     /**
545     * Draws the labels
546     */
547     RGraph.Funnel.prototype.DrawLabels = function ()
548     {
549         /**
550         * Draws the labels
551         */
552         if (this.Get('chart.labels') && this.Get('chart.labels').length > 0) {
553
554             var context = this.context;
555
556             for (var j=0; j<this.coords.length; ++j) {  // MUST be "j"
557                 context.beginPath();
558                 
559                 // Set the color back to black
560                 context.strokeStyle = 'black';
561                 context.fillStyle = this.Get('chart.text.color');
562                 
563                 // Turn off any shadow
564                 RGraph.NoShadow(this);
565                 
566                 var label = this.Get('chart.labels')[j];
567
568                 RGraph.Text(context, this.Get('chart.text.font'), this.Get('chart.text.size'), this.Get('chart.text.halign') == 'left' ? 15 : this.canvas.width / 2, this.coords[j][1], label, 'center', this.Get('chart.text.halign') == 'left' ? 'left' : 'center', true, null, this.Get('chart.text.boxed') ? 'white' : null);
569             }
570         }
571     }
572
573
574     /**
575     * This function is used by MSIE only to manually draw the shadow
576     * 
577     * @param array coords The coords for the bar
578     */
579     RGraph.Funnel.prototype.DrawIEShadow = function (coords, noOffset)
580     {
581         var prevFillStyle = this.context.fillStyle;
582         var offsetx = this.Get('chart.shadow.offsetx');
583         var offsety = this.Get('chart.shadow.offsety');
584         var context = this.context;
585
586         context.lineWidth = 1;
587         context.fillStyle = this.Get('chart.shadow.color');
588         
589         // Draw the shadow
590         context.beginPath();
591             context.moveTo(coords[0] + (noOffset ? 0 : offsetx), coords[1] + (noOffset ? 0 : offsety));
592             context.lineTo(coords[2] + (noOffset ? 0 : offsetx), coords[3] + (noOffset ? 0 : offsety));
593             context.lineTo(coords[4] + (noOffset ? 0 : offsetx), coords[5] + (noOffset ? 0 : offsety));
594             context.lineTo(coords[6] + (noOffset ? 0 : offsetx), coords[7] + (noOffset ? 0 : offsety));
595         context.closePath();
596         
597         context.fill();
598         
599
600         
601         // Change the fillstyle back to what it was
602         this.context.fillStyle = prevFillStyle;
603     }