Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active October 26, 2019 07:07
Show Gist options
  • Save Andrew-Reid/c7ae41a98b8cbb38f1febf13deb9d294 to your computer and use it in GitHub Desktop.
Save Andrew-Reid/c7ae41a98b8cbb38f1febf13deb9d294 to your computer and use it in GitHub Desktop.
Tidy Tree vs Dendogram

This bl.ock mostly just duplicates the cannonical d3.tree and d3.cluster bl.ocks by Mike Bostock. Intended to visualize differences in layout. It proceeds through transitions of:

  • Vertical Tree
  • Vertical Cluster
  • Radial Tree
  • Radial Cluster
  • Horizontal Tree
  • Horizontal Cluster

The difference between cluster and tree (dendogram and tidy tree respectively) is that the cluster puts all childless nodes (terminal nodes) at the same level, while the tree layout places all nodes with the same parent at the same level.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.link {
stroke-width: 2px;
}
</style>
</head>
<body>
<script type="text/javascript">
var data = { "name": "Root", "children": [
{ "name": "A", "children": [ {"name": "A-1" }, {"name": "A-2" }, {"name":"A-3"}, {"name":"A-4"}, { "name":"A-5"} ] },
{ "name": "B", "children": [ {"name": "B-1" } ] },
{ "name": "C" },
{ "name": "D", "children": [ {"name": "D-1" }, {"name": "D-2" }, {"name": "D-3", "children": [ {"name": "D-3-i"}, {"name":"D-3-ii"} ] } ] },
{ "name": "E" },
{ "name": "F" }
] };
var width = 960;
var height = 500;
margin = {left: 100, top: 100, right: 50, bottom: 50}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');
var root = d3.hierarchy(data);
// Tree
var tree = d3.tree()
.size([width-margin.left-margin.right,height-margin.top-margin.bottom]);
// Cluster
var cluster = d3.cluster()
.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
// Set initial vertical Tree
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("path")
.attr("class", "link")
.attr("fill","none")
.attr("stroke","#ccc")
.attr("d", d3.linkVertical()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }));
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
node.append("circle")
.attr("r", 2.5);
node.append("text")
.text(function(d) { return d.data.name; })
.attr('y',-10)
.attr('x',-10)
.attr('text-anchor','middle');
verticalCluster();
showcase();
function verticalTree() {
// Transition to vertical
///
g.transition().attr("transform",'translate('+ margin.left +','+ margin.right +')').duration(5000);
tree.size([width-margin.left-margin.right,height-margin.top-margin.bottom]);
link.data(tree(root).links())
.transition()
.attr("d", d3.linkVertical()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; }))
.duration(5000);
node.transition()
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.duration(5000)
}
function horizontalTree() {
// Transition to horizontal
///
g.transition().attr("transform",'translate('+ margin.left +','+ margin.right +')').duration(5000);
tree.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
link.data(tree(root).links())
.transition()
.attr("d", d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; }))
.duration(5000);
node.transition()
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.duration(5000);
}
function radialTree() {
// Transition to Radial
//
g.transition().attr("transform","translate("+width/2+","+height/2+")").duration(5000);
tree.size([2 * Math.PI, height/2]);
link.data(tree(root).links())
.transition()
.attr("d", d3.linkRadial()
.angle(function(d) { return d.x; })
.radius(function(d) { return d.y; }))
.duration(5000);
node.transition()
.attr("transform", function(d) { return "translate(" + radialPoint(d.x, d.y) + ")"; })
.duration(5000);
}
function horizontalCluster() {
g.transition().attr("transform",'translate('+ margin.left +','+ margin.right +')').duration(5000);
cluster.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
link
.data(cluster(root).links())
.transition()
.attr("d", function(d) { console.log(d);
return "M" + d.source.y + "," + d.source.x
+ "C" + (d.source.y +100) + "," + d.source.x
+ " " + (d.source.y +100) + "," + d.target.x
+ " " + d.target.y + "," + d.target.x;
})
.duration(5000)
node.transition()
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.duration(5000);
}
function verticalCluster() {
g.transition().attr("transform",'translate('+ margin.left +','+ margin.right +')').duration(5000);
cluster.size([width-margin.left-margin.right,height-margin.top-margin.bottom]);
link
.data(cluster(root).links())
.transition()
.attr("d", function(d) { console.log(d);
return "M" + d.source.x + "," + d.source.y
+ "C" + d.source.x + "," + (d.source.y+60)
+ " " + d.target.x + "," + (d.source.y+60)
+ " " + d.target.x + "," + d.target.y;
})
.duration(5000)
node.transition()
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.duration(5000);
}
function radialCluster() {
g.transition().attr("transform","translate("+width/2+","+height/2+")").duration(5000);
cluster.size([2 * Math.PI,height/2 - 40]);
link
.data(cluster(root).links())
.transition()
.attr("d", function(d) {
return "M" + radialPoint(d.source.x, d.source.y)
+ "C" + radialPoint(d.source.x, (d.target.y + d.source.y) / 2)
+ " " + radialPoint(d.target.x, (d.target.y + d.source.y) / 2)
+ " " + radialPoint(d.target.x, d.target.y);
})
.duration(5000)
node.transition()
.attr("transform", function(d) { return "translate(" + radialPoint(d.x, d.y) + ")"; })
.duration(5000);
}
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
var i = 1;
function showcase() {
setTimeout(function() {
if (++i%6 == 4) { horizontalTree(); }
else if (i%6 == 2) { radialTree(); }
else if (i%6 == 5) { horizontalCluster(); }
else if (i%6 == 3) { radialCluster(); }
else if (i%6 == 1) { verticalCluster(); }
else { verticalTree(); }
showcase();
}, 5500)
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment