Start graph and import refactoring
[contractdashboard.git] / lib / flot / jquery.flot.pie.js
blob:a/lib/flot/jquery.flot.pie.js -> blob:b/lib/flot/jquery.flot.pie.js
/* /*
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 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();
   
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();
   
// calculate maximum radius and center point // calculate maximum radius and center point
maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2; maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;
centerTop = (canvas.height/2)+options.series.pie.offset.top; centerTop = (canvas.height/2)+options.series.pie.offset.top;
centerLeft = (canvas.width/2); centerLeft = (canvas.width/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>canvas.width-maxRadius) else if (centerLeft>canvas.width-maxRadius)
centerLeft = canvas.width-maxRadius; centerLeft = canvas.width-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,canvas.width,canvas.height); ctx.clearRect(0,0,canvas.width,canvas.height);
target.children().filter('.pieLabel, .pieLabelBackground').remove(); target.children().filter('.pieLabel, .pieLabelBackground').remove();
} }
   
function drawShadow() function drawShadow()
{ {
var shadowLeft = 5; var shadowLeft = 5;
var shadowTop = 15; var shadowTop = 15;
var edge = 10; var edge = 10;
var alpha = 0.02; var alpha = 0.02;
   
// 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>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) 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 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++)
{ {