// adapted from http://bl.ocks.org/1747543 function force_layout() { var width = 500, height = 400, padding = 3; var circle = svg.selectAll("circle"); var nodes = circle .each(function(d) { var my = d3.select(this); d.radius = parseFloat(my.attr("r")); d.color = my.attr("fill"); d.x = parseFloat(my.attr("cx")) + Math.random(); d.y = parseFloat(my.attr("cy")) + Math.random(); }) .data(); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start(); circle.call(force.drag); function tick(e) { circle .each(cluster(10 * e.alpha * e.alpha)) .each(collide(.5)) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } // Move d to be adjacent to the cluster node. function cluster(alpha) { var max = {}; // Find the largest node for each cluster. nodes.forEach(function(d) { if (!(d.color in max) || (d.radius > max[d.color].radius)) { max[d.color] = d; } }); return function(d) { var node = max[d.color], l, r, x, y, i = -1; if (node == d) return; x = d.x - node.x; y = d.y - node.y; l = Math.sqrt(x * x + y * y); r = d.radius + node.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; node.x += x; node.y += y; } }; } // Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } };