| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| circle { | |
| stroke: #fff; | |
| } | |
| </style> | |
| <body> | |
| <script src="http://mbostock.github.com/d3/d3.js?2.7.4"></script> | |
| <script src="http://mbostock.github.com/d3/d3.geom.js?2.7.4"></script> | |
| <script src="http://mbostock.github.com/d3/d3.layout.js?2.7.4"></script> | |
| <script> | |
| var margin = {top: 0, right: 0, bottom: 0, left: 0}, | |
| width = 640 - margin.left - margin.right, | |
| height = 320 - margin.top - margin.bottom; | |
| var n = 10, | |
| m = 10, | |
| padding = 6, | |
| radius = d3.scale.sqrt().range([0, 50]), | |
| color = d3.scale.category10().domain(d3.range(m)); | |
| var nodes = d3.range(n).map(function() { | |
| var i = Math.floor(Math.random() * m), | |
| v = (i + 1) / m * -Math.log(Math.random()); | |
| return { | |
| radius: radius(v), | |
| color: color(i) | |
| }; | |
| }); | |
| var force = d3.layout.force() | |
| .nodes(nodes) | |
| .size([width, height]) | |
| .gravity(0) | |
| .charge(0) | |
| .on("tick", tick) | |
| .start(); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.top + margin.bottom) | |
| .append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
| var circle = svg.selectAll("circle") | |
| .data(nodes) | |
| .enter().append("circle") | |
| .attr("r", function(d) { return d.radius; }) | |
| .style("fill", function(d) { return d.color; }) | |
| .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, | |
| k = 1, | |
| i = -1; | |
| // For cluster nodes, apply custom gravity. | |
| if (node == d) { | |
| node = {x: width / 2, y: height / 2, radius: -d.radius}; | |
| k = .1 * Math.sqrt(d.radius); | |
| } | |
| 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 * k; | |
| 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 + radius.domain()[1] + 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; | |
| }); | |
| }; | |
| } | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment