Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active February 9, 2016 02:06
Show Gist options
  • Save mbostock/5369146 to your computer and use it in GitHub Desktop.
Save mbostock/5369146 to your computer and use it in GitHub Desktop.
How selectAll Works
license: gpl-3.0

This animation demonstrates how selection.selectAll works: every element in the old selection becomes a group in the new selection. Here, an initial selection of table rows is then used to create a selection of table cells:

d3.selectAll("tr").selectAll("td");

The result is a selection of table cells (td elements) grouped by their table row.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node text {
font: 10px sans-serif;
}
.join,
.link,
.node rect {
fill: none;
stroke: #636363;
stroke-width: 1.5px;
}
.right text {
text-anchor: end;
}
.node rect {
fill: white;
}
.link path,
.node rect,
.node text {
-webkit-transition: fill-opacity 500ms linear, stroke-opacity 500ms linear, stroke 500ms linear, fill 500ms linear;
-moz-transition: fill-opacity 500ms linear, stroke-opacity 500ms linear, stroke 500ms linear, fill 500ms linear;
-ms-transition: fill-opacity 500ms linear, stroke-opacity 500ms linear, stroke 500ms linear, fill 500ms linear;
-o-transition: fill-opacity 500ms linear, stroke-opacity 500ms linear, stroke 500ms linear, fill 500ms linear;
transition: fill-opacity 500ms linear, stroke-opacity 500ms linear, stroke 500ms linear, fill 500ms linear;
}
.node .array rect {
stroke: #636363;
}
.node .element rect {
fill: #bdbdbd;
stroke: none;
}
.node .null rect {
fill: none;
stroke: none;
}
.node .null text {
fill: #636363;
}
.node .function rect {
stroke: #3182bd;
}
.node .selection rect {
stroke: #e6550d;
}
.node .data rect {
fill: #d9d9d9;
stroke: none;
}
.node .code text {
font-family: monospace;
}
.node .key rect {
fill: #a1d99b;
stroke: none;
}
.link .to-data {
stroke: #ddd;
}
.link .to-element {
stroke: #bdbdbd;
}
.link .to-key,
.join {
stroke: #a1d99b;
}
.join {
stroke-dasharray: 2,2;
}
.link .to-null {
stroke-opacity: .5;
stroke-dasharray: .5,3.5;
stroke-linecap: round;
}
.play circle {
fill: #fff;
stroke: #000;
stroke-width: 3px;
}
.play:hover path {
fill: red;
}
.play rect {
fill: none;
pointer-events: all;
cursor: pointer;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 0, right: 120, bottom: 0, left: 120},
width = 720,
step = 100;
function tree(leftRoot, rightRoot, outerHeight) {
if (arguments.length < 3) outerHeight = rightRoot, rightRoot = null;
var height = outerHeight - margin.top - margin.bottom;
var tree = d3.layout.tree()
.size([height, 1])
.separation(function() { return 1; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-top", "58px");
var g = svg.selectAll("g")
.data([].concat(
leftRoot ? {type: "left", nodes: tree.nodes(leftRoot)} : [],
rightRoot ? {type: "right", nodes: tree.nodes(rightRoot).map(flip), flipped: true} : []
))
.enter().append("g")
.attr("class", function(d) { return d.type; })
.attr("transform", function(d) {
return "translate(" + (!!d.flipped * width + margin.left) + "," + margin.top + ")";
});
var node = g.append("g")
.attr("class", "node")
.selectAll("g")
.data(function(d) { return d.nodes; })
.enter().append("g")
.attr("class", function(d) { return d.type; })
.attr("transform", function(d) { return "translate(" + d.depth * step + "," + d.x + ")"; })
node.append("text")
.attr("x", function(d) { return d.flipped ? -6 : 6; })
.attr("dy", ".35em")
.text(function(d) { return d.name; })
.each(function(d) { d.width = Math.max(32, this.getComputedTextLength() + 12); });
node.filter(function(d) { return "join" in d; }).insert("path", "text")
.attr("class", "join")
.attr("d", d3.svg.diagonal()
.source(function(d) { return {y: d.width, x: 0}; })
.target(function(d) { return {y: 88, x: d.join * 24}; })
.projection(function(d) { return [d.y, d.x]; }));
node.insert("rect", "text")
.attr("ry", 6)
.attr("rx", 6)
.attr("y", -10)
.attr("height", 20)
.attr("width", function(d) { return d.width; })
.filter(function(d) { return d.flipped; })
.attr("x", function(d) { return -d.width; });
var link = g.insert("g", ".node")
.attr("class", "link")
.selectAll("path")
.data(function(d) { return tree.links(d.nodes); })
.enter().append("path")
.attr("class", function(d) { return "to-" + d.target.type + " from-" + d.source.type; })
.attr("d", d3.svg.diagonal()
.source(function(d) { return {y: d.source.depth * step + (d.source.flipped ? -1 : +1) * d.source.width, x: d.source.x}; })
.target(function(d) { return {y: d.target.depth * step, x: d.target.x}; })
.projection(function(d) { return [d.y, d.x]; }));
function flip(d) {
d.depth *= -1;
d.flipped = true;
return d;
}
return svg;
}
function treeAnimation(startRoot, startHeight, endRoot, endHeight) {
var end = tree(endRoot, endHeight).remove(),
height = +end.attr("height"),
start = tree(startRoot, startHeight).attr("height", height),
svg = start.node(),
offset = (endHeight - startHeight) / 2,
transform = "translate(" + margin.left + "," + offset + ")";
var play = start.append("g")
.attr("class", "play");
play.append("circle")
.attr("r", 45)
.attr("transform", "translate(" + (margin.left + width / 2) + "," + height / 2 + ")");
play.append("path")
.attr("d", "M-22,-30l60,30l-60,30z")
.attr("transform", "translate(" + (margin.left + width / 2) + "," + height / 2 + ")scale(.7)");
play.append("rect")
.attr("width", width)
.attr("height", height)
.on("click", function() { resetAll(); transition1(); });
end = d3.select(svg.appendChild(end.node().firstChild));
start = d3.select(svg.firstChild).attr("transform", transform);
end.selectAll(".array").each(function() { this.parentNode.appendChild(this); }); // mask elements
var startNodes = start.datum().nodes,
startElements = startNodes.filter(function(d) { return d.type === "element"; }),
endNodes = end.datum().nodes,
endGroups = endNodes.filter(function(d) { return d.type === "array"; });
resetAll();
function resetAll() {
start.call(reset).style("display", null);
end.call(reset).style("display", "none");
play.style("display", null);
}
function reset(svg) {
svg.selectAll(".node g,.link")
.style("stroke-opacity", null)
.style("fill-opacity", null);
start.selectAll(".array")
.attr("class", function(d) { return d.type; })
.attr("transform", function(d, i) { return "translate(" + d.depth * step + "," + d.x + ")"; })
.select("rect")
.attr("width", function(d) { return d.width; });
}
function transition1() {
play.style("display", "none");
var t = start.transition()
.duration(1000 + (startElements.length - 1) * 50)
.each("end", transition2);
t.selectAll(".selection,.array,.link")
.duration(0)
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
t.selectAll(".element")
.duration(500)
.delay(function(d, i) { return 500 + i * 50; })
.attr("transform", function(d, i) { return "translate(" + (d.depth - 1) * step + "," + (endGroups[i].x - offset) + ")"; })
.attr("class", "array")
.select("rect")
.attr("width", function(d, i) { return endGroups[i].width; });
}
function transition2() {
end.style("display", null)
.selectAll(".element,.to-element")
.style("display", "none");
end.selectAll(".selection,.to-array,.array")
.style("stroke-opacity", 0)
.style("fill-opacity", 0)
.transition()
.duration(0)
.style("stroke-opacity", 1)
.style("fill-opacity", 1);
end.transition()
.duration(500)
.each("end", transition3);
}
function transition3() {
start.style("display", "none");
end.selectAll(".element")
.style("display", null)
.attr("transform", function(d) { return "translate(" + d.parent.depth * step + "," + d.parent.x + ")"; })
.transition()
.duration(500)
.delay(function(d, i) { return i * 50; })
.attr("transform", function(d) { return "translate(" + d.depth * step + "," + d.x + ")"; });
end.selectAll(".to-element")
.style("display", null)
.attr("d", d3.svg.diagonal()
.source(function(d) { return {y: d.source.depth * step + d.source.width, x: d.source.x}; })
.target(function(d, i) { return {y: d.source.depth * step + d.source.width, x: d.source.x}; })
.projection(function(d) { return [d.y, d.x]; }))
.transition()
.duration(500)
.delay(function(d, i) { return i * 50; })
.attr("d", d3.svg.diagonal()
.source(function(d) { return {y: d.source.depth * step + d.source.width, x: d.source.x}; })
.target(function(d, i) { return {y: d.target.depth * step, x: d.target.x}; })
.projection(function(d) { return [d.y, d.x]; }));
end.transition()
.duration(5000)
.each("end", resetAll);
}
}
treeAnimation(
{type: "selection", name: "selection", children: [
{type: "array", name: "group₀", children: [
{type: "element", name: "tr₀"},
{type: "element", name: "tr₁"},
{type: "element", name: "tr₂"},
{type: "element", name: "tr₃"}
]}
]},
24 * 4,
{type: "selection", name: "selection", children: [
{type: "array", name: "group₀", children: [
{type: "element", name: "td₀"},
{type: "element", name: "td₁"},
{type: "element", name: "td₂"},
{type: "element", name: "td₃"}
]},
{type: "array", name: "group₁", children: [
{type: "element", name: "td₄"},
{type: "element", name: "td₅"},
{type: "element", name: "td₆"},
{type: "element", name: "td₇"}
]},
{type: "array", name: "group₂", children: [
{type: "element", name: "td₈"},
{type: "element", name: "td₉"},
{type: "element", name: "td₁₀"},
{type: "element", name: "td₁₁"}
]},
{type: "array", name: "group₃", children: [
{type: "element", name: "td₁₂"},
{type: "element", name: "td₁₃"},
{type: "element", name: "td₁₄"},
{type: "element", name: "td₁₅"}
]}
]},
24 * 16
);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment