http://www.elated.com/articles/snazzy-animated-pie-chart-html5-jquery/
Learn how to use the HTML5 canvas element, CSS3 and jQuery to create a gorgeous, interactive animated pie chart. Full code included for your own use.
|



// Now compute and store the start and end angles of each slice in the chart data
var currentPos = 0; // The current position of the slice in the pie (from 0 to 1)
for ( var slice in chartData ) {
var value = chartData[slice]['value'];
if ( value < (totalValue / 300) ) {
totalValue += ( totalValue / 300 );
value = totalValue / 300;
}
chartData[slice]['startAngle'] = 2 * Math.PI * currentPos;
chartData[slice]['endAngle'] = 2 * Math.PI * ( currentPos + ( value / totalValue ) );
currentPos += value / totalValue;
}


I'm looking to adapt the graph slightly. I want each piece of pie to have its own background-image attached to it. Is this easily done?
// Run the code when the DOM is ready
$( pieChart );
function pieChart() {
// Config settings
var chartSizePercent = 55; // De grootte van de circle (in procent)
var sliceBorderWidth = 1; // De grootte (in pixels) van de randen van de stukken
var sliceBorderStyle = "#fff"; // Kleur van de rand rondom de stukken
var sliceGradientColour = "#1F1C1C"; // Kleur om 1 kant van de gradient aan te geven (hier kun je de diagram lichter of donkerder maken
var maxPullOutDistance = 20; // Hier kan je de uitschuif verte mee bepalen
var pullOutFrameStep = 2; // Hier geef je de uitschuif snelheid aan
var pullOutFrameInterval = 55; // Geef hier aan hoe snel het uischuiven moeten gaan
var pullOutLabelPadding = 65; // De verte dat de tekst staat na het uitschuiven
var pullOutLabelFont = "bold 18px 'Trebuchet MS', Verdana, sans-serif"; // Tekst gegevens (titel)
var pullOutValueFont = "bold 12px 'Trebuchet MS', Verdana, sans-serif"; // Tekst gegevens
var pullOutValuePrefix = "Aantal: "; // Tekst die voor de tekst moet komen te staan bij het uitschuiven
var pullOutShadowColour = "rgba( 0, 0, 0, .5 )"; // Colour to use for the pull-out slice shadow
var pullOutShadowOffsetX = 10; // X-offset (in pixels) Hoever de schaduw moet gaan
var pullOutShadowOffsetY = 10; // Y-offset (in pixels) Hoever de schaduw moet gaan
var pullOutShadowBlur = 25; // De Donkerheid van de schaduw
var pullOutBorderWidth = 2; // De dikte van de rand als het stuk uitgeschoven is
var pullOutBorderStyle = "#333"; // De kleur van de rand als het uitgeschoven is
var chartStartAngle = -.5 * Math.PI; // Start de chart op 12u
// Declare some variables for the chart
var canvas; // The canvas element in the page
var currentPullOutSlice = -1; // Welke slice er al standaard uitgeschoven moet zijn (-1 is geen slice)
var currentPullOutDistance = 0; // How many pixels the pulled-out slice is currently pulled out in the animation
var animationId = 0; // Tracks the interval ID for the animation created by setInterval()
var chartData = []; // Chart data (labels, values, and angles)
var chartColours = []; // Kleuren van de slices (die matche met die van de tabel
var totalValue = 0; // Total of all the values in the chart
var canvasWidth; // Width of the canvas, in pixels
var canvasHeight; // Height of the canvas, in pixels
var centreX; // X-coordinate of centre of the canvas/chart
var centreY; // Y-coordinate of centre of the canvas/chart
var chartRadius; // Radius of the pie chart, in pixels
// Set things up and draw the chart
init();
/**
* Set up the chart data and colours, as well as the chart and table click handlers,
* and draw the initial pie chart
*/
function init() {
// Get the canvas element in the page
canvas = document.getElementById('chart');
// Exit if the browser isn't canvas-capable
if ( typeof canvas.getContext === 'undefined' ) return;
// Initialise some properties of the canvas and chart
canvasWidth = canvas.width;
canvasHeight = canvas.height;
centreX = canvasWidth / 2;
centreY = canvasHeight / 2;
chartRadius = Math.min( canvasWidth, canvasHeight ) / 2 * ( chartSizePercent / 100 );
// Grab the data from the table,
// and assign click handlers to the table data cells
var currentRow = -1;
var currentCell = 0;
$('#chartData td').each( function() {
currentCell++;
if ( currentCell % 2 != 0 ) {
currentRow++;
chartData[currentRow] = [];
chartData[currentRow]['label'] = $(this).text();
} else {
var value = parseFloat($(this).text());
totalValue += value;
value = value.toFixed(0);
chartData[currentRow]['value'] = value;
}
// Store the slice index in this cell, and attach a click handler to it
$(this).data( 'slice', currentRow );
$(this).hover( handleTableClick );
// Extract and store the cell colour
if ( rgb = $(this).css('color').match( /rgb\((\d+), (\d+), (\d+)/) ) {
chartColours[currentRow] = [ rgb[1], rgb[2], rgb[3] ];
} else if ( hex = $(this).css('color').match(/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/) ) {
chartColours[currentRow] = [ parseInt(hex[1],16) ,parseInt(hex[2],16), parseInt(hex[3], 16) ];
} else {
alert( "Error: Colour could not be determined! Please specify table colours using the format '#xxxxxx'" );
return;
}
} );
procentTable ();
// Now compute and store the start and end angles of each slice in the chart data
var currentPos = 0; // The current position of the slice in the pie (from 0 to 1)
for ( var slice in chartData ) {
chartData[slice]['startAngle'] = 2 * Math.PI * currentPos;
chartData[slice]['endAngle'] = 2 * Math.PI * ( currentPos + ( chartData[slice]['value'] / totalValue ) );
currentPos += chartData[slice]['value'] / totalValue;
}
// All ready! Now draw the pie chart, and add the click handler to it
drawChart();
$('#chart').click ( handleChartClick );
}
function procentTable()
{
var currentRow = -1;
var currentCell = 0;
var currentProcent = 0;
$('#chartData td').each
(
function()
{
currentCell++;
if ( currentCell % 2 != 0 )
{
currentRow++;
currentProcent++;
}
else
{
var percentvalue = "" + ( parseFloat( chartData[currentRow]['value'] / totalValue * 100).toFixed(2)) + "%";
var percentRow = document.getElementById('chartData').rows[currentProcent];
var cell = percentRow.insertCell(2);
cell.innerHTML = percentvalue;
var select = percentRow.insertCell(3);
select.innerHTML = "<input type='checkbox' onclick='toggleSlice( "+currentProcent+")'>";
}
$(cell).data( 'slice', currentRow );
$(cell).hover( handleTableClick );
$(select).data( 'slice', currentRow );
$(select).hover( handleTableClick );
// Store the slice index in this cell, and attach a click handler to it
//$(this).data( 'slice', currentRow );
//$(this).click( handleTableClick );
}
);
}
/**
* Process mouse clicks in the chart area.
*
* If a slice was clicked, toggle it in or out.
* If the user clicked outside the pie, push any slices back in.
*
* @param Event The click event
*/
function handleChartClick ( clickEvent ) {
// Get the mouse cursor position at the time of the click, relative to the canvas
var mouseX = clickEvent.pageX - this.offsetLeft;
var mouseY = clickEvent.pageY - this.offsetTop;
// Was the click inside the pie chart?
var xFromCentre = mouseX - centreX;
var yFromCentre = mouseY - centreY;
var distanceFromCentre = Math.sqrt( Math.pow( Math.abs( xFromCentre ), 2 ) + Math.pow( Math.abs( yFromCentre ), 2 ) );
if ( distanceFromCentre <= chartRadius ) {
// Yes, the click was inside the chart.
// Find the slice that was clicked by comparing angles relative to the chart centre.
var clickAngle = Math.atan2( yFromCentre, xFromCentre ) - chartStartAngle;
if ( clickAngle < 0 ) clickAngle = 2 * Math.PI + clickAngle;
for ( var slice in chartData ) {
if ( clickAngle >= chartData[slice]['startAngle'] && clickAngle <= chartData[slice]['endAngle'] ) {
// Slice found. Pull it out or push it in, as required.
toggleSlice ( slice );
return;
}
}
}
// User must have clicked outside the pie. Push any pulled-out slice back in.
pushIn();
}
/**
* Process mouse clicks in the table area.
*
* Retrieve the slice number from the jQuery data stored in the
* clicked table cell, then toggle the slice
*
* @param Event The click event
*/
function handleTableClick ( clickEvent ) {
var slice = $(this).data('slice');
toggleSlice ( slice );
}
/**
* Push a slice in or out.
*
* If it's already pulled out, push it in. Otherwise, pull it out.
*
* @param Number The slice index (between 0 and the number of slices - 1)
*/
function toggleSlice ( slice ) {
if ( slice == currentPullOutSlice ) {
pushIn();
} else {
startPullOut ( slice );
}
}
/**
* Start pulling a slice out from the pie.
*
* @param Number The slice index (between 0 and the number of slices - 1)
*/
function startPullOut ( slice ) {
// Exit if we're already pulling out this slice
if ( currentPullOutSlice == slice ) return;
// Record the slice that we're pulling out, clear any previous animation, then start the animation
currentPullOutSlice = slice;
currentPullOutDistance = 0;
clearInterval( animationId );
animationId = setInterval( function() { animatePullOut( slice ); }, pullOutFrameInterval );
// Highlight the corresponding row in the key table
$('#chartData td').removeClass('highlight');
var labelCell = $('#chartData td:eq(' + (slice*4) + ')');
var valueCell = $('#chartData td:eq(' + (slice*4+1) + ')');
var percentCell = $('#chartData td:eq(' + (slice*4+2) + ')');
var selectCell = $('#chartData td:eq(' + (slice*4+3) + ')');
labelCell.addClass('highlight');
valueCell.addClass('highlight');
percentCell.addClass('highlight');
selectCell.addClass('highlight');
}
/**
* Draw a frame of the pull-out animation.
*
* @param Number The index of the slice being pulled out
*/
function animatePullOut ( slice ) {
// Pull the slice out some more
currentPullOutDistance += pullOutFrameStep;
// If we've pulled it right out, stop animating
if ( currentPullOutDistance >= maxPullOutDistance ) {
clearInterval( animationId );
return;
}
// Draw the frame
drawChart();
}
/**
* Push any pulled-out slice back in.
*
* Resets the animation variables and redraws the chart.
* Also un-highlights all rows in the table.
*/
function pushIn() {
currentPullOutSlice = -1;
currentPullOutDistance = 0;
clearInterval( animationId );
drawChart();
$('#chartData td').removeClass('highlight');
}
/**
* Draw the chart.
*
* Loop through each slice of the pie, and draw it.
*/
function drawChart() {
// Get a drawing context
var context = canvas.getContext('2d');
// Clear the canvas, ready for the new frame
context.clearRect ( 0, 0, canvasWidth, canvasHeight );
// Draw each slice of the chart, skipping the pull-out slice (if any)
for ( var slice in chartData ) {
if ( slice != currentPullOutSlice ) drawSlice( context, slice );
}
// If there's a pull-out slice in effect, draw it.
// (We draw the pull-out slice last so its drop shadow doesn't get painted over.)
if ( currentPullOutSlice != -1 ) drawSlice( context, currentPullOutSlice );
}
/**
* Draw an individual slice in the chart.
*
* @param Context A canvas context to draw on
* @param Number The index of the slice to draw
*/
function drawSlice ( context, slice ) {
// Compute the adjusted start and end angles for the slice
var startAngle = chartData[slice]['startAngle'] + chartStartAngle;
var endAngle = chartData[slice]['endAngle'] + chartStartAngle;
if ( slice == currentPullOutSlice ) {
// We're pulling (or have pulled) this slice out.
// Offset it from the pie centre, draw the text label,
// and add a drop shadow.
var midAngle = (startAngle + endAngle) / 2;
var actualPullOutDistance = currentPullOutDistance * easeOut( currentPullOutDistance/maxPullOutDistance, .8 );
startX = centreX + Math.cos(midAngle) * actualPullOutDistance;
startY = centreY + Math.sin(midAngle) * actualPullOutDistance;
context.fillStyle = 'rgb(' + chartColours[slice].join(',') + ')';
context.textAlign = "center";
context.font = pullOutLabelFont;
context.fillText( chartData[slice]['label'], centreX + Math.cos(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ), centreY + Math.sin(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ) );
context.font = pullOutValueFont;
context.fillText( pullOutValuePrefix + chartData[slice]['value'] + " (" + ( parseFloat( chartData[slice]['value'] / totalValue * 100).toFixed(2) ) + "%)", centreX + Math.cos(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ), centreY + Math.sin(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ) + 20 );
context.shadowOffsetX = pullOutShadowOffsetX;
context.shadowOffsetY = pullOutShadowOffsetY;
context.shadowBlur = pullOutShadowBlur;
} else {
// This slice isn't pulled out, so draw it from the pie centre
startX = centreX;
startY = centreY;
}
// Set up the gradient fill for the slice
var sliceGradient = context.createLinearGradient( 0, 0, canvasWidth*.75, canvasHeight*.75 );
sliceGradient.addColorStop( 0, sliceGradientColour );
sliceGradient.addColorStop( 1, 'rgb(' + chartColours[slice].join(',') + ')' );
// Draw the slice
context.beginPath();
context.moveTo( startX, startY );
context.arc( startX, startY, chartRadius, startAngle, endAngle, false );
context.lineTo( startX, startY );
context.closePath();
context.fillStyle = sliceGradient;
context.shadowColor = ( slice == currentPullOutSlice ) ? pullOutShadowColour : "rgba( 0, 0, 0, 0 )";
context.fill();
context.shadowColor = "rgba( 0, 0, 0, 0 )";
// Style the slice border appropriately
if ( slice == currentPullOutSlice ) {
context.lineWidth = pullOutBorderWidth;
context.strokeStyle = pullOutBorderStyle;
} else {
context.lineWidth = sliceBorderWidth;
context.strokeStyle = sliceBorderStyle;
}
// Draw the slice border
context.stroke();
}
/**
* Easing function.
*
* A bit hacky but it seems to work! (Note to self: Re-read my school maths books sometime)
*
* @param Number The ratio of the current distance travelled to the maximum distance
* @param Number The power (higher numbers = more gradual easing)
* @return Number The new ratio
*/
function easeOut( ratio, power ) {
return ( Math.pow ( 1 - ratio, power ) + 1 );
}
};