/* Flot plugin for rendering pie charts. 
 | 
  
 | 
Copyright (c) 2007-2014 IOLA and Ole Laursen. 
 | 
Licensed under the MIT license. 
 | 
  
 | 
The plugin assumes that each series has a single data value, and that each 
 | 
value is a positive integer or zero.  Negative numbers don't make sense for a 
 | 
pie chart, and have unpredictable results.  The values do NOT need to be 
 | 
passed in as percentages; the plugin will calculate the total and per-slice 
 | 
percentages internally. 
 | 
  
 | 
* Created by Brian Medendorp 
 | 
  
 | 
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars 
 | 
  
 | 
The plugin supports these options: 
 | 
  
 | 
    series: { 
 | 
        pie: { 
 | 
            show: true/false 
 | 
            radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' 
 | 
            innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect 
 | 
            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 
 | 
            tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) 
 | 
            offset: { 
 | 
                top: integer value to move the pie up or down 
 | 
                left: integer value to move the pie left or right, or 'auto' 
 | 
            }, 
 | 
            stroke: { 
 | 
                color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') 
 | 
                width: integer pixel width of the stroke 
 | 
            }, 
 | 
            label: { 
 | 
                show: true/false, or 'auto' 
 | 
                formatter:  a user-defined function that modifies the text/style of the label text 
 | 
                radius: 0-1 for percentage of fullsize, or a specified pixel length 
 | 
                background: { 
 | 
                    color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') 
 | 
                    opacity: 0-1 
 | 
                }, 
 | 
                threshold: 0-1 for the percentage value at which to hide labels (if they're too small) 
 | 
            }, 
 | 
            combine: { 
 | 
                threshold: 0-1 for the percentage value at which to combine slices (if they're too small) 
 | 
                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 
 | 
                label: any text value of what the combined slice should be labeled 
 | 
            } 
 | 
            highlight: { 
 | 
                opacity: 0-1 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
More detail and specific examples can be found in the included HTML file. 
 | 
  
 | 
*/ 
 | 
  
 | 
