Skip to content

Instantly share code, notes, and snippets.

@cmdoptesc
Last active February 12, 2018 16:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cmdoptesc/6228457 to your computer and use it in GitHub Desktop.
Save cmdoptesc/6228457 to your computer and use it in GitHub Desktop.
JavaScript D3: Arc tween transitions using attrTween and attr methods

D3: Arcs Tweening Animation

view on bl.ocks.org

Click on the grey circles. The green arcs will transition using attr, whereas the red ones will use the attrTween method.

If you have not seen/read Bostock's arc tween example, it's probably the best place to start. Secondly, I've posted up a basic example of drawing static, concentric arcs (gist), which might be helpful before adding on tweens/animations.

Below is some annotated source code from my experience playing with arcs. The biggest issue I came across was understanding the role of the arcTween helper function in relation to attrTween. Unlike attr, which takes a value as its second argument, attrTween requires a helper function, arcTween, which will be called during the intermediary animation ticks. This method was used for my muniNow project (git).

Countless credit goes to Mike Bostock for all of his wonderful resources.

If you have any questions, please feel free to leave a comment on the Gist.

Cheers, al lin, aug. 2013

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="description" content="Basic guide to tweening arcs in D3 using custom attrTween functions.">
<title>JavaScript D3: Arc Tween Example</title>
<style type='text/css'>
.click-circle {
cursor: pointer;
}
.highlighted {
fill: rgba(201,201,201, 0.80);
}
</style>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js"></script>
</head>
<body>
<div class="chart"></div>
<script type="text/javascript">
var width = window.innerWidth,
height = window.innerHeight;
var greenTranslate = "translate(200, 200)";
var redTranslate = "translate(600, 200)";
if(width < 800) {
redTranslate = "translate(200, 600)";
}
// append svg to the DIV
d3.select(".chart").append("svg:svg")
.attr("width", width)
.attr("height", height);
var render = function(dataset) {
vis = d3.select("svg"); // select the svg
// set constants
var PI = Math.PI;
var arcMin = 75; // inner radius of the first arc
var arcWidth = 15; // width
var arcPad = 1; // padding between arcs
// arc accessor
// d and i are automatically passed to accessor functions,
// with d being the data and i the index of the data
var drawArc = d3.svg.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
return Math.floor((d*6 * (PI/180))*1000)/1000;
});
// bind the data
var arcs = vis.selectAll("path.arc-path").data(dataset);
var redArcs = vis.selectAll("path.red-path").data(dataset);
// update green arcs (the naive way)
arcs.transition() // adding a transition
.duration(300)
.attr("fill", function(d){
// we need to redefine the fills as well since we have new data,
// otherwise the colors would no longer be relative to the data
// values (and arc length). if your fills weren't relative to
// the data, this would not be necessary
var grn = Math.floor((1 - d/60)*255);
return "rgb(0, "+ grn +", 0)";
})
.attr("d", drawArc); // this will only work when the difference between the old
// and new values between two isn't too great. otherwise
// the arc will be drawn using the shortest-path. see below
// for the custom arc2Tween function called by attrTween
// update red arcs
// custom tween function used by the attrTween method to draw the intermediary steps.
// attrTween will actually pass the data, index, and attribute value of the current
// DOM object to this function, but for our purposes, we can omit the attribute value
function arc2Tween(d, indx) {
var interp = d3.interpolate(this._current, d); // this will return an interpolater
// function that returns values
// between 'this._current' and 'd'
// given an input between 0 and 1
this._current = d; // update this._current to match the new value
return function(t) { // returns a function that attrTween calls with
// a time input between 0-1; 0 as the start time,
// and 1 being the end of the animation
var tmp = interp(t); // use the time to get an interpolated value
// (between this._current and d)
return drawArc(tmp, indx); // pass this new information to the accessor
// function to calculate the path points.
// make sure sure you return this.
// n.b. we need to manually pass along the
// index to drawArc so since the calculation of
// the radii depend on knowing the index. if your
// accessor function does not require knowing the
// index, you can omit this argument
}
};
// *** update red arcs -- using attrTeen and a custom tween function ***
redArcs.transition()
.duration(300)
.attr("fill", function(d){
var red = Math.floor((1 - d/60)*255);
return "rgb("+ red +", 51, 51)";
})
.attrTween("d", arc2Tween); // using attrTween instead of attr here since
// attr interpolates linearly without taking
// in account the shape of the arc.
// attrTween requires a function as the second
// argument, whereas attr can just be a value.
// draw green arcs for new data
arcs.enter().append("svg:path")
.attr("class", "arc-path") // assigns a class for easier selecting
.attr("transform", greenTranslate)
.attr("fill", function(d){
// fill is an rgb value with the green value determined by the data
// smaller numbers result in a higher green value (1 - d/60)
var grn = Math.floor((1 - d/60)*255);
return "rgb(0, "+ grn +", 0)";
})
.attr("d", drawArc); // draw the arc
// drawing the red arcs for new data
// similar to above, except for
redArcs.enter().append("svg:path")
.attr("class", "red-path")
.attr("transform", redTranslate)
.attr("fill", function(d){
var red = Math.floor((1 - d/60)*255);
return "rgb("+ red +", 51, 51)";
})
.attr("d", drawArc)
.each(function(d){
this._current = d;
});
};
// the code below is not necessary for your understanding of arc tweening
// instead, it is used to create a click area for people to regenerate
// arcs by generating a new data set and calling render on that set
// for generating a random array of times
var generateTimes = function(quantity) {
var i, times = [];
for(i=0; i<quantity; i++) {
times.push(Math.round(Math.random()*60));
}
return times;
};
// drawing the click area
var initialize = function() {
var arcMin = 75; // this should match the arcMin in render()
var times = generateTimes(6);
render(times);
// making the click circle for green arcs
if(!d3.selectAll("circle.click-circle")[0].length) { // if there is no click area..
d3.select("svg").append("circle")
.attr("class", 'click-circle')
.attr("transform", greenTranslate)
.attr("r", arcMin*0.85)
.attr("fill", "rgba(201, 201, 201, 0.5)")
.on("click", function() {
times = generateTimes(6);
render(times);
})
.on("mouseover", function() {
d3.select(this).classed("highlighted", true);
})
.on("mouseout", function() {
d3.select(this).classed("highlighted", false);
});
// making the click circle for red arcs
d3.select("svg").append("circle")
.attr("class", 'click-circle')
.attr("transform", redTranslate)
.attr("r", arcMin*0.85)
.attr("fill", "rgba(201, 201, 201, 0.5)")
.on("click", function() {
times = generateTimes(6);
render(times);
})
.on("mouseover", function() {
d3.select(this).classed("highlighted", true);
})
.on("mouseout", function() {
d3.select(this).classed("highlighted", false);
});
}
}
initialize();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment