Issues (4542)

js/flot/jquery.flot.pie.js (14 issues)

1
/* Flot plugin for rendering pie charts.
2
3
Copyright (c) 2007-2014 IOLA and Ole Laursen.
4
Licensed under the MIT license.
5
6
The plugin assumes that each series has a single data value, and that each
7
value is a positive integer or zero.  Negative numbers don't make sense for a
8
pie chart, and have unpredictable results.  The values do NOT need to be
9
passed in as percentages; the plugin will calculate the total and per-slice
10
percentages internally.
11
12
* Created by Brian Medendorp
13
14
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
15
16
The plugin supports these options:
17
18
	series: {
19
		pie: {
20
			show: true/false
21
			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
22
			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
23
			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
24
			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
25
			offset: {
26
				top: integer value to move the pie up or down
27
				left: integer value to move the pie left or right, or 'auto'
28
			},
29
			stroke: {
30
				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
31
				width: integer pixel width of the stroke
32
			},
33
			label: {
34
				show: true/false, or 'auto'
35
				formatter:  a user-defined function that modifies the text/style of the label text
36
				radius: 0-1 for percentage of fullsize, or a specified pixel length
37
				background: {
38
					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
39
					opacity: 0-1
40
				},
41
				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
42
			},
43
			combine: {
44
				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
45
				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
46
				label: any text value of what the combined slice should be labeled
47
			}
48
			highlight: {
49
				opacity: 0-1
50
			}
51
		}
52
	}
53
54
More detail and specific examples can be found in the included HTML file.
55
56
*/
57
58
(function($) {
59
60
	// Maximum redraw attempts when fitting labels within the plot
61
62
	var REDRAW_ATTEMPTS = 10;
63
64
	// Factor by which to shrink the pie when fitting labels within the plot
65
66
	var REDRAW_SHRINK = 0.95;
67
68
	function init(plot) {
69
70
		var canvas = null,
71
			target = null,
72
			options = null,
73
			maxRadius = null,
74
			centerLeft = null,
75
			centerTop = null,
76
			processed = false,
77
			ctx = null;
78
79
		// interAktif variables
80
81
		var highlights = [];
82
83
		// add hook to determine if pie plugin in enabled, and then perform necessary operations
84
85
		plot.hooks.processOptions.push(function(plot, options) {
86
			if (options.series.pie.show) {
87
88
				options.grid.show = false;
89
90
				// set labels.show
91
92
				if (options.series.pie.label.show == "auto") {
93
					if (options.legend.show) {
94
						options.series.pie.label.show = false;
95
					} else {
96
						options.series.pie.label.show = true;
97
					}
98
				}
99
100
				// set radius
101
102
				if (options.series.pie.radius == "auto") {
103
					if (options.series.pie.label.show) {
104
						options.series.pie.radius = 3/4;
105
					} else {
106
						options.series.pie.radius = 1;
107
					}
108
				}
109
110
				// ensure sane tilt
111
112
				if (options.series.pie.tilt > 1) {
113
					options.series.pie.tilt = 1;
114
				} else if (options.series.pie.tilt < 0) {
115
					options.series.pie.tilt = 0;
116
				}
117
			}
118
		});
119
120
		plot.hooks.bindEvents.push(function(plot, eventHolder) {
121
			var options = plot.getOptions();
122
			if (options.series.pie.show) {
123
				if (options.grid.hoverable) {
124
					eventHolder.unbind("mousemove").mousemove(onMouseMove);
125
				}
126
				if (options.grid.clickable) {
127
					eventHolder.unbind("click").click(onClick);
128
				}
129
			}
130
		});
131
132
		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
133
			var options = plot.getOptions();
134
			if (options.series.pie.show) {
135
				processDatapoints(plot, series, data, datapoints);
0 ignored issues
show
The call to processDatapoints seems to have too many arguments starting with datapoints.
Loading history...
136
			}
137
		});
138
139
		plot.hooks.drawOverlay.push(function(plot, octx) {
140
			var options = plot.getOptions();
141
			if (options.series.pie.show) {
142
				drawOverlay(plot, octx);
143
			}
144
		});
145
146
		plot.hooks.draw.push(function(plot, newCtx) {
147
			var options = plot.getOptions();
148
			if (options.series.pie.show) {
149
				draw(plot, newCtx);
150
			}
151
		});
152
153
		function processDatapoints(plot, series, datapoints) {
0 ignored issues
show
The parameter datapoints is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
The parameter series is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
154
			if (!processed)	{
155
				processed = true;
156
				canvas = plot.getCanvas();
157
				target = $(canvas).parent();
158
				options = plot.getOptions();
159
				plot.setData(combine(plot.getData()));
160
			}
161
		}
162
163
		function combine(data) {
164
165
			var total = 0,
166
				combined = 0,
167
				numCombined = 0,
168
				color = options.series.pie.combine.color,
169
				newdata = [];
170
171
			// Fix up the raw data from Flot, ensuring the data is numeric
172
173
			for (var i = 0; i < data.length; ++i) {
174
175
				var value = data[i].data;
176
177
				// If the data is an array, we'll assume that it's a standard
178
				// Flot x-y pair, and are concerned only with the second value.
179
180
				// Note how we use the original array, rather than creating a
181
				// new one; this is more efficient and preserves any extra data
182
				// that the user may have stored in higher indexes.
183
184
				if ($.isArray(value) && value.length == 1) {
185
    				value = value[0];
186
				}
187
188
				if ($.isArray(value)) {
189
					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
190
					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
191
						value[1] = +value[1];
192
					} else {
193
						value[1] = 0;
194
					}
195
				} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
196
					value = [1, +value];
197
				} else {
198
					value = [1, 0];
199
				}
200
201
				data[i].data = [value];
202
			}
