Visualizing data with D3.js Trees

Interactive diagrams don’t just look good — they tell stories.
For music fans, there's nothing more engaging than exploring musical lineage, and tools like D3.js bring that history to life with powerful, collapsible tree structures.
Full code at the end but read the explainer first - don't be impatient!
🔹 Why Use D3.js for Trees?
D3.js offers fine-grained control over every element on the screen — from node positioning to transition timing. You can craft a visualization that:
- Collapses and expands dynamically
- Responds to user clicks
- Scales beautifully on all screen sizes
Perfect for things like:
- Musical lineages
- Company org charts
- Product line evolution
- Tech stack inheritance
Getting Started: A Minimal Working Tree
Here’s a simplified code sample of what I used on the British Blues Boom demo:
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="tree"></div>
<script>
const data = {
name: "Bands",
children: [
{
name: "The Yardbirds",
children: [
{ name: "Eric Clapton", children: [ { name: "Cream" } ] },
{ name: "Jimmy Page", children: [ { name: "Led Zeppelin" } ] },
{ name: "Jeff Beck", children: [ { name: "The Jeff Beck Group" } ] }
]
}
]
};
// tree layout + render code goes here (see full example on site)
</script>
💡 Why It Matters
This kind of interactivity does more than impress — it engages.
Your content becomes stickier, your bounce rates drop, and your storytelling becomes memorable.
Great UI isn’t decoration — it’s persuasion.
And it’s not just for music. You can adapt this technique for:
- Mapping relationships
- Explaining tech stacks
- Visualizing customer journeys
Built-In to DevStack
If you're a developer looking to plug in a feature like this, I’ve already done the heavy lifting for you.
My DevStack includes a modular, reusable D3.js integration you can drop into any ASP.NET Core site.
No need to wrestle with JSON or browser quirks — I’ve been there.
🎯 Business Benefits
As someone with over 30 years of business experience, I don’t just build fancy visualizations — I build tools that make content more valuable, more engaging, and ultimately... more profitable.
Ask me how one client added £25K annual sales with a single UI enhancement.
Ready to Engage Your Audience?
This type of feature makes your content shareable, sticky, and smart — especially when paired with solid back-end architecture.
If you want to skip the D3.js learning curve and integrate it fast:
👉 Request access to DevStack or get in touch
Here's a fully working example of a collapsible tree diagram using D3.js from scratch.
Full Implementation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Collapsible Tree</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.node circle {
fill: #999;
stroke: steelblue;
stroke-width: 2px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<div id="tree"></div>
<script>
// JSON Data
const data = {
"name": "Bands",
"children": [
{
"name": "The Yardbirds",
"children": [
{
"name": "Eric Clapton",
"children": [
{ "name": "Cream" },
{ "name": "Derek and the Dominos" }
]
},
{
"name": "Jimmy Page",
"children": [
{ "name": "Led Zeppelin" }
]
},
{
"name": "Jeff Beck",
"children": [
{ "name": "The Jeff Beck Group" }
]
}
]
},
{
"name": "Fleetwood Mac",
"children": [
{
"name": "Peter Green",
"children": [
{ "name": "Solo Career" }
]
},
{
"name": "Mick Fleetwood",
"children": [
{ "name": "Fleetwood Mac (1970s Evolution)" }
]
}
]
}
]
};
// Create the tree layout
const width = 960;
const dx = 20;
const dy = 180;
const margin = { top: 20, right: 120, bottom: 20, left: 120 };
const tree = d3.tree().nodeSize([dx, dy]);
const diagonal = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
const svg = d3.select("#tree").append("svg")
.attr("viewBox", [-margin.left, -margin.top, width, dx])
.style("font", "10px sans-serif")
.style("user-select", "none");
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const root = d3.hierarchy(data);
root.x0 = dx / 2;
root.y0 = 0;
// Collapse all nodes by default
root.descendants().forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth) d.children = null;
});
// Function to update the tree
function update(source) {
const nodes = root.descendants();
const links = root.links();
tree(root);
let left = root;
let right = root;
root.eachBefore(node => {
if (node.x < left.x) left = node;
if (node.x > right.x) right = node;
});
const height = right.x - left.x + margin.top + margin.bottom;
svg.transition()
.duration(750)
.attr("viewBox", [-margin.left, left.x - margin.top, width, height]);
const node = g.selectAll("g.node")
.data(nodes, d => d.id);
const nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", d => `translate(${source.y0},${source.x0})`)
.on("click", (event, d) => {
d.children = d.children ? null : d._children;
update(d);
});
nodeEnter.append("circle")
.attr("r", 5)
.attr("fill", d => d._children ? "#555" : "#999");
nodeEnter.append("text")
.attr("dy", "0.31em")
.attr("x", d => d._children ? -10 : 10)
.attr("text-anchor", d => d._children ? "end" : "start")
.text(d => d.data.name);
const nodeUpdate = node.merge(nodeEnter).transition()
.duration(750)
.attr("transform", d => `translate(${d.y},${d.x})`);
nodeUpdate.select("circle")
.attr("r", 5)
.attr("fill", d => d._children ? "#555" : "#999");
const nodeExit = node.exit().transition()
.duration(750)
.attr("transform", d => `translate(${source.y},${source.x})`)
.remove();
const link = g.selectAll("path.link")
.data(links, d => d.target.id);
const linkEnter = link.enter().append("path")
.attr("class", "link")
.attr("d", d => {
const o = { x: source.x0, y: source.y0 };
return diagonal({ source: o, target: o });
});
link.merge(linkEnter).transition()
.duration(750)
.attr("d", diagonal);
link.exit().transition()
.duration(750)
.attr("d", d => {
const o = { x: source.x, y: source.y };
return diagonal({ source: o, target: o });
})
.remove();
root.eachBefore(d => {
d.x0 = d.x;
d.y0 = d.y;
});
}
update(root);
</script>
<!-- Visual Studio Browser Link -->
<script type="text/javascript" src="/_vs/browserLink" async="async" id="__browserLink_initializationData" data-requestId="5c8b24c8ae3e4264a30fdf4f0f4fb2e7" data-requestMappingFromServer="false" data-connectUrl="http://localhost:56376/c11a15de0f1f44c49cfaad8279c090cf/browserLink"></script>
<!-- End Browser Link -->
<script src="/_framework/aspnetcore-browser-refresh.js"></script></body>
</html>