Use d3.scaleBand and d3-force to group nodes in a force simulation. The number of columns is dependent upon the width of the browser window.
Last active
February 7, 2020 18:34
-
-
Save HarryStevens/6f001c74bff49ae083d2752af4e95dff to your computer and use it in GitHub Desktop.
Grouped Force
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 |
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> | |
<html> | |
<head> | |
<style> | |
body { | |
margin: 0; | |
} | |
</style> | |
</head> | |
<body> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script> | |
let firstDraw = true, simulation = null, tickCount = 0; | |
const groups = "abcdefghijkl".split("").map(letter => ({ | |
letter, | |
data: d3.range(0, randBetween(5, 10)).map((d, i) => ({ | |
id: `${letter}-${i}`, | |
value: randBetween(2, 20) | |
})) | |
})); | |
const flat = flatten(groups.map(d => d.data)); | |
const xAccessor = d => x(d.col) + x.bandwidth(); | |
const yAccessor = d => y(d.row) + y.bandwidth() / 2; | |
const x = d3.scaleBand(); | |
const y = d3.scaleBand(); | |
const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; | |
let width, height, cols; | |
const svg = d3.select("body").append("svg"); | |
const nodes = svg.selectAll("g") | |
.data(flat) | |
.enter().append("g"); | |
const circles = nodes.append("circle") | |
.attr("r", d => d.value); | |
draw(); | |
addEventListener("resize", draw); | |
function draw(){ | |
tickCount = 0; | |
if (simulation) simulation.stop(); | |
width = innerWidth; | |
height = innerHeight; | |
cols = width < 600 ? 3 : 6; | |
svg | |
.attr("width", width) | |
.attr("height", height); | |
groups.forEach((d, i) => { | |
d.row = Math.floor(i / cols); | |
d.col = i % cols; | |
d.data.forEach(d0 => { | |
d0.row = d.row; | |
d0.col = d.col; | |
}); | |
}); | |
x | |
.domain(d3.range(0, cols + 1)) | |
.range([0, width]); | |
y | |
.domain(d3.range(0, d3.max(flat, d => d.row) + 1)) | |
.range([0, height]); | |
circles | |
.style("fill", d => colors[d.col]); | |
simulation = d3.forceSimulation(flat) | |
.force("x", d3.forceX(firstDraw ? width / 2 : xAccessor)) | |
.force("y", d3.forceY(firstDraw ? height / 2 : yAccessor)) | |
.force("collide", d3.forceCollide(d => d.value + 1)) | |
.alphaTarget(.5) | |
.on("tick", tick); | |
// Initialize with 50 ticks | |
if (firstDraw){ | |
simulation.stop(); | |
for (let i = 0; i < 50; i++) simulation.tick(); | |
simulation.restart(); | |
firstDraw = false; | |
} | |
} | |
function tick(){ | |
tickCount++; | |
if (tickCount === 1){ | |
simulation | |
.force("x", d3.forceX(xAccessor)) | |
.force("y", d3.forceY(yAccessor)) | |
} | |
nodes | |
.attr("transform", d => `translate(${d.x}, ${d.y})`); | |
} | |
function flatten(arr){ | |
return [].concat.apply([], arr); | |
} | |
function randBetween(min, max){ | |
return Math.floor(Math.random() * (max - min + 1) + min); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment