block by nitaku 23d7afb3b421a127b8d5

Hierarchical Clustering

Full Screen

-

index.js

(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);

index.html

<!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="//d3js.org/d3.v3.min.js"></script>
        <script src="//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>

index.coffee

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)

index.css

svg {
  background: white;
}

.link {
  fill: none;
  stroke: #BBB;
  stroke-width: 1;
  shape-rendering: crispEdges;
}
.node {
  shape-rendering: crispEdges;
}