Public
Edited
Apr 28, 2024
5 forks
Importers
10 stars
Insert cell
Insert cell
Insert cell
Insert cell
timeline.element
Insert cell
oneYearAgo = new Date().setFullYear(new Date().getFullYear() - 1)
Insert cell
oneYearFromNow = new Date().setFullYear(new Date().getFullYear() + 1)
Insert cell
Insert cell
function dodge(X, radius) {
const Y = new Float64Array(X.length);
const radius2 = radius ** 2;
const epsilon = 1e-3;
let head = null,
tail = null;

// Returns true if circle ⟨x, y⟩ intersects with any circle in the queue.
const intersects = (x, y) => {
let a = head;
while (a) {
const ai = a.index;
if (radius2 - epsilon > (X[ai] - x) ** 2 + (Y[ai] - y) ** 2) return true;
a = a.next;
}
return false;
};

// Place each circle sequentially.
for (const bi of d3.range(X.length).sort((i, j) => X[i] - X[j])) {
while (head && X[head.index] < X[bi] - radius2) head = head.next; // Remove non-intersecting

// Start by attempting to place at y = 0, check for intersections.
Y[bi] = 0;
if (intersects(X[bi], Y[bi])) {
let a = head;
Y[bi] = Infinity; // Start with a high Y value which will be minimized.

do {
const ai = a.index;
const distX = X[ai] - X[bi];
const distToEdge = Math.sqrt(radius2 - distX * distX);

// Check above each circle
let y1 = Y[ai] + distToEdge;
if (Math.abs(y1) < Math.abs(Y[bi]) && !intersects(X[bi], y1))
Y[bi] = y1;

// Check below each circle
let y2 = Y[ai] - distToEdge;
if (Math.abs(y2) < Math.abs(Y[bi]) && !intersects(X[bi], y2))
Y[bi] = y2;

a = a.next;
} while (a);
}

// Add b to the queue.
const b = { index: bi, next: null };
if (head === null) head = tail = b;
else tail = tail.next = b;
}

return Y;
}
Insert cell
function beeswarm(
data,
{ gap = 1, ticks = 50, dynamic = false, direction, ...options }
) {
const dots = Plot.dot(data, options);
const { render } = dots;

dots.render = function () {
const g = render.apply(this, arguments);
const circles = d3.select(g).selectAll("circle");

const nodes = [];
const [cx, cy, x, y, forceX, forceY] =
direction === "x"
? ["cx", "cy", "x", "y", d3.forceX, d3.forceY]
: ["cy", "cx", "y", "x", d3.forceY, d3.forceX];
for (const c of circles) {
const node = {
x: +c.getAttribute(cx),
y: +c.getAttribute(cy),
r: +c.getAttribute("r")
};
nodes.push(node);
}
const force = d3
.forceSimulation(nodes)
.force("x", forceX((d) => d[x]).strength(0.8))
.force("y", forceY((d) => d[y]).strength(0.05))
.force(
"collide",
d3
.forceCollide()
.radius((d) => d.r + gap)
.iterations(3)
)
.tick(ticks)
.stop();
update();
if (dynamic) force.on("tick", update).restart();
return g;

function update() {
circles.attr(cx, (_, i) => nodes[i].x).attr(cy, (_, i) => nodes[i].y);
}
};

return dots;
}
Insert cell
Insert cell
myData = Array.from({ length: 500 }, (x, i) => ({
id: i,
start: new Date(
new Date().getTime() + Math.random() * 100000000000 - 50000000000
)
}))
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more