Skip to content

Instantly share code, notes, and snippets.

@akashwadhwani
Forked from sxywu/.block
Created May 3, 2019 17:59
Show Gist options
  • Save akashwadhwani/8957df487ebb0526185ffcb4b0b8ff68 to your computer and use it in GitHub Desktop.
Save akashwadhwani/8957df487ebb0526185ffcb4b0b8ff68 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