--- /dev/null
+/*\r
+Flot plugin for rendering pie charts. The plugin assumes the data is \r
+coming is as a single data value for each series, and each of those \r
+values is a positive value or zero (negative numbers don't make \r
+any sense and will cause strange effects). The data values do \r
+NOT need to be passed in as percentage values because it \r
+internally calculates the total and percentages.\r
+\r
+* Created by Brian Medendorp, June 2009\r
+* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars\r
+\r
+* Changes:\r
+ 2009-10-22: lineJoin set to round\r
+ 2009-10-23: IE full circle fix, donut\r
+ 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera\r
+ 2009-11-17: Added IE hover capability submitted by Anthony Aragues\r
+ 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well)\r
+ \r
+\r
+Available options are:\r
+series: {\r
+ pie: {\r
+ show: true/false\r
+ radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'\r
+ innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect\r
+ 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\r
+ tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)\r
+ offset: {\r
+ top: integer value to move the pie up or down\r
+ left: integer value to move the pie left or right, or 'auto'\r
+ },\r
+ stroke: {\r
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')\r
+ width: integer pixel width of the stroke\r
+ },\r
+ label: {\r
+ show: true/false, or 'auto'\r
+ formatter: a user-defined function that modifies the text/style of the label text\r
+ radius: 0-1 for percentage of fullsize, or a specified pixel length\r
+ background: {\r
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')\r
+ opacity: 0-1\r
+ },\r
+ threshold: 0-1 for the percentage value at which to hide labels (if they're too small)\r
+ },\r
+ combine: {\r
+ threshold: 0-1 for the percentage value at which to combine slices (if they're too small)\r
+ 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\r
+ label: any text value of what the combined slice should be labeled\r
+ }\r
+ highlight: {\r
+ opacity: 0-1\r
+ }\r
+ }\r
+}\r
+\r
+More detail and specific examples can be found in the included HTML file.\r
+\r
+*/\r
+\r
+(function ($) \r
+{\r
+ function init(plot) // this is the "body" of the plugin\r
+ {\r
+ var canvas = null;\r
+ var target = null;\r
+ var maxRadius = null;\r
+ var centerLeft = null;\r
+ var centerTop = null;\r
+ var total = 0;\r
+ var redraw = true;\r
+ var redrawAttempts = 10;\r
+ var shrink = 0.95;\r
+ var legendWidth = 0;\r
+ var processed = false;\r
+ var raw = false;\r
+ \r
+ // interactive variables \r
+ var highlights = []; \r
+ \r
+ // add hook to determine if pie plugin in enabled, and then perform necessary operations\r
+ plot.hooks.processOptions.push(checkPieEnabled);\r
+ plot.hooks.bindEvents.push(bindEvents); \r
+\r
+ // check to see if the pie plugin is enabled\r
+ function checkPieEnabled(plot, options)\r
+ {\r
+ if (options.series.pie.show)\r
+ {\r
+ //disable grid\r
+ options.grid.show = false;\r
+ \r
+ // set labels.show\r
+ if (options.series.pie.label.show=='auto')\r
+ if (options.legend.show)\r
+ options.series.pie.label.show = false;\r
+ else\r
+ options.series.pie.label.show = true;\r
+ \r
+ // set radius\r
+ if (options.series.pie.radius=='auto')\r
+ if (options.series.pie.label.show)\r
+ options.series.pie.radius = 3/4;\r
+ else\r
+ options.series.pie.radius = 1;\r
+ \r
+ // ensure sane tilt\r
+ if (options.series.pie.tilt>1)\r
+ options.series.pie.tilt=1;\r
+ if (options.series.pie.tilt<0)\r
+ options.series.pie.tilt=0;\r
+ \r
+ // add processData hook to do transformations on the data\r
+ plot.hooks.processDatapoints.push(processDatapoints);\r
+ plot.hooks.drawOverlay.push(drawOverlay); \r
+ \r
+ // add draw hook\r
+ plot.hooks.draw.push(draw);\r
+ }\r
+ }\r
+ \r
+ // bind hoverable events\r
+ function bindEvents(plot, eventHolder) \r
+ { \r
+ var options = plot.getOptions();\r
+ \r
+ if (options.series.pie.show && options.grid.hoverable)\r
+ eventHolder.unbind('mousemove').mousemove(onMouseMove);\r
+ \r
+ if (options.series.pie.show && options.grid.clickable)\r
+ eventHolder.unbind('click').click(onClick);\r
+ } \r
+ \r
+\r
+ // debugging function that prints out an object\r
+ function alertObject(obj)\r
+ {\r
+ var msg = '';\r
+ function traverse(obj, depth)\r
+ {\r
+ if (!depth)\r
+ depth = 0;\r
+ for (var i = 0; i < obj.length; ++i)\r
+ {\r
+ for (var j=0; j<depth; j++)\r
+ msg += '\t';\r
+ \r
+ if( typeof obj[i] == "object")\r
+ { // its an object\r
+ msg += ''+i+':\n';\r
+ traverse(obj[i], depth+1);\r
+ }\r
+ else\r
+ { // its a value\r
+ msg += ''+i+': '+obj[i]+'\n';\r
+ }\r
+ }\r
+ }\r
+ traverse(obj);\r
+ alert(msg);\r
+ }\r
+ \r
+ function calcTotal(data)\r
+ {\r
+ for (var i = 0; i < data.length; ++i)\r
+ {\r
+ var item = parseFloat(data[i].data[0][1]);\r
+ if (item)\r
+ total += item;\r
+ }\r
+ } \r
+ \r
+ function processDatapoints(plot, series, data, datapoints) \r
+ { \r
+ if (!processed)\r
+ {\r
+ processed = true;\r
+ \r
+ canvas = plot.getCanvas();\r
+ target = $(canvas).parent();\r
+ options = plot.getOptions();\r
+ \r
+ plot.setData(combine(plot.getData()));\r
+ }\r
+ }\r
+ \r
+ function setupPie()\r
+ {\r
+ legendWidth = target.children().filter('.legend').children().width();\r
+ \r
+ // calculate maximum radius and center point\r
+ maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;\r
+ centerTop = (canvas.height/2)+options.series.pie.offset.top;\r
+ centerLeft = (canvas.width/2);\r
+ \r
+ if (options.series.pie.offset.left=='auto')\r
+ if (options.legend.position.match('w'))\r
+ centerLeft += legendWidth/2;\r
+ else\r
+ centerLeft -= legendWidth/2;\r
+ else\r
+ centerLeft += options.series.pie.offset.left;\r
+ \r
+ if (centerLeft<maxRadius)\r
+ centerLeft = maxRadius;\r
+ else if (centerLeft>canvas.width-maxRadius)\r
+ centerLeft = canvas.width-maxRadius;\r
+ }\r
+ \r
+ function fixData(data)\r
+ {\r
+ for (var i = 0; i < data.length; ++i)\r
+ {\r
+ if (typeof(data[i].data)=='number')\r
+ data[i].data = [[1,data[i].data]];\r
+ else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')\r
+ {\r
+ if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')\r
+ data[i].label = data[i].data.label; // fix weirdness coming from flot\r
+ data[i].data = [[1,0]];\r
+ \r
+ }\r
+ }\r
+ return data;\r
+ }\r
+ \r
+ function combine(data)\r
+ {\r
+ data = fixData(data);\r
+ calcTotal(data);\r
+ var combined = 0;\r
+ var numCombined = 0;\r
+ var color = options.series.pie.combine.color;\r
+ \r
+ var newdata = [];\r
+ for (var i = 0; i < data.length; ++i)\r
+ {\r
+ // make sure its a number\r
+ data[i].data[0][1] = parseFloat(data[i].data[0][1]);\r
+ if (!data[i].data[0][1])\r
+ data[i].data[0][1] = 0;\r
+ \r
+ if (data[i].data[0][1]/total<=options.series.pie.combine.threshold)\r
+ {\r
+ combined += data[i].data[0][1];\r
+ numCombined++;\r
+ if (!color)\r
+ color = data[i].color;\r
+ } \r
+ else\r
+ {\r
+ newdata.push({\r
+ data: [[1,data[i].data[0][1]]], \r
+ color: data[i].color, \r
+ label: data[i].label,\r
+ angle: (data[i].data[0][1]*(Math.PI*2))/total,\r
+ percent: (data[i].data[0][1]/total*100)\r
+ });\r
+ }\r
+ }\r
+ if (numCombined>0)\r
+ newdata.push({\r
+ data: [[1,combined]], \r
+ color: color, \r
+ label: options.series.pie.combine.label,\r
+ angle: (combined*(Math.PI*2))/total,\r
+ percent: (combined/total*100)\r
+ });\r
+ return newdata;\r
+ } \r
+ \r
+ function draw(plot, newCtx)\r
+ {\r
+ if (!target) return; // if no series were passed\r
+ ctx = newCtx;\r
+ \r
+ setupPie();\r
+ var slices = plot.getData();\r
+ \r
+ var attempts = 0;\r
+ while (redraw && attempts<redrawAttempts)\r
+ {\r
+ redraw = false;\r
+ if (attempts>0)\r
+ maxRadius *= shrink;\r
+ attempts += 1;\r
+ clear();\r
+ if (options.series.pie.tilt<=0.8)\r
+ drawShadow();\r
+ drawPie();\r
+ }\r
+ if (attempts >= redrawAttempts) {\r
+ clear();\r
+ target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>');\r
+ }\r
+ \r
+ if ( plot.setSeries && plot.insertLegend )\r
+ {\r
+ plot.setSeries(slices);\r
+ plot.insertLegend();\r
+ }\r
+ \r
+ // we're actually done at this point, just defining internal functions at this point\r
+ \r
+ function clear()\r
+ {\r
+ ctx.clearRect(0,0,canvas.width,canvas.height);\r
+ target.children().filter('.pieLabel, .pieLabelBackground').remove();\r
+ }\r
+ \r
+ function drawShadow()\r
+ {\r
+ var shadowLeft = 5;\r
+ var shadowTop = 15;\r
+ var edge = 10;\r
+ var alpha = 0.02;\r
+ \r
+ // set radius\r
+ if (options.series.pie.radius>1)\r
+ var radius = options.series.pie.radius;\r
+ else\r
+ var radius = maxRadius * options.series.pie.radius;\r
+ \r
+ if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge)\r
+ return; // shadow would be outside canvas, so don't draw it\r
+ \r
+ ctx.save();\r
+ ctx.translate(shadowLeft,shadowTop);\r
+ ctx.globalAlpha = alpha;\r
+ ctx.fillStyle = '#000';\r
+\r
+ // center and rotate to starting position\r
+ ctx.translate(centerLeft,centerTop);\r
+ ctx.scale(1, options.series.pie.tilt);\r
+ \r
+ //radius -= edge;\r
+ for (var i=1; i<=edge; i++)\r
+ {\r
+ ctx.beginPath();\r
+ ctx.arc(0,0,radius,0,Math.PI*2,false);\r
+ ctx.fill();\r
+ radius -= i;\r
+ } \r
+ \r
+ ctx.restore();\r
+ }\r
+ \r
+ function drawPie()\r
+ {\r
+ startAngle = Math.PI*options.series.pie.startAngle;\r
+ \r
+ // set radius\r
+ if (options.series.pie.radius>1)\r
+ var radius = options.series.pie.radius;\r
+ else\r
+ var radius = maxRadius * options.series.pie.radius;\r
+ \r
+ // center and rotate to starting position\r
+ ctx.save();\r
+ ctx.translate(centerLeft,centerTop);\r
+ ctx.scale(1, options.series.pie.tilt);\r
+ //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera\r
+ \r
+ // draw slices\r
+ ctx.save();\r
+ var currentAngle = startAngle;\r
+ for (var i = 0; i < slices.length; ++i)\r
+ {\r
+ slices[i].startAngle = currentAngle;\r
+ drawSlice(slices[i].angle, slices[i].color, true);\r
+ }\r
+ ctx.restore();\r
+ \r
+ // draw slice outlines\r
+ ctx.save();\r
+ ctx.lineWidth = options.series.pie.stroke.width;\r
+ currentAngle = startAngle;\r
+ for (var i = 0; i < slices.length; ++i)\r
+ drawSlice(slices[i].angle, options.series.pie.stroke.color, false);\r
+ ctx.restore();\r
+ \r
+ // draw donut hole\r
+ drawDonutHole(ctx);\r
+ \r
+ // draw labels\r
+ if (options.series.pie.label.show)\r
+ drawLabels();\r
+ \r
+ // restore to original state\r
+ ctx.restore();\r
+ \r
+ function drawSlice(angle, color, fill)\r
+ { \r
+ if (angle<=0)\r
+ return;\r
+ \r
+ if (fill)\r
+ ctx.fillStyle = color;\r
+ else\r
+ {\r
+ ctx.strokeStyle = color;\r
+ ctx.lineJoin = 'round';\r
+ }\r
+ \r
+ ctx.beginPath();\r
+ if (Math.abs(angle - Math.PI*2) > 0.000000001)\r
+ ctx.moveTo(0,0); // Center of the pie\r
+ else if ($.browser.msie)\r
+ angle -= 0.0001;\r
+ //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera\r
+ ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false);\r
+ ctx.closePath();\r
+ //ctx.rotate(angle); // This doesn't work properly in Opera\r
+ currentAngle += angle;\r
+ \r
+ if (fill)\r
+ ctx.fill();\r
+ else\r
+ ctx.stroke();\r
+ }\r
+ \r
+ function drawLabels()\r
+ {\r
+ var currentAngle = startAngle;\r
+ \r
+ // set radius\r
+ if (options.series.pie.label.radius>1)\r
+ var radius = options.series.pie.label.radius;\r
+ else\r
+ var radius = maxRadius * options.series.pie.label.radius;\r
+ \r
+ for (var i = 0; i < slices.length; ++i)\r
+ {\r
+ if (slices[i].percent >= options.series.pie.label.threshold*100)\r
+ drawLabel(slices[i], currentAngle, i);\r
+ currentAngle += slices[i].angle;\r
+ }\r
+ \r
+ function drawLabel(slice, startAngle, index)\r
+ {\r
+ if (slice.data[0][1]==0)\r
+ return;\r
+ \r
+ // format label text\r
+ var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;\r
+ if (lf)\r
+ text = lf(slice.label, slice);\r
+ else\r
+ text = slice.label;\r
+ if (plf)\r
+ text = plf(text, slice);\r
+ \r
+ var halfAngle = ((startAngle+slice.angle) + startAngle)/2;\r
+ var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);\r
+ var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;\r
+ \r
+ var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">' + text + "</span>";\r
+ target.append(html);\r
+ var label = target.children('#pieLabel'+index);\r
+ var labelTop = (y - label.height()/2);\r
+ var labelLeft = (x - label.width()/2);\r
+ label.css('top', labelTop);\r
+ label.css('left', labelLeft);\r
+ \r
+ // check to make sure that the label is not outside the canvas\r
+ if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)\r
+ redraw = true;\r
+ \r
+ if (options.series.pie.label.background.opacity != 0) {\r
+ // put in the transparent background separately to avoid blended labels and label boxes\r
+ var c = options.series.pie.label.background.color;\r
+ if (c == null) {\r
+ c = slice.color;\r
+ }\r
+ var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;';\r
+ $('<div class="pieLabelBackground" style="position:absolute;width:' + label.width() + 'px;height:' + label.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity);\r
+ }\r
+ } // end individual label function\r
+ } // end drawLabels function\r
+ } // end drawPie function\r
+ } // end draw function\r
+ \r
+ // Placed here because it needs to be accessed from multiple locations \r
+ function drawDonutHole(layer)\r
+ {\r
+ // draw donut hole\r
+ if(options.series.pie.innerRadius > 0)\r
+ {\r
+ // subtract the center\r
+ layer.save();\r
+ innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;\r
+ layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color\r
+ layer.beginPath();\r
+ layer.fillStyle = options.series.pie.stroke.color;\r
+ layer.arc(0,0,innerRadius,0,Math.PI*2,false);\r
+ layer.fill();\r
+ layer.closePath();\r
+ layer.restore();\r
+ \r
+ // add inner stroke\r
+ layer.save();\r
+ layer.beginPath();\r
+ layer.strokeStyle = options.series.pie.stroke.color;\r
+ layer.arc(0,0,innerRadius,0,Math.PI*2,false);\r
+ layer.stroke();\r
+ layer.closePath();\r
+ layer.restore();\r
+ // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.\r
+ }\r
+ }\r
+ \r
+ //-- Additional Interactive related functions --\r
+ \r
+ function isPointInPoly(poly, pt)\r
+ {\r
+ for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)\r
+ ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))\r
+ && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])\r
+ && (c = !c);\r
+ return c;\r
+ }\r
+ \r
+ function findNearbySlice(mouseX, mouseY)\r
+ {\r
+ var slices = plot.getData(),\r
+ options = plot.getOptions(),\r
+ radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;\r
+ \r
+ for (var i = 0; i < slices.length; ++i) \r
+ {\r
+ var s = slices[i]; \r
+ \r
+ if(s.pie.show)\r
+ {\r
+ ctx.save();\r
+ ctx.beginPath();\r
+ ctx.moveTo(0,0); // Center of the pie\r
+ //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.\r
+ ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false);\r
+ ctx.closePath();\r
+ x = mouseX-centerLeft;\r
+ y = mouseY-centerTop;\r
+ if(ctx.isPointInPath)\r
+ {\r
+ if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop))\r
+ {\r
+ //alert('found slice!');\r
+ ctx.restore();\r
+ return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // excanvas for IE doesn;t support isPointInPath, this is a workaround. \r
+ p1X = (radius * Math.cos(s.startAngle));\r
+ p1Y = (radius * Math.sin(s.startAngle));\r
+ p2X = (radius * Math.cos(s.startAngle+(s.angle/4)));\r
+ p2Y = (radius * Math.sin(s.startAngle+(s.angle/4)));\r
+ p3X = (radius * Math.cos(s.startAngle+(s.angle/2)));\r
+ p3Y = (radius * Math.sin(s.startAngle+(s.angle/2)));\r
+ p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5)));\r
+ p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5)));\r
+ p5X = (radius * Math.cos(s.startAngle+s.angle));\r
+ p5Y = (radius * Math.sin(s.startAngle+s.angle));\r
+ arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]];\r
+ arrPoint = [x,y];\r
+ // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?\r
+ if(isPointInPoly(arrPoly, arrPoint))\r
+ {\r
+ ctx.restore();\r
+ return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};\r
+ } \r
+ }\r
+ ctx.restore();\r
+ }\r
+ }\r
+ \r
+ return null;\r
+ }\r
+\r
+ function onMouseMove(e) \r
+ {\r
+ triggerClickHoverEvent('plothover', e);\r
+ }\r
+ \r
+ function onClick(e) \r
+ {\r
+ triggerClickHoverEvent('plotclick', e);\r
+ }\r
+\r
+ // trigger click or hover event (they send the same parameters so we share their code)\r
+ function triggerClickHoverEvent(eventname, e) \r
+ {\r
+ var offset = plot.offset(),\r
+ canvasX = parseInt(e.pageX - offset.left),\r
+ canvasY = parseInt(e.pageY - offset.top),\r
+ item = findNearbySlice(canvasX, canvasY);\r
+ \r
+ if (options.grid.autoHighlight) \r
+ {\r
+ // clear auto-highlights\r
+ for (var i = 0; i < highlights.length; ++i) \r
+ {\r
+ var h = highlights[i];\r
+ if (h.auto == eventname && !(item && h.series == item.series))\r
+ unhighlight(h.series);\r
+ }\r
+ }\r
+ \r
+ // highlight the slice\r
+ if (item) \r
+ highlight(item.series, eventname);\r
+ \r
+ // trigger any hover bind events\r
+ var pos = { pageX: e.pageX, pageY: e.pageY };\r
+ target.trigger(eventname, [ pos, item ]); \r
+ }\r
+\r
+ function highlight(s, auto) \r
+ {\r
+ if (typeof s == "number")\r
+ s = series[s];\r
+\r
+ var i = indexOfHighlight(s);\r
+ if (i == -1) \r
+ {\r
+ highlights.push({ series: s, auto: auto });\r
+ plot.triggerRedrawOverlay();\r
+ }\r
+ else if (!auto)\r
+ highlights[i].auto = false;\r
+ }\r
+\r
+ function unhighlight(s) \r
+ {\r
+ if (s == null) \r
+ {\r
+ highlights = [];\r
+ plot.triggerRedrawOverlay();\r
+ }\r
+ \r
+ if (typeof s == "number")\r
+ s = series[s];\r
+\r
+ var i = indexOfHighlight(s);\r
+ if (i != -1) \r
+ {\r
+ highlights.splice(i, 1);\r
+ plot.triggerRedrawOverlay();\r
+ }\r
+ }\r
+\r
+ function indexOfHighlight(s) \r
+ {\r
+ for (var i = 0; i < highlights.length; ++i) \r
+ {\r
+ var h = highlights[i];\r
+ if (h.series == s)\r
+ return i;\r
+ }\r
+ return -1;\r
+ }\r
+\r
+ function drawOverlay(plot, octx) \r
+ {\r
+ //alert(options.series.pie.radius);\r
+ var options = plot.getOptions();\r
+ //alert(options.series.pie.radius);\r
+ \r
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;\r
+\r
+ octx.save();\r
+ octx.translate(centerLeft, centerTop);\r
+ octx.scale(1, options.series.pie.tilt);\r
+ \r
+ for (i = 0; i < highlights.length; ++i) \r
+ drawHighlight(highlights[i].series);\r
+ \r
+ drawDonutHole(octx);\r
+\r
+ octx.restore();\r
+\r
+ function drawHighlight(series) \r
+ {\r
+ if (series.angle < 0) return;\r
+ \r
+ //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();\r
+ octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor\r
+ \r
+ octx.beginPath();\r
+ if (Math.abs(series.angle - Math.PI*2) > 0.000000001)\r
+ octx.moveTo(0,0); // Center of the pie\r
+ octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false);\r
+ octx.closePath();\r
+ octx.fill();\r
+ }\r
+ \r
+ } \r
+ \r
+ } // end init (plugin body)\r
+ \r
+ // define pie specific options and their default values\r
+ var options = {\r
+ series: {\r
+ pie: {\r
+ show: false,\r
+ radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)\r
+ innerRadius:0, /* for donut */\r
+ startAngle: 3/2,\r
+ tilt: 1,\r
+ offset: {\r
+ top: 0,\r
+ left: 'auto'\r
+ },\r
+ stroke: {\r
+ color: '#FFF',\r
+ width: 1\r
+ },\r
+ label: {\r
+ show: 'auto',\r
+ formatter: function(label, slice){\r
+ return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+slice.color+';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>';\r
+ }, // formatter function\r
+ radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)\r
+ background: {\r
+ color: null,\r
+ opacity: 0\r
+ },\r
+ threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)\r
+ },\r
+ combine: {\r
+ threshold: -1, // percentage at which to combine little slices into one larger slice\r
+ color: null, // color to give the new slice (auto-generated if null)\r
+ label: 'Other' // label to give the new slice\r
+ },\r
+ highlight: {\r
+ //color: '#FFF', // will add this functionality once parseColor is available\r
+ opacity: 0.5\r
+ }\r
+ }\r
+ }\r
+ };\r
+ \r
+ $.plot.plugins.push({\r
+ init: init,\r
+ options: options,\r
+ name: "pie",\r
+ version: "1.0"\r
+ });\r
+})(jQuery);\r