Вход Регистрация
Файл: templates/backend/default/assets/plugins/jquery-flot/jquery.flot.pie.js
Строк: 735
<?php
/*
Flot plugin for rendering pie charts. The plugin assumes the data is 
coming is as a single data value for each series, and each of those 
values is a positive value or zero (negative numbers don't make 
any sense and will cause strange effects). The data values do 
NOT need to be passed in as percentage values because it 
internally calculates the total and percentages.

* Created by Brian Medendorp, June 2009
* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars

* Changes:
    2009-10-22: lineJoin set to round
    2009-10-23: IE full circle fix, donut
    2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera
    2009-11-17: Added IE hover capability submitted by Anthony Aragues
    2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well)
        

Available options are:
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 ($) 
{
    function 
init(plot) // this is the "body" of the plugin
    
{
        var 
canvas = null;
        var 
target = null;
        var 
maxRadius = null;
        var 
centerLeft = null;
        var 
centerTop = null;
        var 
total = 0;
        var 
redraw = true;
        var 
redrawAttempts = 10;
        var 
shrink = 0.95;
        var 
legendWidth = 0;
        var 
processed = false;
        var 
raw = false;
        
        
// interactive variables    
        
var highlights = [];    
    
        
// add hook to determine if pie plugin in enabled, and then perform necessary operations
        
plot.hooks.processOptions.push(checkPieEnabled);
        
plot.hooks.bindEvents.push(bindEvents);    

        
// check to see if the pie plugin is enabled
        
function checkPieEnabled(plot, options)
        {
            if (
options.series.pie.show)
            {
                
//disable grid
                
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;
                if (
options.series.pie.tilt<0)
                    
options.series.pie.tilt=0;
            
                
// add processData hook to do transformations on the data
                
plot.hooks.processDatapoints.push(processDatapoints);
                
plot.hooks.drawOverlay.push(drawOverlay);    
                
                
// add draw hook
                
plot.hooks.draw.push(draw);
            }
        }
    
        
// bind hoverable events
        
function bindEvents(plot, eventHolder)         
        {        
            var 
options = plot.getOptions();
            
            if (
options.series.pie.show && options.grid.hoverable)
                
eventHolder.unbind('mousemove').mousemove(onMouseMove);
                
            if (
options.series.pie.show && options.grid.clickable)
                
eventHolder.unbind('click').click(onClick);
        }    
        

        
// debugging function that prints out an object
        
function alertObject(obj)
        {
            var 
msg = '';
            function 
traverse(obj, depth)
            {
                if (!
depth)
                    
depth = 0;
                for (var 
i = 0; i < obj.length; ++i)
                {
                    for (var 
j=0; j<depth; j++)
                        
msg += 't';
                
                    if( 
typeof obj[i] == "object")
                    {    
// its an object
                        
msg += ''+i+':n';
                        
traverse(obj[i], depth+1);
                    }
                    else
                    {    
// its a value
                        
msg += ''+i+': '+obj[i]+'n';
                    }
                }
            }
            
traverse(obj);
            
alert(msg);
        }
        
        function 
calcTotal(data)
        {
            for (var 
i = 0; i < data.length; ++i)
            {
                var 
item = parseFloat(data[i].data[0][1]);
                if (
item)
                    
total += item;
            }
        }    
        
        function 
processDatapoints(plot, series, data, datapoints) 
        {    
            if (!
processed)
            {
                
processed = true;
            
                
canvas = plot.getCanvas();
                
target = $(canvas).parent();
                
options = plot.getOptions();
            
                
plot.setData(combine(plot.getData()));
            }
        }
        
        function 
setupPie()
        {
            
legendWidth = target.children().filter('.legend').children().width();
        
            
// calculate maximum radius and center point
            
maxRadius =  Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;
            
centerTop = (canvas.height/2)+options.series.pie.offset.top;
            
centerLeft = (canvas.width/2);
            
            if (
options.series.pie.offset.left=='auto')
                if (
options.legend.position.match('w'))
                    
centerLeft += legendWidth/2;
                else
                    
centerLeft -= legendWidth/2;
            else
                
centerLeft += options.series.pie.offset.left;
                    
            if (
centerLeft<maxRadius)
                
centerLeft = maxRadius;
            else if (
centerLeft>canvas.width-maxRadius)
                
centerLeft = canvas.width-maxRadius;
        }
        
        function 
fixData(data)
        {
            for (var 
i = 0; i < data.length; ++i)
            {
                if (
typeof(data[i].data)=='number')
                    
data[i].data = [[1,data[i].data]];
                else if (
typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')
                {
                    if (
typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')
                        
data[i].label = data[i].data.label; // fix weirdness coming from flot
                    
data[i].data = [[1,0]];
                    
                }
            }
            return 
data;
        }
        
        function 
combine(data)
        {
            
data = fixData(data);
            
calcTotal(data);
            var 
combined = 0;
            var 
numCombined = 0;
            var 
color = options.series.pie.combine.color;
            
            var 
newdata = [];
            for (var 
i = 0; i < data.length; ++i)
            {
                
// make sure its a number
                
data[i].data[0][1] = parseFloat(data[i].data[0][1]);
                if (!
data[i].data[0][1])
                    
data[i].data[0][1] = 0;
                    
                if (
data[i].data[0][1]/total<=options.series.pie.combine.threshold)
                {
                    
combined += data[i].data[0][1];
                    
numCombined++;
                    if (!
color)
                        
color = data[i].color;
                }                
                else
                {
                    
newdata.push({
                        
data: [[1,data[i].data[0][1]]], 
                        
color: data[i].color, 
                        
label: data[i].label,
                        
angle: (data[i].data[0][1]*(Math.PI*2))/total,
                        
percent: (data[i].data[0][1]/total*100)
                    });
                }
            }
            if (
numCombined>0)
                
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
            
ctx = newCtx;
        
            
setupPie();
            var 
slices = plot.getData();
        
            var 
attempts = 0;
            while (
redraw && attempts<redrawAttempts)
            {
                
redraw = false;
                if (
attempts>0)
                    
maxRadius *= shrink;
                
attempts += 1;
                
clear();
                if (
options.series.pie.tilt<=0.8)
                    
drawShadow();
                
drawPie();
            }
            if (
attempts >= redrawAttempts) {
                
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,canvas.width,canvas.height);
                
target.children().filter('.pieLabel, .pieLabelBackground').remove();
            }
            
            function 
drawShadow()
            {
                var 
shadowLeft = 5;
                var 
shadowTop = 15;
                var 
edge = 10;
                var 
alpha = 0.02;
            
                
// set radius
                
if (options.series.pie.radius>1)
                    var 
radius = options.series.pie.radius;
                else
                    var 
radius = maxRadius * options.series.pie.radius;
                    
                if (
radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/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()
            {
                
startAngle = Math.PI*options.series.pie.startAngle;
                
                
// set radius
                
if (options.series.pie.radius>1)
                    var 
radius = options.series.pie.radius;
                else
                    var 
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
                
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);
                
                
// draw labels
                
if (options.series.pie.label.show)
                    
drawLabels();
                
                
// restore to original state
                
ctx.restore();
                
                function 
drawSlice(angle, color, fill)
                {    
                    if (
angle<=0)
                        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
                    
else if ($.browser.msie)
                        
angle -= 0.0001;
                    
//ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera
                    
ctx.arc(0,0,radius,currentAngle,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;
                    
                    
// set radius
                    
if (options.series.pie.label.radius>1)
                        var 
radius = options.series.pie.label.radius;
                    else
                        var 
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)
                            
drawLabel(slices[i], currentAngle, i);
                        
currentAngle += slices[i].angle;
                    }
                    
                    function 
drawLabel(slice, startAngle, index)
                    {
                        if (
slice.data[0][1]==0)
                            return;
                            
                        
// 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 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)
                            
redraw = true;
                        
                        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>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity);
                        }
                    } 
// 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)
        {
            
// draw donut hole
            
if(options.series.pie.innerRadius > 0)
            {
                
// subtract the center
                
layer.save();
                
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;
            
            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,false);
                    
ctx.closePath();
                    
x = mouseX-centerLeft;
                    
y = mouseY-centerTop;
                    if(
ctx.isPointInPath)
                    {
                        if (
ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop))
                        {
                            
//alert('found slice!');
                            
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. 
                        
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(),
                
canvasX = parseInt(e.pageX - offset.left),
                
canvasY =  parseInt(e.pageY - offset.top),
                
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) 
        {
            
//alert(options.series.pie.radius);
            
var options = plot.getOptions();
            
//alert(options.series.pie.radius);
            
            
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 (
i = 0; i < highlights.length; ++i) 
                
drawHighlight(highlights[i].series);
            
            
drawDonutHole(octx);

            
octx.restore();

            function 
drawHighlight(series) 
            {
                if (
series.angle < 0) 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,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,
                
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.0"
    
});
})(
jQuery);
?>
Онлайн: 2
Реклама