203
204
			// Sum up all the slices, so we can calculate percentages for each
205
206
			for (var i = 0; i < data.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 173. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
207
				total += data[i].data[0][1];
208
			}
209
210
			// Count the number of slices with percentages below the combine
211
			// threshold; if it turns out to be just one, we won't combine.
212
213
			for (var i = 0; i < data.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 173. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
214
				var value = data[i].data[0][1];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable value already seems to be declared on line 175. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
215
				if (value / total <= options.series.pie.combine.threshold) {
216
					combined += value;
217
					numCombined++;
218
					if (!color) {
219
						color = data[i].color;
220
					}
221
				}
222
			}
223
224
			for (var i = 0; i < data.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 173. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
225
				var value = data[i].data[0][1];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable value already seems to be declared on line 175. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
226
				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
227
					newdata.push(
228
						$.extend(data[i], {     /* extend to allow keeping all other original data values
229
						                           and using them e.g. in labelFormatter. */
230
							data: [[1, value]],
231
							color: data[i].color,
232
							label: data[i].label,
233
							angle: value * Math.PI * 2 / total,
234
							percent: value / (total / 100)
235
						})
236
					);
237
				}
238
			}
239
240
			if (numCombined > 1) {
241
				newdata.push({
242
					data: [[1, combined]],
243
					color: color,
244
					label: options.series.pie.combine.label,
245
					angle: combined * Math.PI * 2 / total,
246
					percent: combined / (total / 100)
247
				});
248
			}
249
250
			return newdata;
251
		}
