swarm = (data, x, r, priority, symmetric = true) => {
let circles = data
.map((d, index) => ({
datum: d,
x: x(d),
y: Infinity,
r: r(d),
priority: priority(d),
index
}))
.sort((a, b) => d3.ascending(a.x, b.x));
let indices = d3
.range(0, circles.length)
.sort((a, b) => d3.ascending(circles[a].priority, circles[b].priority));
indices.forEach((index, order) => (circles[index].order = order));
for (let d of circles) {
if (d.x == undefined)
throw new Error('x is undefined for datum at index ' + d.index);
if (d.r == undefined)
throw new Error('r is undefined for datum at index ' + d.index);
if (d.priority == undefined)
throw new Error('priority is undefined for datum at index ' + d.index);
}
let { sqrt, abs, min } = Math;
let maxRadius = d3.max(circles, d => d.r);
for (let index of indices) {
let intervals = [];
let circle = circles[index];
for (let step of [-1, 1])
for (let i = index + step; i > -1 && i < circles.length; i += step) {
let other = circles[i];
let dist = abs(circle.x - other.x);
let radiusSum = circle.r + other.r;
if (dist > circle.r + maxRadius) break;
if (other.y === Infinity || dist > radiusSum) continue;
let offset = sqrt(radiusSum * radiusSum - dist * dist);
intervals.push([other.y - offset, other.y + offset]);
}
let y = 0;
if (intervals.length) {
let candidates = intervals
.flat()
.sort((a, b) => d3.ascending(abs(a), abs(b)));
for (let candidate of candidates) {
if (!symmetric && candidate > 0) continue;
if (intervals.every(([lo, hi]) => candidate <= lo || candidate >= hi)) {
y = candidate;
break;
}
}
}
circles[index].y = y;
}
return circles;
}