Force Directed clusters

by Andy Jones

HTML

<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">

JavaScript

var width          = 960,
    height         = 500,
    padding        = 1.5,
    clusterPadding = 6,
    maxRadius      = 12;

var n = 300,
    m = 10;

var color = d3.scale.category10()
                    .domain(d3.range(m));

var clusters = new Array(m);

var nodes = d3.range(n)
              .map(function(){
                   var i = Math.floor(Math.random() * m),
                       r = Math.sqrt((i + 1 ) / m * -Math.log(Math.random())) * maxRadius,
                       d = {cluster: i, radius: r};
                  
                  if (!clusters[i] || (r > clusters[i].radius))  clusters[i] = d;
                  return d;
});
                   
// Use the pack layout to initialize node positions.
d3.layout.pack()
    .sort(null)
    .size([width, height])
    .children(function(d) { return d.values; })
    .value(function(d) { return d.radius * d.radius; })
    .nodes({values: d3.nest()
      .key(function(d) { return d.cluster; })
      .entries(nodes)});

var force = d3.layout.force()
                     .nodes(nodes)
                     .size([width, height])
                     .gravity(0.02)
                     .charge(0)
                     .on("tick", tick)
                     .start();

var svg = d3.select("body").append("svg")
                           .attr("width", width)
                           .attr("height", height);

var node = svg.selectAll("circle")
              .data(nodes)
              .enter().append("circle")
              .style("fill", function(d) { return color(d.cluster); })
              .call(force.drag);

node.transition()
    .duration(750)
    .delay(function(d, i) { return i * 5; })
    .attrTween("r", function(d) {
        var i = d3.interpolate(0, d.radius);
        return function(t) { return d.radius = i(t); };
    });

function tick(e) {
     node.each(cluster(10 * e.alpha * e.alpha))
         .each(collide(0.5))
         .attr("cx", function(d) { return d.x; })
         .attr("cy", function(d) {...