source: Dev/trunk/RGraph/libraries/RGraph.funnel.js @ 77

Last change on this file since 77 was 77, checked in by fpvanagthoven, 14 years ago

RGraph

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