Published
Edited
Mar 28, 2022
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const context = DOM.context2d(width, height);
const { canvas } = context;
const r = 1 + Math.sqrt(2000 / n);

canvas.update = (nn0) => {
context.clearRect(0, 0, width, height);
context.beginPath();
for (let i = 0; i < n; i++) {
context.moveTo(X[i] + r, Y[i]);
context.arc(X[i], Y[i], r, 0, tau);
}
context.fillStyle = "black";
context.fill();

if (nn0) {
context.beginPath();
for (const i of nn0) {
context.moveTo(X[i] + r, Y[i]);
context.arc(X[i], Y[i], r, 0, tau);
}
context.fillStyle = "brown";
context.fill();
}

context.beginPath();
{
const i = 0;
context.moveTo(X[i] + r, Y[i]);
context.arc(X[i], Y[i], r, 0, tau);
}
context.fillStyle = "red";
context.fill();
};

return canvas;
}
Insert cell
vrandom = d3.randomNormal(0, 20)
Insert cell
X = Float32Array.from({ length: n }, () => width * Math.random())
Insert cell
Y = Float32Array.from({ length: n }, () => height * Math.random())
Insert cell
VX = Float32Array.from({ length: n }, vrandom)
Insert cell
VY = Float32Array.from({ length: n }, vrandom)
Insert cell
arandom = d3.randomNormal(0, noise)
Insert cell
{
const AX = new Float32Array(n);
const AY = new Float32Array(n);
do {
const t0 = performance.now();

const k = Math.min(k0, n - 1);
const nn = knn(k);

for (let i = 0; i < n; i++) {
AX[i] = 0;
AY[i] = 0;
for (const j of nn(X[i], Y[i])) {
AX[i] += VX[j];
AY[i] += VY[j];
}
AX[i] = VX[i] * 0.2 + arandom() + AX[i] / k;
AY[i] = VY[i] * 0.2 + arandom() + AY[i] / k;
}
for (let i = 0; i < n; i++) {
X[i] = wrap(X[i] + VX[i], 0, width);
Y[i] = wrap(Y[i] + VY[i], 0, height);
}
for (let i = 0; i < n; i++) {
const h = 1 / Math.hypot(AX[i], AY[i]);
VX[i] = AX[i] * h;
VY[i] = AY[i] * h;
}
chart.update(nn(X[0], Y[0]));
yield Math.round(performance.now() - t0); // should be kept < 15ms
} while (true);
}
Insert cell
// todo: same with d3-quadtree — https://github.com/d3/d3-quadtree/issues/42
knn = {
let u, v;
return (k) => {
// torus knn!
const index = new Flatbush(n * 9);
for (let i = 0; i < n; i++) {
for (const a of [-1, 0, 1])
for (const b of [-1, 0, 1])
index.add((u = X[i] + a * width), (v = Y[i] + b * height), u, v);
}
index.finish();
return (x, y) => index.neighbors(x, y, k).map((i) => Math.floor(i / 9));
};
}
Insert cell
// using Flatbush for knn (thank you, @mourner!)
Flatbush = require("flatbush@3")
Insert cell
height = 500
Insert cell
clamp = (x, lo, hi) => (x < lo ? lo : x > hi ? hi : x)
Insert cell
// position on the torus
wrap = (x, lo, hi) => (x < lo ? x + (hi - lo) : x > hi ? x - (hi - lo) : x)
Insert cell
tau = 2 * Math.PI
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