forked from shancarter's block: Clustered Force Layout 4.0
Created
October 11, 2016 04:58
-
-
Save lorenzopub/1dca7b205cfe5b9733afe831c7719a9d 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
license: mit |
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