Skip to content

Instantly share code, notes, and snippets.

@souljorje
Last active August 16, 2017 12:03
Show Gist options
  • Save souljorje/e6ce8dd955bd646314f0ecd258c431c1 to your computer and use it in GitHub Desktop.
Save souljorje/e6ce8dd955bd646314f0ecd258c431c1 to your computer and use it in GitHub Desktop.
d3 animated donut chart with labels
license: gpl-3.0
.diagram {
font-family: sans-serif;
height: 355px;
max-width: 570px;
margin: 80px auto 0;
}
.diagram svg {
fill: transparent;
}
.diagram svg .label {
opacity: 0;
fill: #000b38;
font-size: 18px;
font-weight: 400;
-webkit-animation: diagram 0.5s;
animation: diagram 0.5s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
.diagram svg .label:nth-child(1) {
-webkit-animation-delay: 0.15s;
animation-delay: 0.15s;
}
.diagram svg .label:nth-child(2) {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.diagram svg .label:nth-child(3) {
-webkit-animation-delay: 0.45s;
animation-delay: 0.45s;
}
.diagram svg .label:nth-child(4) {
-webkit-animation-delay: 0.6s;
animation-delay: 0.6s;
}
.diagram svg .label:nth-child(5) {
-webkit-animation-delay: 0.75s;
animation-delay: 0.75s;
}
.diagram svg .label:nth-child(6) {
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
.diagram svg .line {
opacity: 0;
fill: none;
stroke: #00547e;
stroke-width: 1px;
-webkit-animation: diagram 0.5s;
animation: diagram 0.5s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
.diagram svg .line:nth-child(1) {
-webkit-animation-delay: 0.15s;
animation-delay: 0.15s;
}
.diagram svg .line:nth-child(2) {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.diagram svg .line:nth-child(3) {
-webkit-animation-delay: 0.45s;
animation-delay: 0.45s;
}
.diagram svg .line:nth-child(4) {
-webkit-animation-delay: 0.6s;
animation-delay: 0.6s;
}
.diagram svg .line:nth-child(5) {
-webkit-animation-delay: 0.75s;
animation-delay: 0.75s;
}
.diagram svg .line:nth-child(6) {
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
@-webkit-keyframes diagram {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
@keyframes diagram {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Animated donut chart with labels D3 v4</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="index.css">
<script src="index.js"></script>
</head>
<body>
<div class="diagram"></div>
<script>
var dataset = [
{ label:'<tspan x="300" dy="-0.3em">Lorem</tspan><tspan x="300" dy="1.4em">impsum dolor – 52%.</tspan>', color: '#21b5ff', count: 52 },
{ label:'<tspan x="300" dy="0.5em">Sit amet – 37%.</tspan>', color: '#59c8ff', count: 37 },
{ label:'Consectetur adipiscing – 3%.', color: '#7ad3ff', count: 3 },
{ label:'<tspan x="300" dy="-0.3em">Elit – 2%.</tspan>', color: '#9cdeff', count: 2 },
{ label:'<tspan x="300" dy="-0.7em">Suspendisse finibus</tspan><tspan x="300" dy="1.4em">quis lorem – 4%.</tspan>', color: '#c4ebff', count: 4},
{ label:'<tspan x="300" dy="0.3em">Suspendisse non – 2%.</tspan>', color: '#d4f1ff', count: 2}
];
var svg = d3.select('.diagram').append('svg')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 -20 555 340')
.attr('preserveAspectRatio','xMinYMin')
.append("g")
var color = d3.scaleOrdinal()
.range(["#21b5ff", "#59c8ff", "#7ad3ff", "#9cdeff", "#c4ebff", "#d4f1ff"]);
var outerRadius = 120-65;
var innerRadius = 120;
svg.append('g').attr('class', 'slices').attr('transform', 'translate(120,120) rotate(180)');
svg.append('g').attr('class', 'labels');
svg.append('g').attr('class', 'lines').attr('transform', 'translate(120,120) rotate(180)');
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.pie()
.value(function(d) { return d.count; })
.sort(null);
var delaySpec = function(d, i) {
if (i==0) { return 0};
if (i==1) { return 520};
if (i==2) { return 890};
if (i==3) { return 920};
if (i==4) { return 940};
if (i==5) { return 1000}
};
var durationSpec = function(d) {
return d.data.count * 10
};
var path = svg.select('.slices')
.datum(dataset).selectAll('path')
.data(pie(dataset))
.enter().append('path')
.attr('class', 'slice')
.attr('d', arc)
.transition().ease(d3.easeLinear).delay(delaySpec).duration(durationSpec)
.attrTween('d', function(d) {
var i = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = i(t);
return arc(d);
}
})
.attr('fill', function(d, i) {
return color(d.data.color);
});
var text = svg.select(".labels").selectAll("text")
.data(pie(dataset));
text.enter()
.append("text")
.attr("class", "label")
.html(function(d) {
return (d.data.label);
})
.attr('x', 300)
.attr('y', function(d, i) {
return (i*58);
});
var polyline = svg.select('.lines').selectAll('polyline')
.data(pie(dataset))
.enter().append('polyline')
.attr ('class', 'line')
.attr('points', function(d, i) {
if (i==0) {
return '4,116 -162,116'
}
if (i==1) {
return '-81,59 -162,59'
}
if (i==2) {
return '-42,-60 -141,8 -162,8'
}
if (i==3) {
return '-37,-79 -139,-43 -162,-43'
}
if (i==4) {
return '-21,-86 -139,-106 -162,-106'
}
if (i==5) {
return '-5,-99 -140,-169 -162,-169'
}
})
</script>
</body>
</html>
// https://www.npmjs.com/package/innersvg-polyfill
/**
* innerHTML property for SVGElement
* Copyright(c) 2010, Jeff Schiller
*
* Licensed under the Apache License, Version 2
*
* Works in a SVG document in Chrome 6+, Safari 5+, Firefox 4+ and IE9+.
* Works in a HTML5 document in Chrome 7+, Firefox 4+ and IE9+.
* Does not work in Opera since it doesn't support the SVGElement interface yet.
*
* I haven't decided on the best name for this property - thus the duplication.
*/
(function() {
var serializeXML = function(node, output) {
var nodeType = node.nodeType;
if (nodeType == 3) { // TEXT nodes.
// Replace special XML characters with their entities.
output.push(node.textContent.replace(/&/, '&amp;').replace(/</, '&lt;').replace('>', '&gt;'));
} else if (nodeType == 1) { // ELEMENT nodes.
// Serialize Element nodes.
output.push('<', node.tagName);
if (node.hasAttributes()) {
var attrMap = node.attributes;
for (var i = 0, len = attrMap.length; i < len; ++i) {
var attrNode = attrMap.item(i);
output.push(' ', attrNode.name, '=\'', attrNode.value, '\'');
}
}
if (node.hasChildNodes()) {
output.push('>');
var childNodes = node.childNodes;
for (var i = 0, len = childNodes.length; i < len; ++i) {
serializeXML(childNodes.item(i), output);
}
output.push('</', node.tagName, '>');
} else {
output.push('/>');
}
} else if (nodeType == 8) {
// TODO(codedread): Replace special characters with XML entities?
output.push('<!--', node.nodeValue, '-->');
} else {
// TODO: Handle CDATA nodes.
// TODO: Handle ENTITY nodes.
// TODO: Handle DOCUMENT nodes.
throw 'Error serializing XML. Unhandled node of type: ' + nodeType;
}
}
// The innerHTML DOM property for SVGElement.
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
get: function() {
var output = [];
var childNode = this.firstChild;
while (childNode) {
serializeXML(childNode, output);
childNode = childNode.nextSibling;
}
return output.join('');
},
set: function(markupText) {
// Wipe out the current contents of the element.
while (this.firstChild) {
this.removeChild(this.firstChild);
}
try {
// Parse the markup into valid nodes.
var dXML = new DOMParser();
dXML.async = false;
// Wrap the markup into a SVG node to ensure parsing works.
sXML = '<svg xmlns=\'http://www.w3.org/2000/svg\'>' + markupText + '</svg>';
var svgDocElement = dXML.parseFromString(sXML, 'text/xml').documentElement;
// Now take each node, import it and append to this element.
var childNode = svgDocElement.firstChild;
while(childNode) {
this.appendChild(this.ownerDocument.importNode(childNode, true));
childNode = childNode.nextSibling;
}
} catch(e) {
throw new Error('Error parsing XML string');
};
}
});
// The innerSVG DOM property for SVGElement.
Object.defineProperty(SVGElement.prototype, 'innerSVG', {
get: function() {
return this.innerHTML;
},
set: function(markupText) {
this.innerHTML = markupText;
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment