Donut Chart

Donut charts are used to show percentage or proportional data as a series of segments that make up a whole donut.

This donut chart widget is similar to the pie chart widget, but uses the D3 data visualization library. The widget will highlight the segment as you hover over it, and display its data in a small popup. The chart is mobile friendly, as its slices can be selected via a mouseover or by tapping on a touch device.

To get started, play around with the controls to change the data, appearance, and settings of the donut chart. Use the generated HTML snippet to embed this pie chart in your website.

Data Params
data
maxSlices
startAngle (0-359)
sort
 
Appearance Params
colors
width
labelStyle Supported style properties (quotes required): "backgroundColor", "borderColor", "borderWidth", "color", "fontFamily", "fontSize", "fontWeight", "fontStyle", "textDecoration"
labelType
innerLabelStyle
innerLabelType
popupStyle
showPopup
showAnimation

Actions
Events
 

Here is the HTML snippet based on the params above. Embed this snippet in your page.



				
[View Code]

Here is the code for the widget:

<?xml version="1.0" encoding="utf-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svidget="http://www.svidget.org/svidget"
		 width="400" height="400" viewBox="0 0 400 400" style="background:transparent" svidget:version="0.3.0">
	<title>Donut Chart</title>
	<desc>
		A donut chart.
		This donut chart takes in an array of data elements and displays them as a donut.
		This widget was developed by Joe Agster for svidget.com and is licensed under the Creative Commons Attribution 4.0 License.
	</desc>

	<svidget:params>
		<!-- data params -->
		<svidget:param name="data" shortname="d" type="array" coerce="true" group="data" description="An array of name, value arrays. Example: [['Steelers', 0.14], ['Broncos', 0.38], ['Colts', 0.21], ['Raiders', 0.06]]. Note that the values do not need to sum to 1 or 100. The widget will auto sum the values to construct percentages." onset="handleDataParamSet" />
		<svidget:param name="maxSlices" shortname="max" type="number" subtype="integer" coerce="true" group="data" description="The max number of slices to allow. If there are more data for slices, the lowest valued slices will be grouped together as one slice. Default is unlimited." onset="handleDataParamSet" />
		<svidget:param name="startAngle" shortname="angle" type="number" coerce="true" defvalue="0" group="data" description="The angle (in degrees) where the first slice will be located." onset="handleDataParamSet" />
		<svidget:param name="sort" shortname="sort" type="string" subtype="choice" typedata="none|asc|desc" coerce="true" group="data" defvalue="none" description="Whether to sort the data. When true sorted by greatet to least value." onset="handleDataParamSet" />
		<!-- style params -->
		<svidget:param name="colors" shortname="c" type="array" coerce="true" group="appearance" description="An array of colors that correspond to each pie slice. If there are more slices than colors then colors will be reused." onset="handleDataParamSet" />
		<svidget:param name="width" shortname="w" type="number" group="appearance" description="The width of the donut slice." onset="handleDataParamSet" />
		<svidget:param name="labelStyle" shortname="lblstl" type="object" coerce="true" group="appearance" description="The style object for the label. Example { fontFamily: 'Arial', fontSize: '12px' }" onset="handleDataParamSet" />
		<svidget:param name="labelType" shortname="lbltyp" type="string" subtype="choice" typedata="none|percentage|value|text" coerce="true" defvalue="percentage" group="appearance" description="The label value to display. Values are 'percentage', 'value', or 'text'. Default is text." onset="handleDataParamSet" />
		<svidget:param name="innerLabelStyle" shortname="ilblstl" type="object" coerce="true" group="appearance" description="The style object for the label. Example { fontFamily: 'Arial', fontSize: '12px' }" onset="handleDataParamSet" />
		<svidget:param name="innerLabelType" shortname="ilbltyp" type="string" subtype="choice" typedata="none|percentage|value|text" coerce="true" defvalue="text" group="appearance" description="The label value to display. Values are 'percentage', 'value', or 'text'. Default is text." onset="handleDataParamSet" />
		<svidget:param name="popupStyle" shortname="popstl" type="object" coerce="true" group="appearance" description="The style object for the popup. Example { backgroundColor: '#dfdfdf', fontFamily: 'Arial', fontSize: '12px' }" onset="handlePopupParamSet" />
		<svidget:param name="showPopup" shortname="sp" type="bool" coerce="true" defvalue="true" group="appearance" description="Whether to show the popup when hovering or clicking on donut slices." onset="handlePopupParamSet" />
		<svidget:param name="showAnimation" shortname="sa" type="bool" coerce="true" defvalue="true" group="appearance" description="Whether to show animation." onset="handleUpdateParamSet" />
	</svidget:params>

	<svidget:actions>
		<svidget:action name="animate" binding="animate" description="Animates the transition from 0-360 on the chart.">
			<svidget:actionparam name="duration" type="number" defvalue="0.5" />
		</svidget:action>
	</svidget:actions>

	<svidget:events>
		<svidget:event name="sliceSelect" description="Triggered when a slice is selected either by mousing over or touching (on non-mouse devices)." />
		<svidget:event name="sliceActivate" description="Triggered when a slice is activated due to a click or touch after slice was selected." />
	</svidget:events>

	<style>
		<![CDATA[
		a.footer { fill: #afafaf; }
		.nonhover { opacity: 0.5; -webkit-transition: opacity 0.2s ease-in; transition: opacity 0.1s ease-in; }
		.hover { cursor: pointer; opacity: 1.0; }
		.unhover { opacity: 1.0; }
		.popupbox { fill: #fff; border: 1px solid #3f3f3f; }
		]]>
	</style>

	<defs>
		<clipPath id="popupboxclip">
			<rect x="0" y="0" width="120" height="50" stroke-width="2" />
		</clipPath>
	</defs>

	<g>
		<text id="footer" font-size="10" font-family="Helvetica" x="395" y="395" fill="#7f7f7f" text-anchor="end">
			<a xlink:href="http://www.svidget.com" class="footer">Powered by Svidget.js</a>.
		</text>
	</g>

	<g id="test" transform="translate(-1000 -1000)">
		<text id="testtext" font-size="15" font-family="Helvetica" x="0" y="0" />
	</g>

	<g id="chart" transform="translate(200 200)">
		<g class="slice" transform="rotate(0)">
			<path style="fill: #cf9f1f;" stroke="#fff" stroke-width="2" d="M 1.16341e-014 -190 A 190 190 0 0 1 158.384 -104.949 L 91.6963 -60.7601 A 110 110 0 0 0 6.73556e-015 -110 Z" />
			<text style="font-family: Helvetica; font-size: 15px; fill: #fff; text-anchor: middle;" transform="translate(70.964 -132.152)" dy="0.35em">15.7%</text>
		</g>
		<g class="slice" transform="rotate(0)">
			<path style="fill: #1f9f1f;" stroke="#fff" stroke-width="2" d="M 158.384 -104.949 A 190 190 0 0 1 119.116 148.025 L 68.9616 85.6989 A 110 110 0 0 0 91.6963 -60.7601 Z" />
			<text style="font-family: Helvetica; font-size: 15px; fill: #fff; text-anchor: middle;" transform="translate(148.225 23.0087)" dy="0.35em">23.5%</text>
		</g>
		<g class="slice" transform="rotate(0)">
			<path style="fill: #1f1fcf;" stroke="#fff" stroke-width="2" d="M 119.116 148.025 A 190 190 0 0 1 34.9124 186.765 L 20.2124 108.127 A 110 110 0 0 0 68.9616 85.6989 Z" />
			<text style="font-family: Helvetica; font-size: 15px; fill: #fff; text-anchor: middle;" transform="translate(62.694 136.27)" dy="0.35em">7.8%</text>
		</g>
		<g class="slice" transform="rotate(0)">
			<path style="fill: #9f1f9f;" stroke="#fff" stroke-width="2" d="M 34.9124 186.765 A 190 190 0 0 1 -144.287 -123.617 L -83.5345 -71.568 A 110 110 0 0 0 20.2124 108.127 Z" />
			<text style="font-family: Helvetica; font-size: 15px; fill: #fff; text-anchor: middle;" transform="translate(-129.904 75)" dy="0.35em">39.2%</text>
		</g>
		<g class="slice" transform="rotate(0)">
			<path style="fill: #cf1f1f;" stroke="#fff" stroke-width="2" d="M -144.287 -123.617 A 190 190 0 0 1 1.33851e-013 -190 L 7.7493e-014 -110 A 110 110 0 0 0 -83.5345 -71.568 Z" />
			<text style="font-family: Helvetica; font-size: 15px; fill: #fff; text-anchor: middle;" transform="translate(-62.694 -136.27)" dy="0.35em">13.7%</text>
		</g>
	</g>

	<g id="popupbox" clip-path="url(#popupboxclip)" transform="translate(0 0)" display="none">
		<rect x="0" y="0" width="120" height="50" class="popupbox" stroke="#3f3f3f" stroke-width="2" />
		<text id="popupboxlabel" font-weight="bold" stroke="none" stroke-width="0" text-anchor="start" x="5" y="20">Item</text>
		<text id="popupboxvalue" stroke="none" stroke-width="0" text-anchor="start" x="5" y="40">0</text>
	</g>

	<script type="application/javascript" xlink:href="../scripts/svidget.min.js"></script>
	<script type="application/javascript" xlink:href="../scripts/d3.min.js"></script>
	<script type="application/javascript">
		<![CDATA[
	
		// constants
		var DEFAULT_COLORS = ['#cf9f1f', '#1f9f1f', '#1f1fcf', '#9f1f9f', '#cf1f1f'];
		var STYLE_MAPPINGS = { backgroundColor: "fill", borderColor: "stroke", borderWidth: "stroke-width", color: "fill", fontFamily: "font-family", fontSize: "font-size", fontWeight: "font-weight", fontStyle: "font-style", stroke: "stroke", strokeWidth: "stroke-width", strokeDashArray: "stroke-dasharray", textDecoration: "text-decoration" };
		var EDGE = 10; // space between pie and edge
		var SIZE = 400;
		var FULL_RADIUS = SIZE / 2;
		var CHART_RADIUS = FULL_RADIUS - EDGE;
		var ANIMATION_DURATION = 500; //ms
		var ANIMATION_EASING = "ease-in";
		
		// param variables
		var _data = [];
		var _maxSlices = 3;
		var _startAngle = 0;
		var _sort = 'none';
		var _colors = DEFAULT_COLORS;
		var _width = 80;
		var _labelStyle = null; //{ fontFamily: "Verdana", fontSize: "15px", color: "white", fontWeight: "bold" };
		var _labelType = 'percentage';
		var _innerLabelStyle = null;
		var _innerLabelType = 'text';
		var _popupStyle = null; //{ fontFamily: "Helvetica", backgroundColor: "#7fff7f", color: "red", fontSize: "15px", fontWeight: "normal", borderWidth: 0 };
		var _showPopup = true;
		var _showAnimation = true;

		// general variables
		var _loaded = false;
		var _runAnimation = true;
		var _arc = null;
		var _textArc = null;
		var _colorRange = null;
		var _dataSum = 0;
		var _selectedSliceIndex = null;
		
		/* Loading */
		
		// entry point
		function init() {
			//debugger;
			console.log('init');
			// for demo purposes only, in case no data is passed
			_data = [['banana', 4], ['apple', 6], ['cherry', 2], ['orange', 10], ['grape', 3.5]];
			initParams();
			initEvents();

			drawIfStandalone();
			_loaded = true;
		}
		
		function initParams() {
			loadParams();
		}
		
		function initEvents() {
			var surface = d3.select(document.documentElement);
			surface.on("click", handleSurfaceClick);
			var widget = svidget.$;
			widget.onpagepopulate(handlePagePopulate);
		}
		
		function drawIfStandalone() {
			// this is temp hack to give chance for page to pass params, if no page then we are in standalone mode
			d3.select("#chart").selectAll().remove();
			window.setTimeout(function () {
				if (svidget.$.populatedFromPage()) return;
				draw();
			}, 100);
		}
		
		window.addEventListener('load', init, false);
	
		
		/* Param Events */
		
		function loadParams() {
			var widget = svidget.$;
			_data = checkNull(widget.param("data").value(), _data);
			_maxSlices = checkNull(widget.param("maxSlices").value(), _maxSlices);
			_startAngle = checkNull(widget.param("startAngle").value(), _startAngle);
			_sort = checkNull(widget.param("sort").value(), _sort);
			_colors = checkNull(widget.param("colors").value(), _colors);
			_width = checkNull(widget.param("width").value(), _width);
			_labelStyle = checkNull(widget.param("labelStyle").value(), _labelStyle);
			_labelType = checkNull(widget.param("labelType").value(), _labelType);
			_innerLabelStyle = checkNull(widget.param("innerLabelStyle").value(), _innerLabelStyle);
			_innerLabelType = checkNull(widget.param("innerLabelType").value(), _innerLabelType);
			_popupStyle = checkNull(widget.param("popupStyle").value(), _popupStyle);
			_showPopup = checkNull(widget.param("showPopup").value(), _showPopup);
			_showAnimation = checkNull(widget.param("showAnimation").value(), _showAnimation);
		}
		
		// this handler is invoked once the params from page are passed to widget and populated onto params
		function handlePagePopulate(e) {
			loadParams();
			draw();
		}
		
		function handleUpdateParamSet(e) {
			if (!_loaded) return;
			//console.log('handleUpdateParamSet');
			var widget = svidget.$;
			if (widget.connected() && !widget.populatedFromPage()) return; // not all params have been populated, so defer until loadParams() is called
			//console.log('populated from page: ' + widget.populatedFromPage());
			_runAnimation = _runAnimation || e.target.name() == "data";
			loadParams();
		}
		
		// this handler is invoked when these params are set: data, maxSlices, startAngle, sort, fontSize, fontName, labelType
		function handleDataParamSet(e) {
			if (!_loaded) return;
			//console.log('handleDataParamSet');
			var widget = svidget.$;
			if (widget.connected() && !widget.populatedFromPage()) return; // not all params have been populated, so defer until loadParams() is called
			handleUpdateParamSet(e);
			drawChart();
		}
		
		// this handler is invoked when the showPopup params is set
		function handlePopupParamSet(e) {
			if (!_loaded) return;
			//console.log('handlePopupParamSet');
			var widget = svidget.$;
			if (widget.connected() && !widget.populatedFromPage()) return; // not all params have been populated, so defer until loadParams() is called
			_showPopup = widget.param("showPopup").value();
			if (!_showPopup) hidePopup(); // just in case
			drawPopup();
		}
		
		
		/* UI Events */
		
		function handleSliceOver(d, i) {
			//debugger;
			var de = d3.event;
			var chart = d3.select("#chart");
			var slice = d3.select(this);
			var slices = chart.selectAll(".slice");
			// clear previous hovers
			slices.classed("hover", false).classed("nonhover", true);
			// add hover to current slice
			slice.classed("nonhover", false).classed("hover", true);
			
			if (_showPopup) showPopup(d);
			if (_selectedSliceIndex != i) de.preventDefault(); // in case of touchstart, prevents "click" from firing (needs test)
			_selectedSliceIndex = i;
			
			// trigger svidget event
			var eventVal = toEventValue(d.data, _dataSum);
			svidget.$.event("sliceSelect").trigger(eventVal);
		}
		
		function handleSliceOut(d, i) {
			var de = d3.event;
			var chart = d3.select("#chart");
			var slices = chart.selectAll(".slice");
			// clear all hovers
			slices.classed("hover", false).classed("nonhover", false);

			hidePopup();
			_selectedSliceIndex = null;
		}
		
		function handleSliceClick(d, i) {
			var de = d3.event;
			_selectedSliceIndex = i;
			// trigger svidget event
			var eventVal = toEventValue(d.data, _dataSum);
			svidget.$.event("sliceActivate").trigger(eventVal);
			
			de.stopPropagation();
		}
		
		function handleSurfaceClick(d, i) {
			handleSliceOut(d, i);
		}
		
		
		/* Popup */
		
		function showPopup(d) {
			var data = d.data;
			var valueStr = toPctString(data[1], _dataSum) + " (" + data[1] + ")";
			var angle = toDegrees((d.endAngle + d.startAngle) / 2);
			d3.select("#popupboxlabel").text(data[0]);
			d3.select("#popupboxvalue").text(valueStr);
			var box = d3.select("#popupbox");
			var point = getCornerPosition(box, angle);
			box.attr("transform", translate(point.x, point.y));
			setVisible(box, true);
		}
		
		function hidePopup() {
			var box = d3.select("#popupbox");
			setVisible(box, false);
		}
		
		function getCornerPosition(box, angle) {
			var rect = box.select("rect").node();
			var w = rect.width.baseVal.value;
			var h = rect.height.baseVal.value;
			var x = 0;
			var y = 0;
			angle = angle % 360;
			if (angle <= 90)
				x = SIZE - w;
			else if (angle <= 180) {
				x = SIZE - w;
				y = SIZE - h;
			}
			else if (angle <= 270)
				y = SIZE - h;
			return { x: x, y: y };	
		}
		
		
		/* Action Handlers */
		
		function animate(e) {
			_runAnimation = true;
			reanimate();
		}
		
		
		/* Draw */
		
		function draw() {
			drawChart();
			drawPopup();
		}
		
		function reanimate() {
			var chart = d3.select("#chart");
			var slices = chart.selectAll(".slice");
			startAnimation(slices, _arc, _textArc);
		}
		
		function drawChart() {
			// see: http://bl.ocks.org/mbostock/3887193
			// also: http://datavizcatalogue.com/methods/donut_chart.html
			// debugger;
			var TEXT_RADIUS_RATIO = 0.6; // this is the ratio from the donut inner radius to center, to use for the text inside the donut
			var TEXT_MIN_RADIUS = 50;
			var outerRadius = CHART_RADIUS;
			var innerRadius = outerRadius - _width;
			var textInnerRadius = Math.min(innerRadius * TEXT_RADIUS_RATIO, innerRadius - TEXT_MIN_RADIUS);
			var data = maxifyData(_data, _maxSlices);
			
			// colors
			_colorRange = d3.scale.ordinal().range(_colors);

			// pie layout - for layout out arcs as a pie/donut chart
			var pie = d3.layout.pie()
				.startAngle(toRadians(_startAngle))
				.endAngle(toRadians(_startAngle + 360))
				.sort(sortDataFunc(_sort))
				.value(function(d) { 
					return numIt(d[1]); /* second element in data inner array */ 
				});
			
			// the main arc object
			_arc = d3.svg.arc()
				.outerRadius(outerRadius)
				.innerRadius(innerRadius);
			// the text arc object - for placing text inside of the donut
			_textArc = d3.svg.arc()
				.outerRadius(innerRadius)
				.innerRadius(textInnerRadius);
			
			// create chart and slices
			var chart = d3.select("#chart");
			chart.selectAll(".slice").remove(); // removes previous arcs
			var slices = chart.selectAll(".slice")
				//.remove() 
				.data(pie(data))
				.enter()
				.append("g");
			slices.attr("class", "slice hover");
			//g.attr("transform", translate(FULL_RADIUS, FULL_RADIUS));
			var paths = slices.append("path");
			// sum data for percentages
			_dataSum = getDataSum(data);
			
			// draw slices
			drawSlices(slices, paths, _arc, _colorRange);
			// draw labels
			drawLabels(slices, _arc, _textArc);
			// wire events
			wireEvents(slices);
			// animate
			startAnimation(slices, _arc, _textArc);
		}
		
		function drawSlices(slices, paths, arc, colorRange) {
			paths.attr("stroke", "#fff")
				.attr("stroke-width", 2)
				.attr("d", arc)
				.style("fill", function(d, i) { 
					return colorRange(i); 
				});
		}
		
		function drawLabels(slices, arc, textArc) {
			//debugger;
			// draw labels on arcs
			drawArcLabels(slices, arc, _labelType, copyObject(_labelStyle, getDefaultLabelStyle()), _dataSum);
			// draw labels in donut
			drawArcLabels(slices, textArc, _innerLabelType, copyObject(_innerLabelStyle, getDefaultInnerLabelStyle()), _dataSum);
		}
		
		function drawArcLabels(slices, arc, labelType, labelStyle, dataSum) {
			if (labelType == "none") return;
			slices.append("text")
				.attr("dy", ".35em")
				.attr("style", "")
				.style("text-anchor", "middle")
				.style(toSvgStyleObject(labelStyle))
				.attr("transform", function(d) { 
					//console.log('arc centroid: ' + arc.centroid(d));
					return "translate(" + arc.centroid(d) + ")"; 
				})
				.text(function(d) { 
					//console.log(d.data[0]);
					return getLabelString(d.data, labelType, dataSum); 
				});
		}
		
		function drawPopup() {
			//if (_popupStyle == null) return;
			var popupStyle = copyObject(_popupStyle, getDefaultPopupStyle());
			var styleObjs = separatePopupStyleObject(popupStyle);
			var popup = d3.select("#popupbox");
			// rect
			var rectStyle = toSvgStyleObject(styleObjs.rectStyle);
			var rect = popup.select("rect");
			rect.style(rectStyle);
			// text elements
			var textStyle = toSvgStyleObject(styleObjs.textStyle);
			var texts = popup.selectAll("text");
			texts.style(textStyle);
		}
		
		function wireEvents(slices) {
			//slices.data("index", slice.index);
			slices.on("mouseover", handleSliceOver);
			slices.on("touchstart", handleSliceOver); // for touch devices
			slices.on("mouseout", handleSliceOut);
			slices.on("click", handleSliceClick);
		}
		
		function startAnimation(slices, arc, textArc) {
			if (!_runAnimation || !_showAnimation) return;
			// console.log("starting animation");
			// move all slices to starting position
			slices.attr("transform", function(d) { 
				var rotate = "rotate(-" + toDegrees(arc.startAngle()(d)) + ")";
				//console.log(rotate);
				return rotate; 
			});
			slices.transition()
				.duration(ANIMATION_DURATION)
				.ease(ANIMATION_EASING)
				//.each("end", doneFunc) // we may need this later
				.attrTween("transform", tween);
			
			// tween function: rotates from start angle to its current angle position
			function tween(d, i, a) {
				var startRotate = "rotate(-" + toDegrees(arc.startAngle()(d)) + ")";
				var endRotate = "rotate(0)";
				return d3.interpolateString(startRotate, endRotate);
			}
			
			_runAnimation = false; // only run once unless invoked again by action
		}
		
		
		/* Data Helpers */
		
		function sortDataFunc(sort) {
			if (sort == null || sort == "none") return null;
			debugger;
			var asc = (sort == "asc");
			// a compare function to sort data in desc order by value
			function dataComparator(a, b) {
				var neg = asc ? 1 : -1;
				return (a[1] - b[1]) * neg; //sort descending by value (index 1 in subarray)
			}
			return dataComparator;
		}
		
		function getDefaultLabelStyle() {
			return { fontFamily: "Helvetica", fontSize: "15px", color: "#fff" };
		}
		
		function getDefaultInnerLabelStyle() {
			return { fontFamily: "Helvetica", fontSize: "15px", color: "#3f3f3f" };
		}
		
		function getDefaultPopupStyle() {
			return { fontFamily: "Helvetica", fontSize: "15px", color: "#3f3f3f" };
		}
		
		// gets label string: either text, value, or percentage
		function getLabelString(item, type, sum) {
			// item = ['label', value]
			// percentage|value|text
			type = type || _labelType;
			if (item == null || !item.length || item.length < 2) return "";
			if (type == "value")
				return item[1] + '';
			else if (type == "text")
				return item[0];
			else
				return toPctString(item[1], sum);
		}
		
		// returns { rectStyle: {...}, textStyle: {...} }
		function separatePopupStyleObject(styleObj) {
			// segment for rect vs group vs text objs
			// { backgroundColor: "fill", borderColor: "stroke", borderWidth: "stroke-width", color: "fill", fontFamily: "font-family", fontSize: "font-size", fontWeight: "font-weight", fontStyle: "font-style", stroke: "stroke", strokeWidth: "stroke-width", strokeDashArray: "stroke-dasharray", textDecoration: "text-decoration" };
			var rectObj = {};
			var textObj = {};
			for (var key in styleObj) {
				if (key.indexOf("background") == 0 || key.indexOf("border") == 0 || key.indexOf("stroke") == 0)
					rectObj[key] = styleObj[key];
				else
					textObj[key] = styleObj[key];
			}
			return { rectStyle: rectObj, textStyle: textObj };
		}
		
		function getDataSum(data) {
			var sum = 0;
			for (var i=0; i<data.length; i++) {
				sum += numIt(data[i][1]);
			}
			return sum;
		}
		
		function maxifyData(data, max) {
			//debugger;
			if (max == null || max <= 0 || max >= data.length) return data;
			var newdata = [];
			var count = 0;
			var restsum = 0;
			while (count < data.length) {
				if (count >= max - 1) {
					restsum += numIt(data[count][1]);
				}
				else {
					newdata.push(data[count]);
				}
				count++;
			}
			if (restsum > 0) newdata.push(["Other", restsum]);

			return newdata;
		}
		
		
		/* Misc */
		
		function checkNull(item, backupItem) {
			return item != null ? item : backupItem;
		}
		
		function numIt(val) {
			return nantoZero(parseFloat(val));
		}
		
		function nantoZero(val) {
			return isNaN(val) ? 0 : val;
		}
		
		function setVisible(ele, visible) {
			var d = visible ? "" : "none";
			ele.attr("display", d);
		}
		
		function translate(x, y) {
			return "translate(" + x + " " + y + ")";
		}

		function toSvgStyleObject(styleObj) {
			var snapObj = {};
			for (var key in styleObj) {
				var snapKey = STYLE_MAPPINGS[key];
				if (snapKey !== undefined) {
					snapObj[snapKey] = styleObj[key];
				}
			}
			return snapObj;
		}
		
		function copyObject(source, target) {
			if (source == null) return target;
			target = target || {};
			for (var key in source) {
				target[key] = source[key];
			}
			return target;
		}
		
		function toEventValue(data, ratio) {
			return { name: data[0], value: data[1], ratio: ratio };
		}
		
		function toPctString(value, sum) {
			value = numIt(value);
			var ratio = sum != 0 ? value / sum : 0;
			return (Math.round(ratio * 1000) / 10.0) + "%";
		}
		
		function toRadians(deg) {
			return (deg / 360) * Math.PI * 2;
		}
		
		function toDegrees(rad) {
			return (rad * 360) / (Math.PI * 2);
		}

	  ]]>
	</script>
</svg>
[View Code]

Download this widget to place on your website. The zip file contains version 0.3.4 of svidget.js but you can also download the latest version.

Fork me on GitHub