简体   繁体   中英

overlapping labels in flot pie chart

I use jquery flot for my pie charts and I have a problem with overlapping labels when the pie chart pieces are very small. Is there a good solution for that?

My pie chart:

series: { 
                pie: { 
                    show: true, 
                    radius: 1, 
                    label: { 
                        show: true, 
                        radius: 5/8, 
                        formatter: function(label, series){ 
                            return '<div style="font-size:12pt;text-  align:center;padding:2px;color:black;margin-left:-80%;margin-  top:-20%;">'+label+'<br/>'+Math.round(series.percent)+'%</div>'; 
                        }, 
                        background: { opacity: 0.5 } 
                    } 
                } 
            }, 
            legend: { 
                show: false 
            }

Thanks, Arshavski Alexander.

A solution from Flot's Google code issues by Marshall Leggett ( link ):

I've found that it seems common for pie labels to overlap in smaller pie charts making them unreadable, particularly if several slices have small percentage values. This is with the jquery.flot.pie plugin.
Please see attached images. I've worked around this with the addition of an anti-collision routine in the label rendering code. I'm attaching a copy of the revised plugin as well. See lines 472-501 , particularly the new functions getPositions() and comparePositions(). This is based in part on Šime Vidas' DOM-element collision detection code. Something like this might be a nice addition to the pie library.

馅饼标签重叠馅饼标签重叠固定

long story short:

  1. In jquery.flot.pie.js and after the line 463 that contains:

    label.css('left', labelLeft);

add the following code:

// check to make sure that the label doesn't overlap one of the other labels
var label_pos = getPositions(label);
for(var j=0; j<labels.length; j++)
{
var tmpPos = getPositions(labels[j]);
var horizontalMatch = comparePositions(label_pos[0], tmpPos[0]);
var verticalMatch = comparePositions(label_pos[1], tmpPos[1]);                  
var match = horizontalMatch && verticalMatch;                           
if(match)
{
    var newTop = tmpPos[1][0] - (label.height() +1 );
    label.css('top', newTop);
    labelTop = newTop;
}       
}

function getPositions(box) {
        var $box = $(box);
        var pos = $box.position();
        var width = $box.width();
        var height = $box.height();
        return [ [ pos.left, pos.left + width ], [ pos.top, pos.top + height ] ];
}

function comparePositions(p1, p2) {
        var x1 = p1[0] < p2[0] ? p1 : p2;
        var x2 = p1[0] < p2[0] ? p2 : p1;
        return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
}
labels.push(label);
  1. Add the following to drawLabels() and you are done:

    var labels = [];

you could try hiding some of the labels under a certain percentage:

pie: { 
    label: {
         threshold: 0.1
    }
}

See graph / example 6 on http://people.iola.dk/olau/flot/examples/pie.html

I was using a modified version of flot.pie with the possibility to add gradients as fillstyle. So i took the version linked by @Gabo Lato and merged the collision check with my own version. Here is the result:

(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;
                    ctx.fillStyle = getColorOrGradient(options.series.pie.gradient, plot.height(), 0, color, options.series.pie.gradient.radial && radius);

                } 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 getColorOrGradient(spec, bottom, top, defaultColor, radius) {
                // Most of this code is copied from the function with the
                // same name in jquery.flot.js.  I therefore believe that
                // this (common) code should be restructured to avoid
                // duplication of errors and an increase in maintenance
                // efforts.  However, I hope that one of the project
                // maintainers can do that.
                if (typeof spec === 'string') {
                    return spec;
                } else if ((spec === null) || (spec.colors === null)) {
                    return defaultColor;
                } else {
                    // assume this is a gradient spec; IE currently only
                    // supports a simple vertical gradient properly, so that's
                    // what we support too
                    var gradient;

                    if (radius) {
                        gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radius);
                    } else {
                        gradient = ctx.createLinearGradient(0, top, 0, bottom);
                    }

                    for (var i = 0, l = spec.colors.length; i < l; ++i) {
                        var c = spec.colors[i];
                        if (typeof c !== 'string') {
                            var co = $.color.parse(defaultColor);
                            if (c.brightness != null)
                                co = co.scale('rgb', c.brightness);
                            if (c.opacity != null)
                                co.a *= c.opacity;
                            c = co.toString();
                        }
                        gradient.addColorStop(i / (l - 1), c);
                    }

                    return gradient;
                }
            }

            function drawLabels() {
                var labels = [];

                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 doesn't overlap one of the other labels
                    var label_pos = getPositions(label);
                    for(var j=0; j<labels.length; j++)
                    {
                        var tmpPos = getPositions(labels[j]);
                        var horizontalMatch = comparePositions(label_pos[0], tmpPos[0]);
                        var verticalMatch = comparePositions(label_pos[1], tmpPos[1]);
                        var match = horizontalMatch && verticalMatch;
                        if(match)
                        {
                            var newTop = tmpPos[1][0] - (label.height() +1 );
                            label.css('top', newTop);
                            labelTop = newTop;
                        }
                    }

                    function getPositions(box) {
                        var $box = $(box);
                        var pos = $box.position();
                        var width = $box.width();
                        var height = $box.height();
                        return [ [ pos.left, pos.left + width ], [ pos.top, pos.top + height ] ];
                    }

                    function comparePositions(p1, p2) {
                        var x1 = p1[0] < p2[0] ? p1 : p2;
                        var x2 = p1[0] < p2[0] ? p2 : p1;
                        return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
                    }

                    labels.push(label);
                    // 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
            },
            gradient: {
                radial: true, // boolean, indicating radial or linear gradient
                colors: null // e.g., [{opacity: 0.1, brightness: 1.0}, {opacity: 1.0, brightness: 1.0}]
            }
        }
    }
};

$.plot.plugins.push({
    init: init,
    options: options,
    name: "pie",
    version: "1.1"
});

})(jQuery);

full Code in fiddle

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM