Commit db83cd0c authored by David Schnur's avatar David Schnur

Merge pull request #877 from dnschnur/jquery-1.8-support

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