Skip to content

Instantly share code, notes, and snippets.

@denisemauldin
Last active October 4, 2017 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save denisemauldin/c5b1c67dec3cac0b7cb0f189ff57dada to your computer and use it in GitHub Desktop.
Save denisemauldin/c5b1c67dec3cac0b7cb0f189ff57dada to your computer and use it in GitHub Desktop.
Collapsible tree diagram in v4
license: mit

This is a d3.js tree diagram that incldes an interactive element as used as an example in the book D3 Tips and Tricks v4.x.

Any parent node can be clicked on to collapse the portion of the tree below it, on itself. Conversly, it can be clicked on again to regrow.

It is derived from the Mike Bostock Collapsible tree example and updated to use v4.

Kudos and thanks also go out to Soumya Ranjan for steering me in the fight direction for the diagonal solution.

forked from rijine's block: Collapsible tree diagram in v4

forked from sampath-karupakula's block: Collapsible tree diagram in v4

<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
font-weight:bold;
}
.link {
fill: none;
stroke: steelblue;
stroke-width: 6px;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var resp = {
"name": "Top Level",
"children": [
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{ "name": "Level 2: B" }
]
};
var treeData;
// Set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 960 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectH=30,
rectW=60,
root;
var svg = d3.select("body").append("svg")
.attr("width","100%")
.attr("height", 800)
.call(d3.zoom()
.scaleExtent([1 / 2, 8])
.on("zoom", zoomed))
.append("g")
.attr("transform", "translate("
+ (margin.right) + "," + margin.top + ")");
function zoomed() {
svg.attr("transform", d3.event.transform);
}
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
// declares a tree layout and assigns the size
var treemap = d3.tree().nodeSize([170, 170]);
d3.json("people.json", function(error, data) {
if (error) throw error;
treeData = data;
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
//form x and y axis
root.x0 = 0;
root.y0 = height/2;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
});
// Collapse the node and all it's children
function getText(node) {
var textsize = 12;
var maxChar = (rectW / textsize);
var text = node.name;
if (node.node_id == "dummy_node") {
return "";
} else {
return text;
}
}
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var rootNode = treeData.descendants()[0];
var nodes = treeData.descendants().slice(1),
links = treeData.descendants().slice(rootNode.children.length+1);
// Normalize for fixed-depth.
nodes.forEach(function(d){
console.log(d);
//if(d.depth == 1){
d.y = d.depth * 180;
//}else{
// d.y = d.depth * 40;
//}
});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {console.log(d);return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.on('click', click);
// Add Rectangle for the nodes
nodeEnter.append("rect")
.attr('x',-70)
.attr('y',-120)
.attr("width", 100)
.attr("height", 100)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append("text").attr("x", 0).attr("y", -40).attr("width",150 + 20)
.attr("dy", ".35em").attr("text-anchor", "middle").text(function(d) { return d.data.name; }).call(wrap, 80);
/*nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) { return d.data.name; });
*/
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// Update the node attributes and style
nodeUpdate.select("rect").attr("width", 150).attr("height", 150)
.attr("stroke", "black")
.attr("stroke-width",1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
nodeUpdate.select("text").style("fill-opacity", 1);
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select("rect")
.attr('x',-70)
.attr('y',-70)
.attr("width", 150).attr("height", 150)
.attr("stroke", "black")
.attr("stroke-width", 1);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x, y: source.y}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x * 2;
d.y0 = d.y * 2;
});
}
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.x} ${s.y}
C ${(s.x + d.x)/2} ${s.y},
${(s.x + d.x) / 2} ${d.y},
${d.x} ${d.y}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
</body>
{ "name": "toplevel",
"children": [
{
"name": "Rich",
"children": [
{
"name": "John",
"children": [
{"name": "Lee"},
{"name": "Candy"},
{"name": "Elise"},
{"name": "Sydney"},
{"name": "Garth"},
{"name": "Otto"},
{"name": "Randy"}
]
},
{
"name": "Corry",
"children": [
{"name": "Adam"},
{"name": "Vinney"},
{"name": "Adison"}
]
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment