function beeswarm(
data,
{ radius = 1, x = (d) => d, side = 1, sort = true } = {}
) {
const circles = data
.map((d, i, data) => ({
x: +x(d, i, data) / radius,
data: d,
y: 0,
priority: i
}))
.sort((a, b) => a.x - b.x);
let n = data.length;
let order = [];
for (let i = 0; i < n; i++) {
if (sort) circles[i].priority = i;
order[circles[i].priority] = i;
}
for (let iter = 0; iter < n; iter++) {
let i = order[iter];
let nearby_circles = [];
let start = i;
while (start > 0 && circles[start - 1].x > circles[i].x - 1) --start;
for (let j = start; j < n; j++) {
if (circles[j].priority >= iter) continue;
if (circles[j].x >= circles[i].x + 1) break;
let x_diff = circles[i].x - circles[j].x;
let dx_sq = x_diff * x_diff;
nearby_circles.push({
y: circles[j].y,
priority: circles[j].priority,
forbiddenYBottom: circles[j].y - Math.sqrt(1 - dx_sq),
forbiddenYTop: circles[j].y + Math.sqrt(1 - dx_sq)
});
}
nearby_circles.sort((a, b) => a.y - b.y);
if (is_y_feasible(0, nearby_circles, 0, nearby_circles.length)) {
circles[i].y = 0;
} else {
let yAbs = Infinity;
let sides = side === 0 ? [1, -1] : side === 1 ? [1] : [-1];
for (let s of sides) {
let p = s === 1 ? n : -1;
for (let j = 0; j < nearby_circles.length; j++) {
let poty =
s == -1
? nearby_circles[j].forbiddenYBottom
: nearby_circles[j].forbiddenYTop;
let potyAbs = Math.abs(poty);
let is_improvement =
potyAbs <= yAbs &&
(potyAbs < yAbs || nearby_circles[j].priority < p);
if (is_improvement && can_use_poty(j, poty, nearby_circles)) {
circles[i].y = poty;
yAbs = Math.abs(poty);
p = nearby_circles[j].priority;
}
}
}
}
}
var result = circles.map(({ x, y, data }) => ({
x: x * radius,
y: y * radius,
data
}));
return result;
}