Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:16
Show Gist options
  • Save nitaku/23d7afb3b421a127b8d5 to your computer and use it in GitHub Desktop.
Save nitaku/23d7afb3b421a127b8d5 to your computer and use it in GitHub Desktop.
Hierarchical Clustering
svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
# translate the viewBox to have (0,0) at the center of the vis
svg
.attr
viewBox: "#{-width/2} #{-height/2} #{width} #{height}"
items = d3.range(40).map (d) -> [
30+Math.random()*200,
30+Math.random()*200,
30+Math.random()*200
]
console.debug 'Computing hierarchical clustering...'
clusters = clusterfck.hcluster(
items,
clusterfck.EUCLIDEAN_DISTANCE,
clusterfck.SINGLE_LINKAGE
)
tree = tree_utils.binary_to_std(clusters)
tree_a = _.cloneDeep tree
tree_b = _.cloneDeep tree
tree_c = _.cloneDeep tree
tree_utils.canonical_sort(tree_b)
tree_utils.recursive_sort(
tree_c,
(a,b) -> d3.hcl(a.key).l - d3.hcl(b.key).l,
(n) ->
if n.children?
n.key = _.min(n.children, (c) -> d3.hcl(c.key).l).key
else
n.key = d3.rgb(n.value[0],n.value[1],n.value[2])
)
cluster_layout = d3.layout.cluster()
.size([440, 200])
.separation((d) -> 1)
nodes_a = cluster_layout.nodes(tree_a)
links_a = cluster_layout.links(nodes_a)
nodes_b = cluster_layout.nodes(tree_b)
links_b = cluster_layout.links(nodes_b)
nodes_c = cluster_layout.nodes(tree_c)
links_c = cluster_layout.links(nodes_c)
console.debug 'Drawing...'
original = svg.append('g')
.attr('transform', 'translate(-450,-230)')
result_a = svg.append('g')
.attr('transform', 'translate(10,-230)')
result_b = svg.append('g')
.attr('transform', 'translate(-450,10)')
result_c = svg.append('g')
.attr('transform', 'translate(10,10)')
original.selectAll('.node')
.data(items)
.enter().append('rect')
.attr('class', 'node')
.attr('x', -4)
.attr('y', -5)
.attr('width', 8)
.attr('height', 24)
.attr('transform', (d, i) -> "translate(#{6+i*11},200)")
.attr('fill', (d) -> d3.rgb(d[0],d[1],d[2]))
result_a.selectAll('.link')
.data(links_a)
.enter().append('path')
.attr('class', 'link')
.attr('d', (l) -> "M#{l.source.x} #{l.source.y} L#{l.target.x} #{l.source.y} L#{l.target.x} #{l.target.y}")
result_a.selectAll('.node')
.data(nodes_a.filter (n) -> not n.children?)
.enter().append('rect')
.attr('class', 'node')
.attr('x', -4)
.attr('y', -5)
.attr('width', 8)
.attr('height', 24)
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
.attr('fill', (d) -> d3.rgb(d.value[0],d.value[1],d.value[2]))
result_b.selectAll('.link')
.data(links_b)
.enter().append('path')
.attr('class', 'link')
.attr('d', (l) -> "M#{l.source.x} #{l.source.y} L#{l.target.x} #{l.source.y} L#{l.target.x} #{l.target.y}")
result_b.selectAll('.node')
.data(nodes_b.filter (n) -> not n.children?)
.enter().append('rect')
.attr('class', 'node')
.attr('x', -4)
.attr('y', -5)
.attr('width', 8)
.attr('height', 24)
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
.attr('fill', (d) -> d3.rgb(d.value[0],d.value[1],d.value[2]))
result_c.selectAll('.link')
.data(links_c)
.enter().append('path')
.attr('class', 'link')
.attr('d', (l) -> "M#{l.source.x} #{l.source.y} L#{l.target.x} #{l.source.y} L#{l.target.x} #{l.target.y}")
result_c.selectAll('.node')
.data(nodes_c.filter (n) -> not n.children?)
.enter().append('rect')
.attr('class', 'node')
.attr('x', -4)
.attr('y', -5)
.attr('width', 8)
.attr('height', 24)
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
.attr('fill', (d) -> d3.rgb(d.value[0],d.value[1],d.value[2]))
result_c.selectAll('.internal_node')
.data(nodes_c.filter (n) -> n.children?)
.enter().append('circle')
.attr('class', 'internal_node')
.attr('r', 4)
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
.attr('fill', (d) -> d.key)
svg {
background: white;
}
.link {
fill: none;
stroke: #BBB;
stroke-width: 1;
shape-rendering: crispEdges;
}
.node {
shape-rendering: crispEdges;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Hierarchical Clustering" />
<title>Hierarchical Clustering</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="//wafi.iit.cnr.it/webvis/tmp/clusterfck.js"></script>
<script src="//wafi.iit.cnr.it/webvis/libs/jigmaps/zip.js"></script>
<script src="//wafi.iit.cnr.it/webvis/libs/jigmaps/tree_utils.js"></script>
</head>
<body>
<svg height="500" width="960"></svg>
<script src="index.js"></script>
</body>
</html>
(function() {
var cluster_layout, clusters, height, items, links_a, links_b, links_c, nodes_a, nodes_b, nodes_c, original, result_a, result_b, result_c, svg, tree, tree_a, tree_b, tree_c, width;
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
svg.attr({
viewBox: "" + (-width / 2) + " " + (-height / 2) + " " + width + " " + height
});
items = d3.range(40).map(function(d) {
return [30 + Math.random() * 200, 30 + Math.random() * 200, 30 + Math.random() * 200];
});
console.debug('Computing hierarchical clustering...');
clusters = clusterfck.hcluster(items, clusterfck.EUCLIDEAN_DISTANCE, clusterfck.SINGLE_LINKAGE);
tree = tree_utils.binary_to_std(clusters);
tree_a = _.cloneDeep(tree);
tree_b = _.cloneDeep(tree);
tree_c = _.cloneDeep(tree);
tree_utils.canonical_sort(tree_b);
tree_utils.recursive_sort(tree_c, function(a, b) {
return d3.hcl(a.key).l - d3.hcl(b.key).l;
}, function(n) {
if (n.children != null) {
return n.key = _.min(n.children, function(c) {
return d3.hcl(c.key).l;
}).key;
} else {
return n.key = d3.rgb(n.value[0], n.value[1], n.value[2]);
}
});
cluster_layout = d3.layout.cluster().size([440, 200]).separation(function(d) {
return 1;
});
nodes_a = cluster_layout.nodes(tree_a);
links_a = cluster_layout.links(nodes_a);
nodes_b = cluster_layout.nodes(tree_b);
links_b = cluster_layout.links(nodes_b);
nodes_c = cluster_layout.nodes(tree_c);
links_c = cluster_layout.links(nodes_c);
console.debug('Drawing...');
original = svg.append('g').attr('transform', 'translate(-450,-230)');
result_a = svg.append('g').attr('transform', 'translate(10,-230)');
result_b = svg.append('g').attr('transform', 'translate(-450,10)');
result_c = svg.append('g').attr('transform', 'translate(10,10)');
original.selectAll('.node').data(items).enter().append('rect').attr('class', 'node').attr('x', -4).attr('y', -5).attr('width', 8).attr('height', 24).attr('transform', function(d, i) {
return "translate(" + (6 + i * 11) + ",200)";
}).attr('fill', function(d) {
return d3.rgb(d[0], d[1], d[2]);
});
result_a.selectAll('.link').data(links_a).enter().append('path').attr('class', 'link').attr('d', function(l) {
return "M" + l.source.x + " " + l.source.y + " L" + l.target.x + " " + l.source.y + " L" + l.target.x + " " + l.target.y;
});
result_a.selectAll('.node').data(nodes_a.filter(function(n) {
return n.children == null;
})).enter().append('rect').attr('class', 'node').attr('x', -4).attr('y', -5).attr('width', 8).attr('height', 24).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).attr('fill', function(d) {
return d3.rgb(d.value[0], d.value[1], d.value[2]);
});
result_b.selectAll('.link').data(links_b).enter().append('path').attr('class', 'link').attr('d', function(l) {
return "M" + l.source.x + " " + l.source.y + " L" + l.target.x + " " + l.source.y + " L" + l.target.x + " " + l.target.y;
});
result_b.selectAll('.node').data(nodes_b.filter(function(n) {
return n.children == null;
})).enter().append('rect').attr('class', 'node').attr('x', -4).attr('y', -5).attr('width', 8).attr('height', 24).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).attr('fill', function(d) {
return d3.rgb(d.value[0], d.value[1], d.value[2]);
});
result_c.selectAll('.link').data(links_c).enter().append('path').attr('class', 'link').attr('d', function(l) {
return "M" + l.source.x + " " + l.source.y + " L" + l.target.x + " " + l.source.y + " L" + l.target.x + " " + l.target.y;
});
result_c.selectAll('.node').data(nodes_c.filter(function(n) {
return n.children == null;
})).enter().append('rect').attr('class', 'node').attr('x', -4).attr('y', -5).attr('width', 8).attr('height', 24).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).attr('fill', function(d) {
return d3.rgb(d.value[0], d.value[1], d.value[2]);
});
result_c.selectAll('.internal_node').data(nodes_c.filter(function(n) {
return n.children != null;
})).enter().append('circle').attr('class', 'internal_node').attr('r', 4).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).attr('fill', function(d) {
return d.key;
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment