This example shows the use of the d3 transition library, particularly with regards to timing. Four boxes are displayed, each containing a circle of a different color. One of them is open and the others are closed. Click on a box to open it.
Three separate transitions are initiated on three different element selections. Yet, the box outline and circle opacity transitions start after the box position transitions.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<style>
body {
font: 10px sans-serif;
}
path {
fill: #fff;
stroke: #555;
}
text {
font-size: 14px;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 960,
height = 500;
var boxes = [],
boxwidth = 40,
openboxwidth = 180,
boxheight = 180,
margin = 40,
openbox = 0;
color = [ "#d7191c", "#e66101", "#1a9641", "#2b83ba" ];
// place boxes at the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "boxes")
.attr("transform", "translate(40, 40)");
// compute the initial layout using global coordinates, and an open box index
var boxes = boxLayout(openbox);
// make the boxes
d3.select("#boxes").selectAll("g").data(boxes).enter().append("g");
// translate the box to a starting point in the layout
var boxEnter = d3.selectAll("#boxes > g")
.attr("class", function(d) { return d.status; })
.attr("transform", function(d) { return "translate("+d.t+",0)"; })
.on('click', function(d) { togglebox(); update(); });
// make the outline of the box as a path
boxEnter.append('g')
.attr("class", "outline")
.append("path")
.transition()
.duration(500)
.attr('d', function(d) {
return 'M '+d.x0+' '+d.y0+' L '+d.x1+' '+d.y1+' L '+d.x2+' '+d.y2+' L '+d.x3+' '+d.y3+' z';
});
// make a drawing inside the box
boxEnter.append("g")
.attr("class", "drawing")
.style("opacity", function(d) { return d.opacity; })
.append("circle")
.attr("fill", function(d) { return color[d.id]; })
.attr("r", boxwidth)
.attr("cx", openboxwidth/2)
.attr("cy", boxheight/2);
// tell visitors what to do with the boxes
d3.select("body > svg").append("text")
.attr("text-anchor", "left")
.attr("x", margin)
.attr("y", (2*margin + boxheight))
.text("Click on a box to open it.");
// create a new set of box transform and path extremity points
function boxLayout(index) {
var b,
bl = [],
bx = 0;
for (var i=0; i<4; i++) {
b = {};
b.id = i;
b.t = bx;
b.x0 = b.x3 = 0;
b.x1 = b.x2 = (i === index) ? openboxwidth : boxwidth;
b.y0 = b.y1 = 0;
b.y2 = b.y3 = boxheight;
b.opacity = (i === index) ? 1 : 0;
b.status = (i === index) ? 'open' : 'closed';
bl.push(b);
bx += (i === index) ? openboxwidth : boxwidth;
}
return bl;
}
function update() {
// set the openbox index
d3.selectAll("#boxes").select(".open").each(function(d) { openbox = d.id; });
// make the layout with the new openbox
var boxes = boxLayout(openbox);
// move the boxes into place
var boxUpdate = d3.selectAll("#boxes > g")
.data(boxes, function(d) { return d.id; })
.transition()
.duration(500)
.attr("transform", function(d) { return "translate("+d.t+",0)"; });
// update the box frames
boxUpdate.select("g.outline > path")
.transition()
.duration(500)
.attr('d', function(d) {
return 'M '+d.x0+' '+d.y0+' L '+d.x1+' '+d.y1+' L '+d.x2+' '+d.y2+' L '+d.x3+' '+d.y3+' z';
});
// update the drawing opacity (exposes the new openbox and hides the old)
boxUpdate.select("g.drawing")
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", function(d) { return d.opacity; });
}
function togglebox() {
// close the open box
d3.select("#boxes").select(".open").attr("class", "closed");
// open the selected box
d3.select(d3.event.currentTarget).attr("class","open");
}
</script>
</body>
</html>