Progress pie chart

Show 0-100% as a clock face progression with basic CSS animation

by codeplug

HTML

<input id="percent" type="number" min="0" max="100" value="35" step="5" />
<input id="size" type="number" min="16" max="1000" value="100" step="10" />
<select id="state">
    <option>none</option>
    <option>info</option>
    <option>success</option>
    <option>warning</option>
    <option>danger</option>
</select>

<div id="pie">;llll
</div>

CSS

/* Create a clipped round hole for the pie chart */
.pie-wrapper {
	width: 250px;
	height: 250px;
	position: relative;
	background: white;
	border-radius: 50%;
	overflow: hidden;
	border: 1px solid #eee;
	-moz-box-sizing: border-box;
	-webkit-box-sizing: border-box;
	box-sizing: border-box;
}

	/* Create a half circle that animates transisions around the centre of the circle */
	.pie-wrapper > .pie {
		width: 50%;
		height: 100%;
		border-radius: 125px 0 0 125px;
		position: absolute;
		background: #eee;
		border: 1px solid #eee;
		border-right: none;
		-moz-box-sizing: border-box;
		-webkit-box-sizing: border-box;
		box-sizing: border-box;

		/* Ensure that transitions rotate around the centre of the complete circle, not the centre of the div */
		-moz-transform-origin: right center;
		-ms-transform-origin: right center;
		-o-transform-origin: right center;
		-webkit-transform-origin: right center;
		transform-origin: right center;

		/* Animate the rotation */
		-moz-transition: transform .6s linear;
		-o-transition: transform .6s linear;
		-webkit-transition: transform .6s linear;
		transition: transform .6s linear;
	}

		/* .pie-filler sits behind and tracks 0-180deg */
		.pie-wrapper > .pie.pie-filler {
		}

		/* .pie-amount sits in front and tracks 0-360deg */
		.pie-wrapper > .pie.pie-amount {
		}

	/* .pie-mask hides the .pie sections as they rotate 0-180deg */
	.pie-wrapper > .pie-mask {
		width: 50%;
		height: 50%;
		position: absolute;
		opacity: 1;
		background: inherit;
		-moz-transition: transform .6s ease;
		-o-transition: transform .6s ease;
		-webkit-transition: transform .6s ease;
		transition: transform .6s ease;
	}

		/* .pie-mask.q3 rotates out of view 0-180deg */
		.pie-wrapper > .pie-mask.q3 {
			top: 50%;
			border-radius: 0 0 0 125px;
			-moz-transform-origin: right top;
			-ms-transform-origin: right top;
			-o-transform-origin: right top;
			-webkit-transform-origin: right top;
			transform-origin: right top;
		}

		/* .pie-mask.q4...

JavaScript

(function ($) {
	'use strict';

	var timers = {};
	var transformSupported = false;

	function rotateIncrementPie($pie, deg) {
		/// <summary>Increment the pie by up to 90deg towards the value.</summary>
		/// <param name="$pie" type="jQuery">The pie jQuery object.</param>
		/// <param name="deg" type="Number">The degrees to rotate 0-360.</param>

		var incrementKey = $pie.attr('id') || 'default';
		window.clearTimeout(timers[incrementKey]);

		var current = $pie.data('deg');
		var difference = Math.abs(deg - current);
		if (difference > 90) {
			// The panels glitch when CSS animated more than 90deg in one go - there isn't enough time to hide the masks as the amount semicircle rotates in
			// Set a timeout so that after the current animation finishes it tries again
			var original = deg;
			timers[incrementKey] = window.setTimeout(function () {
				rotateIncrementPie($pie, original);
			}, 600);

			// Adjust the value we're going to set so that it is within 90deg of the current 
			deg = deg > current ? current + 90 : current - 90
		}

		rotatePie($pie, deg);
	}

	function rotatePie($pie, deg) {
		/// <summary>Rotate the panels in the pie to represent the value.</summary>
		/// <param name="$pie" type="jQuery">The pie jQuery object.</param>
		/// <param name="deg" type="Number">The degrees to rotate 0-360.</param>

		// Set a data value we can get back easily
		var current = $pie.data('deg');
		$pie.data('deg', deg);

		// Rotate the amount pie half by the degs
		$pie.find('.pie-amount').css('transform', 'rotate(' + deg + 'deg)');

		if (deg < 90) {
			// If less than 90 masks are needed for q3 and q4 to hide the .pie-amount
			$pie.find('.pie-mask.q3').show().css('transform', 'rotate(0deg)');
			$pie.find('.pie-mask.q4').show();

			// If less than 180 then rotate the .pie-filler with the .pie-amount
			$pie.find('.pie-filler').css('transform', 'rotate(' + deg + 'deg)');
		}
		else {
			if (deg < 180) {
				// If less than 180 but more than 90 then rotate q3...