Skip to content

Instantly share code, notes, and snippets.

@sxywu
Last active May 3, 2019 17:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sxywu/8192e134d310a91beeb433fa65c21c9f to your computer and use it in GitHub Desktop.
Save sxywu/8192e134d310a91beeb433fa65c21c9f to your computer and use it in GitHub Desktop.
Animate Donut Chart
license: gpl-3.0

Developed while working with WalletIQ, inspired by mbostock's Arc Tween example. Exiting arcs are first animated out, then existing arcs are updated, and finally entering arcs are animated in.

My main goal for this exercise was to learn D3.v4, and in particular its new way of working with selections and transitions. I definitely didn't have the time to carefully read through the new changes, and it's apparent in my implementation of the chained transitions; it's important to note that this doesn't seem like the best way of chaining transitions, since if at any point the animation does not occur the next animation does not trigger. In particular, the animations do not start for the first 2 seconds, because the exit animations does not happen and the rest are not triggered. I will experiment more in the future.

Another great thing to note: along with Mike's example that this is forked from, there are many other arcTween examples, among some are:

https://twitter.com/yonester/status/753771544627728384 https://twitter.com/FournaiseThomas/status/753893453688365058 https://twitter.com/micahstubbs/status/753865966283436032 https://twitter.com/veltman/status/753959910656868352

--

forked from mbostock's block: Arc Tween

<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js'></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie = d3.pie()
.sort(null)
.value(function(d) {return d[1]});
var arc = d3.arc()
.innerRadius(120)
.outerRadius(150);
var allData = [
[[1, 5], [2, 9], [3, 34], [4, 98], [5, 54]],
[[4, 23], [9, 76], [2, 15], [10, 87], [7, 56], [5, 5], [6, 43]],
[[3, 98], [2, 46], [7, 82]],
[[7, 28], [6, 20], [5, 98], [2, 9], [4, 98]],
[[4, 23], [8, 65], [10, 87], [6, 43], [1, 34], [3, 34]]
];
var prevData = [];
var data = [];
var index = 0;
var duration = 600;
var color = d3.scaleOrdinal(d3.schemePaired);
function arcTween() {
return function(d) {
// interpolate both its starting and ending angles
var interpolateStart = d3.interpolate(d.start.startAngle, d.end.startAngle);
var interpolateEnd = d3.interpolate(d.start.endAngle, d.end.endAngle);
return function(t) {
return arc({
startAngle: interpolateStart(t),
endAngle: interpolateEnd(t),
});
};
};
}
function updatePie() {
var prevPieById = _.reduce(pie(prevData), function(obj, d) {
obj[d.data[0]] = d;
return obj;
}, {});
var currentPie = pie(data);
var arcs = g.selectAll('path')
.data(currentPie, function(d) {return d.data[0]});
// enter and update the arcs first
var exit = arcs.exit();
var enter = arcs.enter().append('path');
var enterUpdate = enter.merge(arcs)
.attr('fill', function(d) {return color(d.data[0])})
// then calculate start and end positions for each of the arcs
exit.each(function(d) {
// the arcs that need to exit, animate it back to its starting angle
d.start = {startAngle: d.startAngle, endAngle: d.endAngle};
d.end = {startAngle: d.startAngle, endAngle: d.startAngle};
});
enterUpdate.each(function(d) {
var prevPie = prevPieById[d.data[0]];
if (prevPie) {
// if previous data exists, it must mean it's just an update
d.start = {startAngle: prevPie.startAngle, endAngle: prevPie.endAngle};
d.end = {startAngle: d.startAngle, endAngle: d.endAngle};
} else {
// if no previous data, must be an enter
d.start = {startAngle: d.startAngle, endAngle: d.startAngle};
d.end = {startAngle: d.startAngle, endAngle: d.endAngle};
}
});
// note: this doesn't seem like
// the best way of chaining transitions, since
// if at any point the animation does not occur
// the next animation does not trigger
// (for example, in the beginning there are no exits,
// so the arcs aren't animated in)
// I will experiment more in the future
exit.transition().duration(duration)
.attrTween('d', arcTween())
.remove()
.on('end', function() {
// then animate the updating arcs
arcs.transition().duration(duration)
.attrTween('d', arcTween())
.on('end', function() {
// and finally animate in the arcs
enter.transition().attrTween('d', arcTween());
});
});
}
d3.interval(function() {
index += 1;
prevData = data;
data = _.sortBy(allData[index % 5], function(d) {return d[0]});
updatePie();
}, 2000);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment