Skip to content

Instantly share code, notes, and snippets.

@theTonyHo
Forked from d3noob/.block
Last active November 20, 2019 06:00
Show Gist options
  • Save theTonyHo/61562c751af9c0abc8c7b67624467056 to your computer and use it in GitHub Desktop.
Save theTonyHo/61562c751af9c0abc8c7b67624467056 to your computer and use it in GitHub Desktop.
Interactive d3.js tree diagram

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

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 but it is a slightly cut down version.

Checkout JSFiddle and notebook on ObservableHQ Tidy-Tree

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>var d3TreeJsonURL = "https://catia-configurator.firebaseio.com/D3Tree/projects.json";
// Get data from Firebase url
var treeData = {name: "", type: "", children: []};
$.ajax({
url: d3TreeJsonURL,
async: false,
dataType: 'json',
success: function(data) {
for(var index in data.children) {
/* document.write( index + " : " + data.children[index] + "<br />") */;
treeData.name = data.name;
treeData.type = data.type;
var project = data.children[index];
// console.log("bf", project);
var projChildren = [];
for (var scenarioIndex in project.children) {
projChildren.push(project.children[scenarioIndex]);
}
project.children = projChildren;
// console.log(project);
treeData.children.push(project);
}
}
});
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 1920 - margin.right - margin.left,
height = 2160 - margin.top - margin.bottom,
radius = 3;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
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 + ")");
root = treeData;
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes 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);
nodeEnter.append("circle")
.attr("r",0.01)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
//.attr("x", function(d) { return d.children || d._children ? -13 : 13; })
.attr("x", function (d) {
var spacing = computeRadius(d) + 20;
return d.children || d._children ? -spacing : spacing;
})
.attr("dy", "1")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.html(function (d) {
return "<tspan x='0' dy='0'>" + d.name + "(" + d.type + ")" + "</tspan>"
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", function (d) {
return computeRadius(d);
})
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// 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);
}
function computeRadius(d) {
if (d.children || d._children) return radius + (radius * nbEndNodes(d) / 10);
else return radius;
}
function nbEndNodes(n) {
nb = 0;
if (n.children) {
n.children.forEach(function (c) {
nb += nbEndNodes(c);
});
} else if (n._children) {
n._children.forEach(function (c) {
nb += nbEndNodes(c);
});
} else nb++;
return nb;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment