Skip to content

Instantly share code, notes, and snippets.

@d3noob
Last active May 24, 2023 03:23
Show Gist options
  • Save d3noob/43a860bc0024792f8803bba8ca0d5ecd to your computer and use it in GitHub Desktop.
Save d3noob/43a860bc0024792f8803bba8ca0d5ecd 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.

<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var treeData =
{
"name": "Top Level",
"children": [
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{ "name": "Level 2: B" }
]
};
// 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 = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d){ d.y = d.depth * 180});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(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.y0 + "," + source.x0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
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.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// 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.x0, y: source.y0}
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;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
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>
@suarezph
Copy link

suarezph commented Apr 25, 2017

Dude, how to make this tree vertical instead of horizontal.

@clarkus978
Copy link

How would I accomplish this using an external json file?

@andrewterra
Copy link

How could I make it collapse at the root node instead of the root node's children.

@cstadler1000
Copy link

Very nice job ! Would it be possible to add a license to this code ?

@DrPDash
Copy link

DrPDash commented Apr 10, 2020

This is nice and helps people not so familiar with D3 updates- works with the latest D3JS.

Question:
If for a child element, there are some properties, how to display the properties inside a box?

For example, I use the following example JSON file to create my tree. I would like the properties of the 3rd child (name, doc, code) shown in a box when the user clicks on the child element.

{
	"name": "Help me to choose a product",
	"children": [
		{
			"name": "Chlorophyll",
			"children": [{
					"name": "Chl Prod 1"
				},
				{
					"name": "Chl Prod 2"
				},
				{
					"name": "Chl Prod 3",
					"properties": {
						"Name": "New Born",
						"Doc": "In Print",
						"Code": "New Git"
					}
				}
			]
		},
		{
			"name": "Sea Surface Temperature",
			"children": [{
					"name": "SST Prod 1"
				},
				{
					"name": "SST Prod 2"
				},
				{
					"name": "SST Prod 3"
				}
			]
		},
		{
			"name": "Sea Surface Salinity",
			"children": [{
					"name": "SSS Prod 1"
				}
			]
		},
		{
			"name": "Altimetry",
			"children": [{
					"name": "SSH Prod 1"
				}
			]
		}
	]
}

@d3noob
Copy link
Author

d3noob commented Apr 10, 2020

I'm afraid that I won't be able to solve this for you, but I can hopefully steer you in the right direction. I found some direction here, here and here which might be of assistance.

@DrPDash
Copy link

DrPDash commented Apr 21, 2020

@d3noob You actually solved this for me by pointing to the right resources. I could not find those earlier, perhaps because I did not know what to search for ('m new to D3). Those pointers were useful to show how to add click events and URLs to D3 elements. Many thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment