A commented example of tweening arcs; see Pie Chart Update, II and Arc Tween (Clock) for older examples. (There’s also The Amazing Pie and Donut Transitions if you want to get fancy.) See the API reference for transition.attrTween and my tutorial Working with Transitions for more.
Last active
June 9, 2017 21:49
-
-
Save SpaceActuary/ccb5cb0b0242b2fe1063dd0991862fee to your computer and use it in GitHub Desktop.
Arc Tween
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<svg width="960" height="500"></svg> | |
<script src="//d3js.org/d3.v4.min.js"></script> | |
<script> | |
console.clear() | |
var tau = 2 * Math.PI; // http://tauday.com/tau-manifesto | |
// An arc function with all values bound except the endAngle. So, to compute an | |
// SVG path string for a given angle, we pass an object with an endAngle | |
// property to the `arc` function, and it will return the corresponding string. | |
var arc = d3.arc() | |
.innerRadius(190) | |
.outerRadius(230) | |
.startAngle(0); | |
var arc2 = d3.arc() | |
.innerRadius(0); | |
// Get the SVG container, and apply a transform such that the origin is the | |
// center of the canvas. This way, we don’t need to position arcs individually. | |
var svg = d3.select("svg"), | |
width = +svg.attr("width"), | |
height = +svg.attr("height"), | |
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
// Add the background arc, from 0 to 100% (tau). | |
var background = g.append("path") | |
.datum({startAngle: 0, endAngle: tau, outerRadius: 245}) | |
.style("fill", "#ddd") | |
.attr("d", arc2); | |
// Add the foreground arc in orange, currently showing 12.7%. | |
var data = [ | |
[0, 0, 0, 0, 0, 0], | |
[0, 0.25, 0.4, 0.65, 0.9, 1], | |
[0, 1, 1, 1, 1, 1] | |
] | |
var pairedData = data.map(function(d){ | |
var d2 = d3.pairs(d, pair); | |
console.log("d2", d2) | |
d2.forEach(function(d){ d.outerRadius = 240; }) | |
return d2; | |
}); | |
function pair(a, b) { | |
return { | |
x0: a, | |
x1: b | |
}; | |
} | |
var colors = ['red', 'yellow', 'green', 'blue', 'purple'] | |
console.log(pairedData) | |
var foreground2 = g.selectAll("path.fg").data(pairedData[0]) | |
foreground2.enter().append("path") | |
.attr("class", "fg") | |
.style("fill", function(d,i){ return colors[i];}) | |
.attr("d", arc2) | |
.merge(foreground2); | |
var foreground = g.append("path") | |
.datum({startAngle: 0 * tau, endAngle: 0 * tau}) | |
.style("fill", "white") | |
.attr("d", arc); | |
var inOut = 0; | |
// Every so often, start a transition to a new random angle. The attrTween | |
// definition is encapsulated in a separate function (a closure) below. | |
d3.interval(function() { | |
foreground.transition() | |
.duration(750) | |
.attrTween("d", arcTween(Math.random() * tau)); | |
inOut = 1 - inOut; | |
foreground2 = g.selectAll("path.fg") | |
if(inOut){ | |
console.log("in") | |
foreground2.data(pairedData[1]) | |
.transition() | |
.duration(1750) | |
.attrTween("d", arcTweenIn()); | |
} else { | |
console.log("out") | |
foreground2.data(pairedData[1]).transition() | |
.duration(1750) | |
.attrTween("d", arcTweenOut()); | |
} | |
}, 3500); | |
// Returns a tween for a transition’s "d" attribute, transitioning any selected | |
// arcs from their current angle to the specified new angle. | |
function arcTween(newAngle) { | |
// The function passed to attrTween is invoked for each selected element when | |
// the transition starts, and for each element returns the interpolator to use | |
// over the course of transition. This function is thus responsible for | |
// determining the starting angle of the transition (which is pulled from the | |
// element’s bound datum, d.endAngle), and the ending angle (simply the | |
// newAngle argument to the enclosing function). | |
return function(d) { | |
// To interpolate between the two angles, we use the default d3.interpolate. | |
// (Internally, this maps to d3.interpolateNumber, since both of the | |
// arguments to d3.interpolate are numbers.) The returned function takes a | |
// single argument t and returns a number between the starting angle and the | |
// ending angle. When t = 0, it returns d.endAngle; when t = 1, it returns | |
// newAngle; and for 0 < t < 1 it returns an angle in-between. | |
var interpolate = d3.interpolate(d.endAngle, newAngle); | |
// The return value of the attrTween is also a function: the function that | |
// we want to run for each tick of the transition. Because we used | |
// attrTween("d"), the return value of this last function will be set to the | |
// "d" attribute at every tick. (It’s also possible to use transition.tween | |
// to run arbitrary code for every tick, say if you want to set multiple | |
// attributes from a single function.) The argument t ranges from 0, at the | |
// start of the transition, to 1, at the end. | |
return function(t) { | |
// Calculate the current arc angle based on the transition time, t. Since | |
// the t for the transition and the t for the interpolate both range from | |
// 0 to 1, we can pass t directly to the interpolator. | |
// | |
// Note that the interpolated angle is written into the element’s bound | |
// data object! This is important: it means that if the transition were | |
// interrupted, the data bound to the element would still be consistent | |
// with its appearance. Whenever we start a new arc transition, the | |
// correct starting angle can be inferred from the data. | |
d.endAngle = interpolate(t); | |
// Lastly, compute the arc path given the updated data! In effect, this | |
// transition uses data-space interpolation: the data is interpolated | |
// (that is, the end angle) rather than the path string itself. | |
// Interpolating the angles in polar coordinates, rather than the raw path | |
// string, produces valid intermediate arcs during the transition. | |
return arc(d); | |
}; | |
}; | |
} | |
function arcTweenIn() { | |
console.log("arcTweenIn") | |
return function(d) { | |
var interpolateStart = d3.interpolate(0, d.x0 * tau); | |
var interpolateEnd = d3.interpolate(0, d.x1 * tau); | |
return function(t) { | |
d.startAngle = interpolateStart(t); | |
d.endAngle = interpolateEnd(t); | |
return arc2(d); | |
}; | |
}; | |
} | |
function arcTweenOut() { | |
console.log("arcTweenOut") | |
return function(d) { | |
var interpolateStart = d3.interpolate(d.startAngle, tau); | |
var interpolateEnd = d3.interpolate(d.endAngle, tau); | |
return function(t) { | |
d.startAngle = interpolateStart(t); | |
d.endAngle = interpolateEnd(t); | |
return arc2(d); | |
}; | |
}; | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment