OpenSencillo  2015.009
Long live the simplicity of PHP
 All Data Structures Namespaces Files Functions Pages
jquery.flot.pie.js
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  // interactive 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);
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) {
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) {
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) {
214  var value = data[i].data[0][1];
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) {
225  var value = data[i].data[0][1];
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;
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) {
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;
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 Interactive 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]))
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;
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);