62 var REDRAW_ATTEMPTS = 10;
66 var REDRAW_SHRINK = 0.95;
85 plot.hooks.processOptions.push(
function(plot, options) {
86 if (options.series.pie.show) {
88 options.grid.show =
false;
92 if (options.series.pie.label.show ==
"auto") {
93 if (options.legend.show) {
94 options.series.pie.label.show =
false;
96 options.series.pie.label.show =
true;
102 if (options.series.pie.radius ==
"auto") {
103 if (options.series.pie.label.show) {
104 options.series.pie.radius = 3/4;
106 options.series.pie.radius = 1;
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;
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);
126 if (options.grid.clickable) {
127 eventHolder.unbind(
"click").click(onClick);
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);
139 plot.hooks.drawOverlay.push(
function(plot, octx) {
140 var options = plot.getOptions();
141 if (options.series.pie.show) {
142 drawOverlay(plot, octx);
146 plot.hooks.draw.push(
function(plot, newCtx) {
147 var options = plot.getOptions();
148 if (options.series.pie.show) {
153 function processDatapoints(plot, series, datapoints) {
156 canvas = plot.getCanvas();
157 target = $(canvas).parent();
158 options = plot.getOptions();
159 plot.setData(combine(plot.getData()));
163 function combine(data) {
168 color = options.series.pie.combine.color,
173 for (var i = 0; i < data.length; ++i) {
175 var value = data[i].data;
184 if ($.isArray(value) && value.length == 1) {
188 if ($.isArray(value)) {
190 if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
191 value[1] = +value[1];
195 }
else if (!isNaN(parseFloat(value)) && isFinite(value)) {
201 data[i].data = [value];
206 for (var i = 0; i < data.length; ++i) {
207 total += data[i].data[0][1];
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) {
219 color = data[i].color;
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) {
231 color: data[i].color,
232 label: data[i].label,
233 angle: value * Math.PI * 2 / total,
234 percent: value / (total / 100)
240 if (numCombined > 1) {
242 data: [[1, combined]],
244 label: options.series.pie.combine.label,
245 angle: combined * Math.PI * 2 / total,
246 percent: combined / (total / 100)
253 function draw(plot, newCtx) {
259 var canvasWidth = plot.getPlaceholder().width(),
260 canvasHeight = plot.getPlaceholder().height(),
261 legendWidth = target.children().filter(
".legend").children().width() || 0;
290 maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
291 centerTop = canvasHeight / 2 + options.series.pie.offset.top;
292 centerLeft = canvasWidth / 2;
294 if (options.series.pie.offset.left ==
"auto") {
295 if (options.legend.position.match(
"w")) {
296 centerLeft += legendWidth / 2;
298 centerLeft -= legendWidth / 2;
300 if (centerLeft < maxRadius) {
301 centerLeft = maxRadius;
302 }
else if (centerLeft > canvasWidth - maxRadius) {
303 centerLeft = canvasWidth - maxRadius;
306 centerLeft += options.series.pie.offset.left;
309 var slices = plot.getData(),
317 maxRadius *= REDRAW_SHRINK;
321 if (options.series.pie.tilt <= 0.8) {
324 }
while (!drawPie() && attempts < REDRAW_ATTEMPTS)
326 if (attempts >= REDRAW_ATTEMPTS) {
328 target.prepend(
"<div class='error'>Could not draw pie with labels contained inside canvas</div>");
331 if (plot.setSeries && plot.insertLegend) {
332 plot.setSeries(slices);
339 ctx.clearRect(0, 0, canvasWidth, canvasHeight);
340 target.children().filter(
".pieLabel, .pieLabelBackground").remove();
343 function drawShadow() {
345 var shadowLeft = options.series.pie.shadow.left;
346 var shadowTop = options.series.pie.shadow.top;
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;
351 if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
356 ctx.translate(shadowLeft,shadowTop);
357 ctx.globalAlpha = alpha;
358 ctx.fillStyle =
"#000";
362 ctx.translate(centerLeft,centerTop);
363 ctx.scale(1, options.series.pie.tilt);
367 for (var i = 1; i <= edge; i++) {
369 ctx.arc(0, 0, radius, 0, Math.PI * 2,
false);
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;
385 ctx.translate(centerLeft,centerTop);
386 ctx.scale(1, options.series.pie.tilt);
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);
401 if (options.series.pie.stroke.width > 0) {
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);
419 if (options.series.pie.label.show) {
423 function drawSlice(angle, color, fill) {
425 if (angle <= 0 || isNaN(angle)) {
430 ctx.fillStyle = color;
432 ctx.strokeStyle = color;
433 ctx.lineJoin =
"round";
437 if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
442 ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2,
false);
443 ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle,
false);
446 currentAngle += angle;
455 function drawLabels() {
457 var currentAngle = startAngle;
458 var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
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)) {
466 currentAngle += slices[i].angle;
471 function drawLabel(slice, startAngle, index) {
473 if (slice.data[0][1] == 0) {
479 var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
482 text = lf(slice.label, slice);
488 text = plf(text, slice);
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;
495 var html =
"<span class='pieLabel' id='pieLabel" + index +
"' style='position:absolute;top:" + y +
"px;left:" + x +
"px;'>" + text +
"</span>";
498 var label = target.children(
"#pieLabel" + index);
499 var labelTop = (y - label.height() / 2);
500 var labelLeft = (x - label.width() / 2);
502 label.css(
"top", labelTop);
503 label.css(
"left", labelLeft);
507 if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
511 if (options.series.pie.label.background.opacity != 0) {
515 var c = options.series.pie.label.background.color;
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);
535 function drawDonutHole(layer) {
536 if (options.series.pie.innerRadius > 0) {
541 var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
542 layer.globalCompositeOperation =
"destination-out";
544 layer.fillStyle = options.series.pie.stroke.color;
545 layer.arc(0, 0, innerRadius, 0, Math.PI * 2,
false);
554 layer.strokeStyle = options.series.pie.stroke.color;
555 layer.arc(0, 0, innerRadius, 0, Math.PI * 2,
false);
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])
574 function findNearbySlice(mouseX, mouseY) {
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,
581 for (var i = 0; i < slices.length; ++i) {
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);
594 x = mouseX - centerLeft;
595 y = mouseY - centerTop;
597 if (ctx.isPointInPath) {
598 if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
601 datapoint: [s.percent, s.data],
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]],
626 if (isPointInPoly(arrPoly, arrPoint)) {
629 datapoint: [s.percent, s.data],
644 function onMouseMove(e) {
645 triggerClickHoverEvent(
"plothover", e);
648 function onClick(e) {
649 triggerClickHoverEvent(
"plotclick", e);
654 function triggerClickHoverEvent(eventname, e) {
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);
661 if (options.grid.autoHighlight) {
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);
676 highlight(item.series, eventname);
681 var pos = { pageX: e.pageX, pageY: e.pageY };
682 target.trigger(eventname, [pos, item]);
685 function highlight(s,
auto) {
690 var i = indexOfHighlight(s);
693 highlights.push({ series: s,
auto:
auto });
694 plot.triggerRedrawOverlay();
696 highlights[i].auto =
false;
700 function unhighlight(s) {
703 plot.triggerRedrawOverlay();
710 var i = indexOfHighlight(s);
713 highlights.splice(i, 1);
714 plot.triggerRedrawOverlay();
718 function indexOfHighlight(s) {
719 for (var i = 0; i < highlights.length; ++i) {
720 var h = highlights[i];
727 function drawOverlay(plot, octx) {
729 var options = plot.getOptions();
731 var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
734 octx.translate(centerLeft, centerTop);
735 octx.scale(1, options.series.pie.tilt);
737 for (var i = 0; i < highlights.length; ++i) {
738 drawHighlight(highlights[i].series);
745 function drawHighlight(series) {
747 if (series.angle <= 0 || isNaN(series.angle)) {
752 octx.fillStyle =
"rgba(255, 255, 255, " + options.series.pie.highlight.opacity +
")";
754 if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
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);
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>";
813 $.plot.plugins.push({