Skip to content

Instantly share code, notes, and snippets.

@SpaceActuary
Last active June 9, 2017 21:49
Show Gist options
  • Save SpaceActuary/ccb5cb0b0242b2fe1063dd0991862fee to your computer and use it in GitHub Desktop.
Save SpaceActuary/ccb5cb0b0242b2fe1063dd0991862fee to your computer and use it in GitHub Desktop.
Arc Tween
license: gpl-3.0
<!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