252
253
		function draw(plot, newCtx) {
254
255
			if (!target) {
256
				return; // if no series were passed
257
			}
258
259
			var canvasWidth = plot.getPlaceholder().width(),
260
				canvasHeight = plot.getPlaceholder().height(),
261
				legendWidth = target.children().filter(".legend").children().width() || 0;
262
263
			ctx = newCtx;
264
265
			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
266
267
			// When combining smaller slices into an 'other' slice, we need to
268
			// add a new series.  Since Flot gives plugins no way to modify the
269
			// list of series, the pie plugin uses a hack where the first call
270
			// to processDatapoints results in a call to setData with the new
271
			// list of series, then subsequent processDatapoints do nothing.
272
273
			// The plugin-global 'processed' flag is used to control this hack;
274
			// it starts out false, and is set to true after the first call to
275
			// processDatapoints.
276
277
			// Unfortunately this turns future setData calls into no-ops; they
278
			// call processDatapoints, the flag is true, and nothing happens.
279
280
			// To fix this we'll set the flag back to false here in draw, when
281
			// all series have been processed, so the next sequence of calls to
282
			// processDatapoints once again starts out with a slice-combine.
283
			// This is really a hack; in 0.9 we need to give plugins a proper
284
			// way to modify series before any processing begins.
285
286
			processed = false;
287
288
			// calculate maximum radius and center point
289
290
			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
291
			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
292
			centerLeft = canvasWidth / 2;
293
294
			if (options.series.pie.offset.left == "auto") {
295
				if (options.legend.position.match("w")) {
296
					centerLeft += legendWidth / 2;
297
				} else {
298
					centerLeft -= legendWidth / 2;
299
				}
300
				if (centerLeft < maxRadius) {
301
					centerLeft = maxRadius;
302
				} else if (centerLeft > canvasWidth - maxRadius) {
303
					centerLeft = canvasWidth - maxRadius;
304
				}
305
			} else {
306
				centerLeft += options.series.pie.offset.left;
307
			}
308
309
			var slices = plot.getData(),
310
				attempts = 0;
311
312
			// Keep shrinking the pie's radius until drawPie returns true,
313
			// indicating that all the labels fit, or we try too many times.
314
315
			do {
316
				if (attempts > 0) {
317
					maxRadius *= REDRAW_SHRINK;
0 ignored issues
show
The variable maxRadius is changed as part of the loop loop for example by REDRAW_SHRINK on line 317. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
318
				}
319
				attempts += 1;
320
				clear();
321
				if (options.series.pie.tilt <= 0.8) {
322
					drawShadow();
323
				}
324
			} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
325
326
			if (attempts >= REDRAW_ATTEMPTS) {
327
				clear();
328
				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
329
			}
330
331
			if (plot.setSeries && plot.insertLegend) {
332
				plot.setSeries(slices);
333
				plot.insertLegend();
334
			}
335
336
			// we're actually done at this point, just defining internal functions at this point
337
338
			function clear() {
339
				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
340
				target.children().filter(".pieLabel, .pieLabelBackground").remove();
341
			}
342
343
			function drawShadow() {
344
345
				var shadowLeft = options.series.pie.shadow.left;
346
				var shadowTop = options.series.pie.shadow.top;
347
				var edge = 10;
348
				var alpha = options.series.pie.shadow.alpha;
349
				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
350
351
				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
352
					return;	// shadow would be outside canvas, so don't draw it
353
				}
354
355
				ctx.save();
356
				ctx.translate(shadowLeft,shadowTop);
357
				ctx.globalAlpha = alpha;
358
				ctx.fillStyle = "#000";
359
360
				// center and rotate to starting position
361
362
				ctx.translate(centerLeft,centerTop);
363
				ctx.scale(1, options.series.pie.tilt);
364
365
				//radius -= edge;
366
367
				for (var i = 1; i <= edge; i++) {
368
					ctx.beginPath();
369
					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
370
					ctx.fill();
371
					radius -= i;
372
				}
373
374
				ctx.restore();
375
			}
376
377
			function drawPie() {
378
379
				var startAngle = Math.PI * options.series.pie.startAngle;
380
				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
381
382
				// center and rotate to starting position
383
384
				ctx.save();
385
				ctx.translate(centerLeft,centerTop);
386
				ctx.scale(1, options.series.pie.tilt);
387
				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
388
389
				// draw slices
390
391
				ctx.save();
392
				var currentAngle = startAngle;
393
				for (var i = 0; i < slices.length; ++i) {
394
					slices[i].startAngle = currentAngle;
395
					drawSlice(slices[i].angle, slices[i].color, true);
396
				}
397
				ctx.restore();
398
399
				// draw slice outlines
400
401
				if (options.series.pie.stroke.width > 0) {
402
					ctx.save();
403
					ctx.lineWidth = options.series.pie.stroke.width;
404
					currentAngle = startAngle;
405
					for (var i = 0; i < slices.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 393. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
406
						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
407
					}
408
					ctx.restore();
409
				}
410
411
				// draw donut hole
412
413
				drawDonutHole(ctx);
414
415
				ctx.restore();
416
417
				// Draw the labels, returning true if they fit within the plot
418
419
				if (options.series.pie.label.show) {
420
					return drawLabels();
421
				} else return true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
422
423
				function drawSlice(angle, color, fill) {
424
425
					if (angle <= 0 || isNaN(angle)) {
426
						return;
427
					}
428
429
					if (fill) {
430
						ctx.fillStyle = color;
431
					} else {
432
						ctx.strokeStyle = color;
433
						ctx.lineJoin = "round";
434
					}
435
436
					ctx.beginPath();
437
					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
438
						ctx.moveTo(0, 0); // Center of the pie
439
					}
440
441
					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
442
					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
443
					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
444
					ctx.closePath();
445
					//ctx.rotate(angle); // This doesn't work properly in Opera
446
					currentAngle += angle;
447
448
					if (fill) {
449
						ctx.fill();
450
					} else {
451
						ctx.stroke();
452
					}
453
				}
454
455
				function drawLabels() {
456
457
					var currentAngle = startAngle;
458
					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
459
460
					for (var i = 0; i < slices.length; ++i) {
461
						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
462
							if (!drawLabel(slices[i], currentAngle, i)) {
463
								return false;
464
							}
465
						}
466
						currentAngle += slices[i].angle;
467
					}
468
469
					return true;
470
471
					function drawLabel(slice, startAngle, index) {
472
473
						if (slice.data[0][1] == 0) {
474
							return true;
475
						}
476
477
						// format label text
478
479
						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
480
481
						if (lf) {
482
							text = lf(slice.label, slice);
483
						} else {
484
							text = slice.label;
485
						}
486
487
						if (plf) {
488
							text = plf(text, slice);
489
						}
490
491
						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
492
						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
493
						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
494
495
						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
496
						target.append(html);
497
498
						var label = target.children("#pieLabel" + index);
499
						var labelTop = (y - label.height() / 2);
500
						var labelLeft = (x - label.width() / 2);
501
502
						label.css("top", labelTop);
503
						label.css("left", labelLeft);
504
505
						// check to make sure that the label is not outside the canvas
506
507
						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
508
							return false;
509
						}
510
511
						if (options.series.pie.label.background.opacity != 0) {
512
513
							// put in the transparent background separately to avoid blended labels and label boxes
514
515
							var c = options.series.pie.label.background.color;
516
517
							if (c == null) {
518
								c = slice.color;
519
							}
520
521
							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
522
							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
523
								.css("opacity", options.series.pie.label.background.opacity)
524
								.insertBefore(label);
525
						}
526
527
						return true;
528
					} // end individual label function
529
				} // end drawLabels function
530
			} // end drawPie function
531
		} // end draw function
532
533
		// Placed here because it needs to be accessed from multiple locations
534
535
		function drawDonutHole(layer) {
536
			if (options.series.pie.innerRadius > 0) {
537
538
				// subtract the center
539
540
				layer.save();
541
				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
542
				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
543
				layer.beginPath();
544
				layer.fillStyle = options.series.pie.stroke.color;
545
				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
546
				layer.fill();
547
				layer.closePath();
548
				layer.restore();
549
550
				// add inner stroke
551
552
				layer.save();
553
				layer.beginPath();
554
				layer.strokeStyle = options.series.pie.stroke.color;
555
				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
556
				layer.stroke();
557
				layer.closePath();
558
				layer.restore();
559
560
				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
561
			}
562
		}
563
564
		//-- Additional InterAktif related functions --
565
566
		function isPointInPoly(poly, pt) {
567
			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
568
				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
569
				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
570
				&& (c = !c);
571
			return c;
572
		}
573
574
		function findNearbySlice(mouseX, mouseY) {
575
576
			var slices = plot.getData(),
577
				options = plot.getOptions(),
578
				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
579
				x, y;
580
581
			for (var i = 0; i < slices.length; ++i) {
582
583
				var s = slices[i];
584
585
				if (s.pie.show) {
586
587
					ctx.save();
588
					ctx.beginPath();
589
					ctx.moveTo(0, 0); // Center of the pie
590
					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
591
					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
592
					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
593
					ctx.closePath();
594
					x = mouseX - centerLeft;
595
					y = mouseY - centerTop;
596
597
					if (ctx.isPointInPath) {
598
						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
599
							ctx.restore();
600
							return {
601
								datapoint: [s.percent, s.data],
602
								dataIndex: 0,
603
								series: s,
604
								seriesIndex: i
605
							};
606
						}
607
					} else {
608
609
						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
610
611
						var p1X = radius * Math.cos(s.startAngle),
612
							p1Y = radius * Math.sin(s.startAngle),
613
							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
614
							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
615
							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
616
							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
617
							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
618
							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
619
							p5X = radius * Math.cos(s.startAngle + s.angle),
620
							p5Y = radius * Math.sin(s.startAngle + s.angle),
621
							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
622
							arrPoint = [x, y];
623
624
						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
625
626
						if (isPointInPoly(arrPoly, arrPoint)) {
627
							ctx.restore();
628
							return {
629
								datapoint: [s.percent, s.data],
630
								dataIndex: 0,
631
								series: s,
632
								seriesIndex: i
633
							};
634
						}
635
					}
636
637
					ctx.restore();
638
				}
639
			}
640
641
			return null;
642
		}
643
644
		function onMouseMove(e) {
645
			triggerClickHoverEvent("plothover", e);
646
		}
647
648
		function onClick(e) {
649
			triggerClickHoverEvent("plotclick", e);
650
		}
651
652
		// trigger click or hover event (they send the same parameters so we share their code)
653
654
		function triggerClickHoverEvent(eventname, e) {
655
656
			var offset = plot.offset();
657
			var canvasX = parseInt(e.pageX - offset.left);
658
			var canvasY =  parseInt(e.pageY - offset.top);
659
			var item = findNearbySlice(canvasX, canvasY);
660
661
			if (options.grid.autoHighlight) {
662
663
				// clear auto-highlights
664
665
				for (var i = 0; i < highlights.length; ++i) {
666
					var h = highlights[i];
667
					if (h.auto == eventname && !(item && h.series == item.series)) {
668
						unhighlight(h.series);
669
					}
670
				}
671
			}
672
673
			// highlight the slice
674
675
			if (item) {
676
				highlight(item.series, eventname);
677
			}
678
679
			// trigger any hover bind events
680
681
			var pos = { pageX: e.pageX, pageY: e.pageY };
682
			target.trigger(eventname, [pos, item]);
683
		}
684
685
		function highlight(s, auto) {
686
			//if (typeof s == "number") {
687
			//	s = series[s];
688
			//}
689
690
			var i = indexOfHighlight(s);
691
692
			if (i == -1) {
693
				highlights.push({ series: s, auto: auto });
694
				plot.triggerRedrawOverlay();
695
			} else if (!auto) {
696
				highlights[i].auto = false;
697
			}
698
		}
699
700
		function unhighlight(s) {
701
			if (s == null) {
702
				highlights = [];
703
				plot.triggerRedrawOverlay();
704
			}
705
706
			//if (typeof s == "number") {
707
			//	s = series[s];
708
			//}
709
710
			var i = indexOfHighlight(s);
711
712
			if (i != -1) {
713
				highlights.splice(i, 1);
714
				plot.triggerRedrawOverlay();
715
			}
716
		}
717
718
		function indexOfHighlight(s) {
719
			for (var i = 0; i < highlights.length; ++i) {
720
				var h = highlights[i];
721
				if (h.series == s)
722
					return i;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
723
			}
724
			return -1;
725
		}
726
727
		function drawOverlay(plot, octx) {
728
729
			var options = plot.getOptions();
730
731
			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
732
733
			octx.save();
734
			octx.translate(centerLeft, centerTop);
735
			octx.scale(1, options.series.pie.tilt);
736
737
			for (var i = 0; i < highlights.length; ++i) {
738
				drawHighlight(highlights[i].series);
739
			}
740
741
			drawDonutHole(octx);
742
743
			octx.restore();
744
745
			function drawHighlight(series) {
746
747
				if (series.angle <= 0 || isNaN(series.angle)) {
748
					return;
749
				}
750
751
				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
752
				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
753
				octx.beginPath();
754
				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
755
					octx.moveTo(0, 0); // Center of the pie
756
				}
757
				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
758
				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
759
				octx.closePath();
760
				octx.fill();
761
			}
762
		}
763
	} // end init (plugin body)
764
765
	// define pie specific options and their default values
766
767
	var options = {
768
		series: {
769
			pie: {
770
				show: false,
771
				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
772
				innerRadius: 0, /* for donut */
773
				startAngle: 3/2,
774
				tilt: 1,
775
				shadow: {
776
					left: 5,	// shadow left offset
777
					top: 15,	// shadow top offset
778
					alpha: 0.02	// shadow alpha
779
				},
780
				offset: {
781
					top: 0,
782
					left: "auto"
783
				},
784
				stroke: {
785
					color: "#fff",
786
					width: 1
787
				},
788
				label: {
789
					show: "auto",
790
					formatter: function(label, slice) {
791
						return "<div style='font-size:13px;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + " " + Math.round(slice.percent) + "%</div>";
792
					},	// formatter function
793
					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
794
					background: {
795
						color: null,
796
						opacity: 0
797
					},
798
					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
799
				},
800
				combine: {
801
					threshold: -1,	// percentage at which to combine little slices into one larger slice
802
					color: null,	// color to give the new slice (auto-generated if null)
803
					label: "Other"	// label to give the new slice
804
				},
805
				highlight: {
806
					//color: "#fff",		// will add this functionality once parseColor is available
807
					opacity: 0.5
808
				}
809
			}
810
		}
811
	};
812
813
	$.plot.plugins.push({
814
		init: init,
815
		options: options,
816
		name: "pie",
817
		version: "1.1"
818
	});
819
820
})(jQuery);
821