Last active
February 8, 2018 21:13
-
-
Save shancarter/f621ac5d93498aa1223d8d20e5d3a0f4 to your computer and use it in GitHub Desktop.
Clustered Force Layout 4.0
This file contains 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.v4.min.js"></script> | |
<script> | |
let margin = {top: 100, right: 100, bottom: 100, left: 100}; | |
let width = 960, | |
height = 500, | |
padding = 1.5, // separation between same-color circles | |
clusterPadding = 6, // separation between different-color circles | |
maxRadius = 12; | |
let n = 200, // total number of nodes | |
m = 10, // number of distinct clusters | |
z = d3.scaleOrdinal(d3.schemeCategory20), | |
clusters = new Array(m); | |
let svg = d3.select('body') | |
.append('svg') | |
.attr('height', height) | |
.attr('width', width) | |
.append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); | |
let nodes = d3.range(200).map(() => { | |
let i = Math.floor(Math.random() * m), | |
radius = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, | |
d = {cluster: i, r: radius}; | |
if (!clusters[i] || (radius > clusters[i].r)) clusters[i] = d; | |
return d; | |
}); | |
let circles = svg.append('g') | |
.datum(nodes) | |
.selectAll('.circle') | |
.data(d => d) | |
.enter().append('circle') | |
.attr('r', (d) => d.r) | |
.attr('fill', (d) => z(d.cluster)) | |
.attr('stroke', 'black') | |
.attr('stroke-width', 1); | |
let simulation = d3.forceSimulation(nodes) | |
.velocityDecay(0.2) | |
.force("x", d3.forceX().strength(.0005)) | |
.force("y", d3.forceY().strength(.0005)) | |
.force("collide", collide) | |
.force("cluster", clustering) | |
.on("tick", ticked); | |
function ticked() { | |
circles | |
.attr('cx', (d) => d.x) | |
.attr('cy', (d) => d.y); | |
} | |
// These are implementations of the custom forces. | |
function clustering(alpha) { | |
nodes.forEach(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.r + cluster.r; | |
if (l !== r) { | |
l = (l - r) / l * alpha; | |
d.x -= x *= l; | |
d.y -= y *= l; | |
cluster.x += x; | |
cluster.y += y; | |
} | |
}); | |
} | |
function collide(alpha) { | |
var quadtree = d3.quadtree() | |
.x((d) => d.x) | |
.y((d) => d.y) | |
.addAll(nodes); | |
nodes.forEach(function(d) { | |
var r = d.r + 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.data && (quad.data !== d)) { | |
var x = d.x - quad.data.x, | |
y = d.y - quad.data.y, | |
l = Math.sqrt(x * x + y * y), | |
r = d.r + quad.data.r + (d.cluster === quad.data.cluster ? padding : clusterPadding); | |
if (l < r) { | |
l = (l - r) / l * alpha; | |
d.x -= x *= l; | |
d.y -= y *= l; | |
quad.data.x += x; | |
quad.data.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