Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active February 7, 2020 18:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HarryStevens/6f001c74bff49ae083d2752af4e95dff to your computer and use it in GitHub Desktop.
Save HarryStevens/6f001c74bff49ae083d2752af4e95dff to your computer and use it in GitHub Desktop.
Grouped Force
license: gpl-3.0

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.

<!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