This variation of a clustered force layout uses D3’s circle-packing layout to initialize node positions.
-
-
Save mbostock/7882658 to your computer and use it in GitHub Desktop.
Cluster Force Layout IV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
redirect: https://beta.observablehq.com/@mbostock/clustered-bubbles-2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<body> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script> | |
var width = 960, | |
height = 500, | |
padding = 1.5, // separation between same-color nodes | |
clusterPadding = 6, // separation between different-color nodes | |
maxRadius = 12; | |
var n = 200, // total number of nodes | |
m = 10; // number of distinct clusters | |
var color = d3.scale.category10() | |
.domain(d3.range(m)); | |
// The largest node for each cluster. | |
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(.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(.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) { | |
return function(d) { | |
var cluster = clusters[d.cluster]; | |
if (cluster === d) return; | |
var x = d.x - cluster.x, | |
y = d.y - cluster.y, | |
l = Math.sqrt(x * x + y * y), | |
r = d.radius + cluster.radius; | |
if (l != r) { | |
l = (l - r) / l * alpha; | |
d.x -= x *= l; | |
d.y -= y *= l; | |
cluster.x += x; | |
cluster.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 + maxRadius + Math.max(padding, clusterPadding), | |
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.cluster === quad.point.cluster ? padding : clusterPadding); | |
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
That's a very interesting way for a variation. But doing this , you can't choose the number of nodes for each cluster, can you ?