Skip to content

Instantly share code, notes, and snippets.

@StewartNoyce
Last active August 29, 2015 13:57
Show Gist options
  • Save StewartNoyce/9496239 to your computer and use it in GitHub Desktop.
Save StewartNoyce/9496239 to your computer and use it in GitHub Desktop.
Box Transition Revisited

This example revisits the box transition lesson, which was an initial attempt to build a box layout function for D3. That lesson exposed my emerging understanding of transition behavior. Clearly, the result was awkward. Box outlines would move before they morphed. It had to finish one task before it could start another.

For a seamless transition, this version eliminates the transform translate transition on the starting point of each box in the box layout transition code. Now, each box outline has a path that originates from the starting point and expresses either an open box (square) or a closed box (rectangle). This method makes a smooth box transition with nothing that sticks out.

This change exposed another issue where the click handler on the box would sometimes fire for an adjacent box, but not always. This issue was fixed by adding the box contents only to the open box. Now the update code removes the contents of the previously open box and adds the appropriate diagram contents of the newly opened box.

<!DOCTYPE html>
<html>
<head>
<!-- this version adds and removes the diagram in the open box -->
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
body {
font: 10px sans-serif;
margin: 0;
}
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");
// 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");
// set an event handler on each box
var boxEnter = d3.selectAll("#boxes > g")
.attr("class", function(d) { return d.status; })
.on('click', function(d) { togglebox(); update(); });
// make the outline of each 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; })
.each(function(d) { (openbox === d.id) ? add_diagram(d) : {} ; });
// 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.");
// layout box outline extremity points
function boxLayout(index) {
var b,
bl = [],
bx = margin;
for (var i=0; i<4; i++) {
b = {};
b.id = i;
b.x0 = b.x3 = bx;
b.x1 = b.x2 = (i === index) ? bx + openboxwidth : bx + boxwidth;
b.y0 = b.y1 = margin;
b.y2 = b.y3 = margin + 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() {
// identify the open box
d3.selectAll("#boxes").select(".open").each(function(d) { openbox = d.id; });
// remake the layout with the new open box
var boxes = boxLayout(openbox);
// select the boxes and assign the new layout to them
var boxUpdate = d3.selectAll("#boxes > g")
.data(boxes, function(d) { return d.id; });
// update the box outlines
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';
});
// remove diagram from previously open box
remove_diagram();
// add diagram to the new open box
boxUpdate.each(function(d) { (openbox === d.id) ? add_diagram(d) : {} ; });
// expose the open box contents gradually
boxUpdate.select("g.drawing")
.transition()
.duration(750)
.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");
}
function add_diagram(d) {
d3.selectAll("#boxes").select("g.open").select("g.drawing")
.append("circle")
.attr("fill", function(d) { return color[d.id]; })
.attr("r", boxwidth)
.attr("cx", function (d) { return (d.x0 + d.x1)/2; })
.attr("cy", function (d) { return d.y0 + boxheight/2; });
}
function remove_diagram() {
d3.selectAll("#boxes").select("g.drawing > circle")
.remove();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment