2 * o------------------------------------------------------------------------------o
3 * | This file is part of the RGraph package - you can learn more at: |
5 * | http://www.rgraph.net |
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: |
11 * | http://www.rgraph.net/LICENSE.txt |
12 * o------------------------------------------------------------------------------o
15 if (typeof(RGraph) == 'undefined') RGraph = {};
18 * The pie chart constructor
20 * @param data array The data to be represented on the pie chart
22 RGraph.Pie = function (id, data)
25 this.canvas = document.getElementById(id);
26 this.context = this.canvas.getContext("2d");
27 this.canvas.__object__ = this;
38 * Compatibility with older browsers
40 RGraph.OldBrowserCompat(this.context);
43 'chart.colors': ['rgb(255,0,0)', '#ddd', 'rgb(0,255,0)', 'rgb(0,0,255)', 'pink', 'yellow', 'red', 'rgb(0,255,255)', 'black', 'white'],
44 'chart.strokestyle': '#999',
47 'chart.labels.sticks': false,
48 'chart.labels.sticks.color': '#aaa',
52 'chart.title.background': null,
53 'chart.title.hpos': null,
54 'chart.title.vpos': null,
55 'chart.shadow': false,
56 'chart.shadow.color': 'rgba(0,0,0,0.5)',
57 'chart.shadow.offsetx': 3,
58 'chart.shadow.offsety': 3,
59 'chart.shadow.blur': 3,
60 'chart.text.size': 10,
61 'chart.text.color': 'black',
62 'chart.text.font': 'Verdana',
63 'chart.contextmenu': null,
65 'chart.tooltips.event': 'onclick',
66 'chart.tooltips.effect': 'fade',
67 'chart.tooltips.css.class': 'RGraph_tooltip',
68 'chart.tooltips.highlight': true,
70 'chart.highlight.style': '3d',
71 'chart.highlight.style.2d.color': 'rgba(255,255,255,0.5)',
72 'chart.border': false,
73 'chart.border.color': 'rgba(255,255,255,0.5)',
75 'chart.key.background': 'white',
76 'chart.key.position': 'graph',
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.annotatable': false,
88 'chart.annotate.color': 'black',
89 'chart.align': 'center',
90 'chart.zoom.factor': 1.5,
91 'chart.zoom.fade.in': true,
92 'chart.zoom.fade.out': true,
93 'chart.zoom.hdir': 'right',
94 'chart.zoom.vdir': 'down',
95 'chart.zoom.frames': 10,
96 'chart.zoom.delay': 50,
97 'chart.zoom.shadow': true,
98 'chart.zoom.mode': 'canvas',
99 'chart.zoom.thumbnail.width': 75,
100 'chart.zoom.thumbnail.height': 75,
101 'chart.zoom.background': true,
102 'chart.zoom.action': 'zoom',
103 'chart.resizable': false,
104 'chart.variant': 'pie'
108 * Calculate the total
110 for (var i=0,len=data.length; i<len; i++) {
111 this.total += data[i];
114 // Check the common library has been included
115 if (typeof(RGraph) == 'undefined') {
116 alert('[PIE] Fatal error: The common library does not appear to have been included');
124 RGraph.Pie.prototype.Set = function (name, value)
126 this.properties[name] = value;
133 RGraph.Pie.prototype.Get = function (name)
135 return this.properties[name];
140 * This draws the pie chart
142 RGraph.Pie.prototype.Draw = function ()
145 * Fire the onbeforedraw event
147 RGraph.FireCustomEvent(this, 'onbeforedraw');
151 * Clear all of this canvases event handlers (the ones installed by RGraph)
153 RGraph.ClearEventListeners(this.id);
156 this.diameter = Math.min(this.canvas.height, this.canvas.width) - (2 * this.Get('chart.gutter'));
157 this.radius = this.Get('chart.radius') ? this.Get('chart.radius') : this.diameter / 2;
158 // this.centerx now defined below
159 this.centery = this.canvas.height / 2;
164 * Alignment (Pie is center aligned by default) Only if centerx is not defined - donut defines the centerx
166 if (this.Get('chart.align') == 'left') {
167 this.centerx = this.radius + this.Get('chart.gutter');
169 } else if (this.Get('chart.align') == 'right') {
170 this.centerx = this.canvas.width - (this.radius + this.Get('chart.gutter'));
173 this.centerx = this.canvas.width / 2;
177 * Draw the shadow if required
179 if (this.Get('chart.shadow')) {
181 var offsetx = document.all ? this.Get('chart.shadow.offsetx') : 0;
182 var offsety = document.all ? this.Get('chart.shadow.offsety') : 0;
184 this.context.beginPath();
185 this.context.fillStyle = this.Get('chart.shadow.color');
187 this.context.shadowColor = this.Get('chart.shadow.color');
188 this.context.shadowBlur = this.Get('chart.shadow.blur');
189 this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
190 this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
192 this.context.arc(this.centerx + offsetx, this.centery + offsety, this.radius, 0, 6.28, 0);
196 // Now turn off the shadow
197 RGraph.NoShadow(this);
201 * The total of the array of values
203 this.total = RGraph.array_sum(this.data);
205 for (var i=0,len=this.data.length; i<len; i++) {
206 var angle = (this.data[i] / this.total) * 360;
208 this.DrawSegment(angle,
209 this.Get('chart.colors')[i],
210 i == (this.data.length - 1));
214 * Redraw the seperating lines
216 if (this.Get('chart.linewidth') > 0) {
217 this.context.beginPath();
218 this.context.lineWidth = this.Get('chart.linewidth');
219 this.context.strokeStyle = this.Get('chart.strokestyle');
221 for (var i=0,len=this.angles.length; i<len; ++i) {
222 this.context.moveTo(this.centerx, this.centery);
223 this.context.arc(this.centerx, this.centery, this.radius, this.angles[i][0] / 57.3, (this.angles[i][0] + 0.01) / 57.3, 0);
226 this.context.stroke();
229 * And finally redraw the border
231 this.context.beginPath();
232 this.context.moveTo(this.centerx, this.centery);
233 this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, 0);
234 this.context.stroke();
240 if (this.Get('chart.labels.sticks')) {
243 // Redraw the border going around the Pie chart if the stroke style is NOT white
245 this.Get('chart.strokestyle') != 'white'
246 && this.Get('chart.strokestyle') != '#fff'
247 && this.Get('chart.strokestyle') != '#fffffff'
248 && this.Get('chart.strokestyle') != 'rgb(255,255,255)'
249 && this.Get('chart.strokestyle') != 'rgba(255,255,255,0)'
252 this.context.beginPath();
253 this.context.strokeStyle = this.Get('chart.strokestyle');
254 this.context.lineWidth = this.Get('chart.linewidth');
255 this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, false);
256 this.context.stroke();
268 if (this.Get('chart.align') == 'left') {
269 var centerx = this.radius + this.Get('chart.gutter');
271 } else if (this.Get('chart.align') == 'right') {
272 var centerx = this.canvas.width - (this.radius + this.Get('chart.gutter'));
278 RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'), centerx, this.Get('chart.text.size') + 2);
282 * Setup the context menu if required
284 if (this.Get('chart.contextmenu')) {
285 RGraph.ShowContext(this);
291 if (this.Get('chart.tooltips').length) {
294 * Register this object for redrawing
296 RGraph.Register(this);
301 //this.canvas.onclick = function (e)
302 var canvas_onclick_func = function (e)
304 RGraph.HideZoomedCanvas();
306 e = RGraph.FixEventObject(e);
308 var mouseCoords = RGraph.getMouseXY(e);
310 var canvas = e.target;
311 var context = canvas.getContext('2d');
312 var obj = e.target.__object__;
314 var x = mouseCoords[0] - obj.centerx;
315 var y = mouseCoords[1] - obj.centery;
316 var theta = Math.atan(y / x); // RADIANS
317 var hyp = y / Math.sin(theta);
322 * If it's actually a donut make sure the hyp is bigger
323 * than the size of the hole in the middle
325 if (obj.Get('chart.variant') == 'donut' && Math.abs(hyp) < (obj.radius / 2)) {
330 * The angles for each segment are stored in "angles",
331 * so go through that checking if the mouse position corresponds
333 var isDonut = obj.Get('chart.variant') == 'donut';
334 var hStyle = obj.Get('chart.highlight.style');
335 var segment = RGraph.getSegment(e);
340 if (RGraph.Registry.Get('chart.tooltip') && segment[5] == RGraph.Registry.Get('chart.tooltip').__index__) {
346 if (isDonut || hStyle == '2d') {
350 context.fillStyle = obj.Get('chart.highlight.style.2d.color');
352 context.moveTo(obj.centerx, obj.centery);
353 context.arc(obj.centerx, obj.centery, obj.radius, RGraph.degrees2Radians(obj.angles[segment[5]][0]), RGraph.degrees2Radians(obj.angles[segment[5]][1]), 0);
354 context.lineTo(obj.centerx, obj.centery);
359 //Removed 7th December 2010
364 context.lineWidth = 2;
367 * Draw a white segment where the one that has been clicked on was
369 context.fillStyle = 'white';
370 context.strokeStyle = 'white';
372 context.moveTo(obj.centerx, obj.centery);
373 context.arc(obj.centerx, obj.centery, obj.radius, obj.angles[segment[5]][0] / 57.3, obj.angles[segment[5]][1] / 57.3, 0);
377 context.lineWidth = 1;
379 context.shadowColor = '#666';
380 context.shadowBlur = 3;
381 context.shadowOffsetX = 3;
382 context.shadowOffsetY = 3;
384 // Draw the new segment
386 context.fillStyle = obj.Get('chart.colors')[segment[5]];
387 context.strokeStyle = 'rgba(0,0,0,0)';
388 context.moveTo(obj.centerx - 3, obj.centery - 3);
389 context.arc(obj.centerx - 3, obj.centery - 3, obj.radius, RGraph.degrees2Radians(obj.angles[segment[5]][0]), RGraph.degrees2Radians(obj.angles[segment[5]][1]), 0);
390 context.lineTo(obj.centerx - 3, obj.centery - 3);
396 // Turn off the shadow
397 RGraph.NoShadow(obj);
400 * If a border is defined, redraw that
402 if (obj.Get('chart.border')) {
404 context.strokeStyle = obj.Get('chart.border.color');
405 context.lineWidth = 5;
406 context.arc(obj.centerx - 3, obj.centery - 3, obj.radius - 2, RGraph.degrees2Radians(obj.angles[i][0]), RGraph.degrees2Radians(obj.angles[i][1]), 0);
412 * If a tooltip is defined, show it
416 * Get the tooltip text
418 if (typeof(obj.Get('chart.tooltips')) == 'function') {
419 var text = String(obj.Get('chart.tooltips')(segment[5]));
421 } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[segment[5]]) == 'function') {
422 var text = String(obj.Get('chart.tooltips')[segment[5]](segment[5]));
424 } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
425 var text = String(obj.Get('chart.tooltips')[segment[5]]);
432 RGraph.Tooltip(canvas, text, e.pageX, e.pageY, segment[5]);
436 * Need to redraw the key?
438 if (obj.Get('chart.key') && obj.Get('chart.key').length && obj.Get('chart.key.position') == 'graph') {
439 RGraph.DrawKey(obj, obj.Get('chart.key'), obj.Get('chart.colors'));
447 var event_name = this.Get('chart.tooltips.event') == 'onmousemove' ? 'mousemove' : 'click';
449 this.canvas.addEventListener(event_name, canvas_onclick_func, false);
450 RGraph.AddEventListener(this.id, event_name, canvas_onclick_func);
460 * The onmousemove event for changing the cursor
462 //this.canvas.onmousemove = function (e)
463 var canvas_onmousemove_func = function (e)
465 RGraph.HideZoomedCanvas();
467 e = RGraph.FixEventObject(e);
469 var segment = RGraph.getSegment(e);
472 e.target.style.cursor = 'pointer';
478 * Put the cursor back to null
480 e.target.style.cursor = 'default';
482 this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
483 RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
488 * If a border is pecified, draw it
490 if (this.Get('chart.border')) {
491 this.context.beginPath();
492 this.context.lineWidth = 5;
493 this.context.strokeStyle = this.Get('chart.border.color');
495 this.context.arc(this.centerx,
502 this.context.stroke();
506 * Draw the kay if desired
508 if (this.Get('chart.key') != null) {
509 //this.Set('chart.key.position', 'graph');
510 RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
515 * If this is actually a donut, draw a big circle in the middle
517 if (this.Get('chart.variant') == 'donut') {
518 this.context.beginPath();
519 this.context.strokeStyle = this.Get('chart.strokestyle');
520 this.context.fillStyle = 'white';//this.Get('chart.fillstyle');
521 this.context.arc(this.centerx, this.centery, this.radius / 2, 0, 6.28, 0);
522 this.context.stroke();
526 RGraph.NoShadow(this);
529 * If the canvas is annotatable, do install the event handlers
531 if (this.Get('chart.annotatable')) {
532 RGraph.Annotate(this);
536 * This bit shows the mini zoom window if requested
538 if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
539 RGraph.ShowZoomWindow(this);
544 * This function enables resizing
546 if (this.Get('chart.resizable')) {
547 RGraph.AllowResizing(this);
551 * Fire the RGraph ondraw event
553 RGraph.FireCustomEvent(this, 'ondraw');
558 * Draws a single segment of the pie chart
560 * @param int degrees The number of degrees for this segment
562 RGraph.Pie.prototype.DrawSegment = function (degrees, color, last)
564 var context = this.context;
565 var canvas = this.canvas;
566 var subTotal = this.subTotal;
570 context.fillStyle = color;
571 context.strokeStyle = this.Get('chart.strokestyle');
572 context.lineWidth = 0;
574 context.arc(this.centerx,
578 (last ? 360 : subTotal + degrees) / 57.3,
581 context.lineTo(this.centerx, this.centery);
583 // Keep hold of the angles
584 this.angles.push([subTotal, subTotal + degrees])
585 this.context.closePath();
590 * Calculate the segment angle
592 this.Get('chart.segments').push([subTotal, subTotal + degrees]);
593 this.subTotal += degrees;
597 * Draws the graphs labels
599 RGraph.Pie.prototype.DrawLabels = function ()
601 var hAlignment = 'left';
602 var vAlignment = 'center';
603 var labels = this.Get('chart.labels');
604 var context = this.context;
607 * Turn the shadow off
609 RGraph.NoShadow(this);
611 context.fillStyle = 'black';
615 * Draw the key (ie. the labels)
617 if (labels && labels.length) {
619 var text_size = this.Get('chart.text.size');
621 for (i=0; i<labels.length; ++i) {
624 * T|his ensures that if we're given too many labels, that we don't get an error
626 if (typeof(this.Get('chart.segments')[i]) == 'undefined') {
630 // Move to the centre
631 context.moveTo(this.centerx,this.centery);
633 var a = this.Get('chart.segments')[i][0] + ((this.Get('chart.segments')[i][1] - this.Get('chart.segments')[i][0]) / 2);
640 vAlignment = 'center';
641 } else if (a < 180) {
642 hAlignment = 'right';
643 vAlignment = 'center';
644 } else if (a < 270) {
645 hAlignment = 'right';
646 vAlignment = 'center';
647 } else if (a < 360) {
649 vAlignment = 'center';
652 context.fillStyle = this.Get('chart.text.color');
655 this.Get('chart.text.font'),
657 this.centerx + ((this.radius + 10)* Math.cos(a / 57.3)) + (this.Get('chart.labels.sticks') ? (a < 90 || a > 270 ? 2 : -2) : 0),
658 this.centery + (((this.radius + 10) * Math.sin(a / 57.3))),
670 * This function draws the pie chart sticks (for the labels)
672 RGraph.Pie.prototype.DrawSticks = function ()
674 var context = this.context;
675 var segments = this.Get('chart.segments');
676 var offset = this.Get('chart.linewidth') / 2;
678 for (var i=0; i<segments.length; ++i) {
680 var degrees = segments[i][1] - segments[i][0];
683 context.strokeStyle = this.Get('chart.labels.sticks.color');
684 context.lineWidth = 1;
686 var midpoint = (segments[i][0] + (degrees / 2)) / 57.3;
688 context.arc(this.centerx,
696 context.arc(this.centerx,
698 this.radius - offset,