(function($) { 
 | 
  
 | 
    // Maximum redraw attempts when fitting labels within the plot 
 | 
  
 | 
    var REDRAW_ATTEMPTS = 10; 
 | 
  
 | 
    // Factor by which to shrink the pie when fitting labels within the plot 
 | 
  
 | 
    var REDRAW_SHRINK = 0.95; 
 | 
  
 | 
    function init(plot) { 
 | 
  
 | 
        var canvas = null, 
 | 
            target = null, 
 | 
            options = null, 
 | 
            maxRadius = null, 
 | 
            centerLeft = null, 
 | 
            centerTop = null, 
 | 
            processed = false, 
 | 
            ctx = null; 
 | 
  
 | 
        // interactive variables 
 | 
  
 | 
        var highlights = []; 
 | 
  
 | 
        // add hook to determine if pie plugin in enabled, and then perform necessary operations 
 | 
  
 | 
        plot.hooks.processOptions.push(function(plot, options) { 
 | 
            if (options.series.pie.show) { 
 | 
  
 | 
                options.grid.show = false; 
 | 
  
 | 
                // set labels.show 
 | 
  
 | 
                if (options.series.pie.label.show == "auto") { 
 | 
                    if (options.legend.show) { 
 | 
                        options.series.pie.label.show = false; 
 | 
                    } else { 
 | 
                        options.series.pie.label.show = true; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                // set radius 
 | 
  
 | 
                if (options.series.pie.radius == "auto") { 
 | 
                    if (options.series.pie.label.show) { 
 | 
                        options.series.pie.radius = 3/4; 
 | 
                    } else { 
 | 
                        options.series.pie.radius = 1; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                // ensure sane tilt 
 | 
  
 | 
                if (options.series.pie.tilt > 1) { 
 | 
                    options.series.pie.tilt = 1; 
 | 
                } else if (options.series.pie.tilt < 0) { 
 | 
                    options.series.pie.tilt = 0; 
 | 
                } 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        plot.hooks.bindEvents.push(function(plot, eventHolder) { 
 | 
            var options = plot.getOptions(); 
 | 
            if (options.series.pie.show) { 
 | 
                if (options.grid.hoverable) { 
 | 
                    eventHolder.unbind("mousemove").mousemove(onMouseMove); 
 | 
                } 
 | 
                if (options.grid.clickable) { 
 | 
                    eventHolder.unbind("click").click(onClick); 
 | 
                } 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { 
 | 
            var options = plot.getOptions(); 
 | 
            if (options.series.pie.show) { 
 | 
                processDatapoints(plot, series, data, datapoints); 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        plot.hooks.drawOverlay.push(function(plot, octx) { 
 | 
            var options = plot.getOptions(); 
 | 
            if (options.series.pie.show) { 
 | 
                drawOverlay(plot, octx); 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        plot.hooks.draw.push(function(plot, newCtx) { 
 | 
            var options = plot.getOptions(); 
 | 
            if (options.series.pie.show) { 
 | 
                draw(plot, newCtx); 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        function processDatapoints(plot, series, datapoints) { 
 | 
            if (!processed)    { 
 | 
                processed = true; 
 | 
                canvas = plot.getCanvas(); 
 | 
                target = $(canvas).parent(); 
 | 
                options = plot.getOptions(); 
 | 
                plot.setData(combine(plot.getData())); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        function combine(data) { 
 | 
  
 | 
            var total = 0, 
 | 
                combined = 0, 
 | 
                numCombined = 0, 
 | 
                color = options.series.pie.combine.color, 
 | 
                newdata = []; 
 | 
  
 | 
            // Fix up the raw data from Flot, ensuring the data is numeric 
 | 
  
 | 
            for (var i = 0; i < data.length; ++i) { 
 | 
  
 | 
                var value = data[i].data; 
 | 
  
 | 
                // If the data is an array, we'll assume that it's a standard 
 | 
                // Flot x-y pair, and are concerned only with the second value. 
 | 
  
 | 
                // Note how we use the original array, rather than creating a 
 | 
                // new one; this is more efficient and preserves any extra data 
 | 
                // that the user may have stored in higher indexes. 
 | 
  
 | 
                if ($.isArray(value) && value.length == 1) { 
 | 
                    value = value[0]; 
 | 
                } 
 | 
  
 | 
                if ($.isArray(value)) { 
 | 
                    // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 
 | 
                    if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { 
 | 
                        value[1] = +value[1]; 
 | 
                    } else { 
 | 
                        value[1] = 0; 
 | 
                    } 
 | 
                } else if (!isNaN(parseFloat(value)) && isFinite(value)) { 
 | 
                    value = [1, +value]; 
 | 
                } else { 
 | 
                    value = [1, 0]; 
 | 
                } 
 | 
  
 | 
                data[i].data = [value]; 
 | 
            } 
 | 
  
 | 
            // Sum up all the slices, so we can calculate percentages for each 
 | 
  
 | 
            for (var i = 0; i < data.length; ++i) { 
 | 
                total += data[i].data[0][1]; 
 | 
            } 
 | 
  
 | 
            // Count the number of slices with percentages below the combine 
 | 
            // threshold; if it turns out to be just one, we won't combine. 
 | 
  
 | 
            for (var i = 0; i < data.length; ++i) { 
 | 
                var value = data[i].data[0][1]; 
 | 
                if (value / total <= options.series.pie.combine.threshold) { 
 | 
                    combined += value; 
 | 
                    numCombined++; 
 | 
                    if (!color) { 
 | 
                        color = data[i].color; 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
  
 | 
            for (var i = 0; i < data.length; ++i) { 
 | 
                var value = data[i].data[0][1]; 
 | 
                if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { 
 | 
                    newdata.push( 
 | 
                        $.extend(data[i], {     /* extend to allow keeping all other original data values 
 | 
                                                   and using them e.g. in labelFormatter. */ 
 | 
                            data: [[1, value]], 
 | 
                            color: data[i].color, 
 | 
                            label: data[i].label, 
 | 
                            angle: value * Math.PI * 2 / total, 
 | 
                            percent: value / (total / 100) 
 | 
                        }) 
 | 
                    ); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            if (numCombined > 1) { 
 | 
                newdata.push({ 
 | 
                    data: [[1, combined]], 
 | 
                    color: color, 
 | 
                    label: options.series.pie.combine.label, 
 | 
                    angle: combined * Math.PI * 2 / total, 
 | 
                    percent: combined / (total / 100) 
 | 
                }); 
 | 
            } 
 | 
  
 | 
            return newdata; 
 | 
        } 
 | 
  
 | 
        function draw(plot, newCtx) { 
 | 
  
 | 
            if (!target) { 
 | 
                return; // if no series were passed 
 | 
            } 
 | 
  
 | 
            var canvasWidth = plot.getPlaceholder().width(), 
 | 
                canvasHeight = plot.getPlaceholder().height(), 
 | 
                legendWidth = target.children().filter(".legend").children().width() || 0; 
 | 
  
 | 
            ctx = newCtx; 
 | 
  
 | 
            // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! 
 | 
  
 | 
            // When combining smaller slices into an 'other' slice, we need to 
 | 
            // add a new series.  Since Flot gives plugins no way to modify the 
 | 
            // list of series, the pie plugin uses a hack where the first call 
 | 
            // to processDatapoints results in a call to setData with the new 
 | 
            // list of series, then subsequent processDatapoints do nothing. 
 | 
  
 | 
            // The plugin-global 'processed' flag is used to control this hack; 
 | 
            // it starts out false, and is set to true after the first call to 
 | 
            // processDatapoints. 
 | 
  
 | 
            // Unfortunately this turns future setData calls into no-ops; they 
 | 
            // call processDatapoints, the flag is true, and nothing happens. 
 | 
  
 | 
            // To fix this we'll set the flag back to false here in draw, when 
 | 
            // all series have been processed, so the next sequence of calls to 
 | 
            // processDatapoints once again starts out with a slice-combine. 
 | 
            // This is really a hack; in 0.9 we need to give plugins a proper 
 | 
            // way to modify series before any processing begins. 
 | 
  
 | 
            processed = false; 
 | 
  
 | 
            // calculate maximum radius and center point 
 | 
  
 | 
            maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; 
 | 
            centerTop = canvasHeight / 2 + options.series.pie.offset.top; 
 | 
            centerLeft = canvasWidth / 2; 
 | 
  
 | 
            if (options.series.pie.offset.left == "auto") { 
 | 
                if (options.legend.position.match("w")) { 
 | 
                    centerLeft += legendWidth / 2; 
 | 
                } else { 
 | 
                    centerLeft -= legendWidth / 2; 
 | 
                } 
 | 
                if (centerLeft < maxRadius) { 
 | 
                    centerLeft = maxRadius; 
 | 
                } else if (centerLeft > canvasWidth - maxRadius) { 
 | 
                    centerLeft = canvasWidth - maxRadius; 
 | 
                } 
 | 
            } else { 
 | 
                centerLeft += options.series.pie.offset.left; 
 | 
            } 
 | 
  
 | 
            var slices = plot.getData(), 
 | 
                attempts = 0; 
 | 
  
 | 
            // Keep shrinking the pie's radius until drawPie returns true, 
 | 
            // indicating that all the labels fit, or we try too many times. 
 | 
  
 | 
            do { 
 | 
                if (attempts > 0) { 
 | 
                    maxRadius *= REDRAW_SHRINK; 
 | 
                } 
 | 
                attempts += 1; 
 | 
                clear(); 
 | 
                if (options.series.pie.tilt <= 0.8) { 
 | 
                    drawShadow(); 
 | 
                } 
 | 
            } while (!drawPie() && attempts < REDRAW_ATTEMPTS) 
 | 
  
 | 
            if (attempts >= REDRAW_ATTEMPTS) { 
 | 
                clear(); 
 | 
                target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); 
 | 
            } 
 | 
  
 | 
            if (plot.setSeries && plot.insertLegend) { 
 | 
                plot.setSeries(slices); 
 | 
                plot.insertLegend(); 
 | 
            } 
 | 
  
 | 
            // we're actually done at this point, just defining internal functions at this point 
 | 
  
 | 
            function clear() { 
 | 
                ctx.clearRect(0, 0, canvasWidth, canvasHeight); 
 | 
                target.children().filter(".pieLabel, .pieLabelBackground").remove(); 
 | 
            } 
 | 
  
 | 
            function drawShadow() { 
 | 
  
 | 
                var shadowLeft = options.series.pie.shadow.left; 
 | 
                var shadowTop = options.series.pie.shadow.top; 
 | 
                var edge = 10; 
 | 
                var alpha = options.series.pie.shadow.alpha; 
 | 
                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; 
 | 
  
 | 
                if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { 
 | 
                    return;    // shadow would be outside canvas, so don't draw it 
 | 
                } 
 | 
  
 | 
                ctx.save(); 
 | 
                ctx.translate(shadowLeft,shadowTop); 
 | 
                ctx.globalAlpha = alpha; 
 | 
                ctx.fillStyle = "#000"; 
 | 
  
 | 
                // center and rotate to starting position 
 | 
  
 | 
                ctx.translate(centerLeft,centerTop); 
 | 
                ctx.scale(1, options.series.pie.tilt); 
 | 
  
 | 
                //radius -= edge; 
 | 
  
 | 
                for (var i = 1; i <= edge; i++) { 
 | 
                    ctx.beginPath(); 
 | 
                    ctx.arc(0, 0, radius, 0, Math.PI * 2, false); 
 | 
                    ctx.fill(); 
 | 
                    radius -= i; 
 | 
                } 
 | 
  
 | 
                ctx.restore(); 
 | 
            } 
 | 
  
 | 
            function drawPie() { 
 | 
  
 | 
                var startAngle = Math.PI * options.series.pie.startAngle; 
 | 
                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; 
 | 
  
 | 
                // center and rotate to starting position 
 | 
  
 | 
                ctx.save(); 
 | 
                ctx.translate(centerLeft,centerTop); 
 | 
                ctx.scale(1, options.series.pie.tilt); 
 | 
                //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera 
 | 
  
 | 
                // draw slices 
 | 
  
 | 
                ctx.save(); 
 | 
                var currentAngle = startAngle; 
 | 
                for (var i = 0; i < slices.length; ++i) { 
 | 
                    slices[i].startAngle = currentAngle; 
 | 
                    drawSlice(slices[i].angle, slices[i].color, true); 
 | 
                } 
 | 
                ctx.restore(); 
 | 
  
 | 
                // draw slice outlines 
 | 
  
 | 
                if (options.series.pie.stroke.width > 0) { 
 | 
                    ctx.save(); 
 | 
                    ctx.lineWidth = options.series.pie.stroke.width; 
 | 
                    currentAngle = startAngle; 
 | 
                    for (var i = 0; i < slices.length; ++i) { 
 | 
                        drawSlice(slices[i].angle, options.series.pie.stroke.color, false); 
 | 
                    } 
 | 
                    ctx.restore(); 
 | 
                } 
 | 
  
 | 
                // draw donut hole 
 | 
  
 | 
                drawDonutHole(ctx); 
 | 
  
 | 
                ctx.restore(); 
 | 
  
 | 
                // Draw the labels, returning true if they fit within the plot 
 | 
  
 | 
                if (options.series.pie.label.show) { 
 | 
                    return drawLabels(); 
 | 
                } else return true; 
 | 
  
 | 
                function drawSlice(angle, color, fill) { 
 | 
  
 | 
                    if (angle <= 0 || isNaN(angle)) { 
 | 
                        return; 
 | 
                    } 
 | 
  
 | 
                    if (fill) { 
 | 
                        ctx.fillStyle = color; 
 | 
                    } else { 
 | 
                        ctx.strokeStyle = color; 
 | 
                        ctx.lineJoin = "round"; 
 | 
                    } 
 | 
  
 | 
                    ctx.beginPath(); 
 | 
                    if (Math.abs(angle - Math.PI * 2) > 0.000000001) { 
 | 
                        ctx.moveTo(0, 0); // Center of the pie 
 | 
                    } 
 | 
  
 | 
                    //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera 
 | 
                    ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); 
 | 
                    ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); 
 | 
                    ctx.closePath(); 
 | 
                    //ctx.rotate(angle); // This doesn't work properly in Opera 
 | 
                    currentAngle += angle; 
 | 
  
 | 
                    if (fill) { 
 | 
                        ctx.fill(); 
 | 
                    } else { 
 | 
                        ctx.stroke(); 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                function drawLabels() { 
 | 
  
 | 
                    var currentAngle = startAngle; 
 | 
                    var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; 
 | 
  
 | 
                    for (var i = 0; i < slices.length; ++i) { 
 | 
                        if (slices[i].percent >= options.series.pie.label.threshold * 100) { 
 | 
                            if (!drawLabel(slices[i], currentAngle, i)) { 
 | 
                                return false; 
 | 
                            } 
 | 
                        } 
 | 
                        currentAngle += slices[i].angle; 
 | 
                    } 
 | 
  
 | 
                    return true; 
 | 
  
 | 
                    function drawLabel(slice, startAngle, index) { 
 | 
  
 | 
                        if (slice.data[0][1] == 0) { 
 | 
                            return true; 
 | 
                        } 
 | 
  
 | 
                        // format label text 
 | 
  
 | 
                        var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; 
 | 
  
 | 
                        if (lf) { 
 | 
                            text = lf(slice.label, slice); 
 | 
                        } else { 
 | 
                            text = slice.label; 
 | 
                        } 
 | 
  
 | 
                        if (plf) { 
 | 
                            text = plf(text, slice); 
 | 
                        } 
 | 
  
 | 
                        var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; 
 | 
                        var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); 
 | 
                        var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; 
 | 
  
 | 
                        var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; 
 | 
                        target.append(html); 
 | 
  
 | 
                        var label = target.children("#pieLabel" + index); 
 | 
                        var labelTop = (y - label.height() / 2); 
 | 
                        var labelLeft = (x - label.width() / 2); 
 | 
  
 | 
                        label.css("top", labelTop); 
 | 
                        label.css("left", labelLeft); 
 | 
  
 | 
                        // check to make sure that the label is not outside the canvas 
 | 
  
 | 
                        if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { 
 | 
                            return false; 
 | 
                        } 
 | 
  
 | 
                        if (options.series.pie.label.background.opacity != 0) { 
 | 
  
 | 
                            // put in the transparent background separately to avoid blended labels and label boxes 
 | 
  
 | 
                            var c = options.series.pie.label.background.color; 
 | 
  
 | 
                            if (c == null) { 
 | 
                                c = slice.color; 
 | 
                            } 
 | 
  
 | 
                            var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; 
 | 
                            $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") 
 | 
                                .css("opacity", options.series.pie.label.background.opacity) 
 | 
                                .insertBefore(label); 
 | 
                        } 
 | 
  
 | 
                        return true; 
 | 
                    } // end individual label function 
 | 
                } // end drawLabels function 
 | 
            } // end drawPie function 
 | 
        } // end draw function 
 | 
  
 | 
        // Placed here because it needs to be accessed from multiple locations 
 | 
  
 | 
        function drawDonutHole(layer) { 
 | 
            if (options.series.pie.innerRadius > 0) { 
 | 
  
 | 
                // subtract the center 
 | 
  
 | 
                layer.save(); 
 | 
                var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; 
 | 
                layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color 
 | 
                layer.beginPath(); 
 | 
                layer.fillStyle = options.series.pie.stroke.color; 
 | 
                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); 
 | 
                layer.fill(); 
 | 
                layer.closePath(); 
 | 
                layer.restore(); 
 | 
  
 | 
                // add inner stroke 
 | 
  
 | 
                layer.save(); 
 | 
                layer.beginPath(); 
 | 
                layer.strokeStyle = options.series.pie.stroke.color; 
 | 
                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); 
 | 
                layer.stroke(); 
 | 
                layer.closePath(); 
 | 
                layer.restore(); 
 | 
  
 | 
                // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. 
 | 
            } 
 | 
        } 
 | 
  
 | 
        //-- Additional Interactive related functions -- 
 | 
  
 | 
        function isPointInPoly(poly, pt) { 
 | 
            for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) 
 | 
                ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) 
 | 
                && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) 
 | 
                && (c = !c); 
 | 
            return c; 
 | 
        } 
 | 
  
 | 
        function findNearbySlice(mouseX, mouseY) { 
 | 
  
 | 
            var slices = plot.getData(), 
 | 
                options = plot.getOptions(), 
 | 
                radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, 
 | 
                x, y; 
 | 
  
 | 
            for (var i = 0; i < slices.length; ++i) { 
 | 
  
 | 
                var s = slices[i]; 
 | 
  
 | 
                if (s.pie.show) { 
 | 
  
 | 
                    ctx.save(); 
 | 
                    ctx.beginPath(); 
 | 
                    ctx.moveTo(0, 0); // Center of the pie 
 | 
                    //ctx.scale(1, options.series.pie.tilt);    // this actually seems to break everything when here. 
 | 
                    ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); 
 | 
                    ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); 
 | 
                    ctx.closePath(); 
 | 
                    x = mouseX - centerLeft; 
 | 
                    y = mouseY - centerTop; 
 | 
  
 | 
                    if (ctx.isPointInPath) { 
 | 
                        if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { 
 | 
                            ctx.restore(); 
 | 
                            return { 
 | 
                                datapoint: [s.percent, s.data], 
 | 
                                dataIndex: 0, 
 | 
                                series: s, 
 | 
                                seriesIndex: i 
 | 
                            }; 
 | 
                        } 
 | 
                    } else { 
 | 
  
 | 
                        // excanvas for IE doesn;t support isPointInPath, this is a workaround. 
 | 
  
 | 
                        var p1X = radius * Math.cos(s.startAngle), 
 | 
                            p1Y = radius * Math.sin(s.startAngle), 
 | 
                            p2X = radius * Math.cos(s.startAngle + s.angle / 4), 
 | 
                            p2Y = radius * Math.sin(s.startAngle + s.angle / 4), 
 | 
                            p3X = radius * Math.cos(s.startAngle + s.angle / 2), 
 | 
                            p3Y = radius * Math.sin(s.startAngle + s.angle / 2), 
 | 
                            p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), 
 | 
                            p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), 
 | 
                            p5X = radius * Math.cos(s.startAngle + s.angle), 
 | 
                            p5Y = radius * Math.sin(s.startAngle + s.angle), 
 | 
                            arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], 
 | 
                            arrPoint = [x, y]; 
 | 
  
 | 
                        // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? 
 | 
  
 | 
                        if (isPointInPoly(arrPoly, arrPoint)) { 
 | 
                            ctx.restore(); 
 | 
                            return { 
 | 
                                datapoint: [s.percent, s.data], 
 | 
                                dataIndex: 0, 
 | 
                                series: s, 
 | 
                                seriesIndex: i 
 | 
                            }; 
 | 
                        } 
 | 
                    } 
 | 
  
 | 
                    ctx.restore(); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            return null; 
 | 
        } 
 | 
  
 | 
        function onMouseMove(e) { 
 | 
            triggerClickHoverEvent("plothover", e); 
 | 
        } 
 | 
  
 | 
        function onClick(e) { 
 | 
            triggerClickHoverEvent("plotclick", e); 
 | 
        } 
 | 
  
 | 
        // trigger click or hover event (they send the same parameters so we share their code) 
 | 
  
 | 
        function triggerClickHoverEvent(eventname, e) { 
 | 
  
 | 
            var offset = plot.offset(); 
 | 
            var canvasX = parseInt(e.pageX - offset.left); 
 | 
            var canvasY =  parseInt(e.pageY - offset.top); 
 | 
            var item = findNearbySlice(canvasX, canvasY); 
 | 
  
 | 
            if (options.grid.autoHighlight) { 
 | 
  
 | 
                // clear auto-highlights 
 | 
  
 | 
                for (var i = 0; i < highlights.length; ++i) { 
 | 
                    var h = highlights[i]; 
 | 
                    if (h.auto == eventname && !(item && h.series == item.series)) { 
 | 
                        unhighlight(h.series); 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
  
 | 
            // highlight the slice 
 | 
  
 | 
            if (item) { 
 | 
                highlight(item.series, eventname); 
 | 
            } 
 | 
  
 | 
            // trigger any hover bind events 
 | 
  
 | 
            var pos = { pageX: e.pageX, pageY: e.pageY }; 
 | 
            target.trigger(eventname, [pos, item]); 
 | 
        } 
 | 
  
 | 
        function highlight(s, auto) { 
 | 
            //if (typeof s == "number") { 
 | 
            //    s = series[s]; 
 | 
            //} 
 | 
  
 | 
            var i = indexOfHighlight(s); 
 | 
  
 | 
            if (i == -1) { 
 | 
                highlights.push({ series: s, auto: auto }); 
 | 
                plot.triggerRedrawOverlay(); 
 | 
            } else if (!auto) { 
 | 
                highlights[i].auto = false; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        function unhighlight(s) { 
 | 
            if (s == null) { 
 | 
                highlights = []; 
 | 
                plot.triggerRedrawOverlay(); 
 | 
            } 
 | 
  
 | 
            //if (typeof s == "number") { 
 | 
            //    s = series[s]; 
 | 
            //} 
 | 
  
 | 
            var i = indexOfHighlight(s); 
 | 
  
 | 
            if (i != -1) { 
 | 
                highlights.splice(i, 1); 
 | 
                plot.triggerRedrawOverlay(); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        function indexOfHighlight(s) { 
 | 
            for (var i = 0; i < highlights.length; ++i) { 
 | 
                var h = highlights[i]; 
 | 
                if (h.series == s) 
 | 
                    return i; 
 | 
            } 
 | 
            return -1; 
 | 
        } 
 | 
  
 | 
        function drawOverlay(plot, octx) { 
 | 
  
 | 
            var options = plot.getOptions(); 
 | 
  
 | 
            var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; 
 | 
  
 | 
            octx.save(); 
 | 
            octx.translate(centerLeft, centerTop); 
 | 
            octx.scale(1, options.series.pie.tilt); 
 | 
  
 | 
            for (var i = 0; i < highlights.length; ++i) { 
 | 
                drawHighlight(highlights[i].series); 
 | 
            } 
 | 
  
 | 
            drawDonutHole(octx); 
 | 
  
 | 
            octx.restore(); 
 | 
  
 | 
            function drawHighlight(series) { 
 | 
  
 | 
                if (series.angle <= 0 || isNaN(series.angle)) { 
 | 
                    return; 
 | 
                } 
 | 
  
 | 
                //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); 
 | 
                octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor 
 | 
                octx.beginPath(); 
 | 
                if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { 
 | 
                    octx.moveTo(0, 0); // Center of the pie 
 | 
                } 
 | 
                octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); 
 | 
                octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); 
 | 
                octx.closePath(); 
 | 
                octx.fill(); 
 | 
            } 
 | 
        } 
 | 
    } // end init (plugin body) 
 | 
  
 | 
    // define pie specific options and their default values 
 | 
  
 | 
    var options = { 
 | 
        series: { 
 | 
            pie: { 
 | 
                show: false, 
 | 
                radius: "auto",    // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) 
 | 
                innerRadius: 0, /* for donut */ 
 | 
                startAngle: 3/2, 
 | 
                tilt: 1, 
 | 
                shadow: { 
 | 
                    left: 5,    // shadow left offset 
 | 
                    top: 15,    // shadow top offset 
 | 
                    alpha: 0.02    // shadow alpha 
 | 
                }, 
 | 
                offset: { 
 | 
                    top: 0, 
 | 
                    left: "auto" 
 | 
                }, 
 | 
                stroke: { 
 | 
                    color: "#fff", 
 | 
                    width: 1 
 | 
                }, 
 | 
                label: { 
 | 
                    show: "auto", 
 | 
                    formatter: function(label, slice) { 
 | 
                        return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>"; 
 | 
                    },    // formatter function 
 | 
                    radius: 1,    // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) 
 | 
                    background: { 
 | 
                        color: null, 
 | 
                        opacity: 0 
 | 
                    }, 
 | 
                    threshold: 0    // percentage at which to hide the label (i.e. the slice is too narrow) 
 | 
                }, 
 | 
                combine: { 
 | 
                    threshold: -1,    // percentage at which to combine little slices into one larger slice 
 | 
                    color: null,    // color to give the new slice (auto-generated if null) 
 | 
                    label: "Other"    // label to give the new slice 
 | 
                }, 
 | 
                highlight: { 
 | 
                    //color: "#fff",        // will add this functionality once parseColor is available 
 | 
                    opacity: 0.5 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
    }; 
 | 
  
 | 
    $.plot.plugins.push({ 
 | 
        init: init, 
 | 
        options: options, 
 | 
        name: "pie", 
 | 
        version: "1.1" 
 | 
    }); 
 | 
  
 | 
})(jQuery); 
 |