|
<!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> |