Published
Edited
Nov 25, 2021
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const ctx = DOM.context2d(w, h, 1);
Object.assign(ctx.canvas.style, {width: w * (2 ** zoom) + 'px', imageRendering: 'pixelated'});
ctx.putImageData(initialImgData, 0, 0); if (animate) yield ctx.canvas;

// treat pixel colors as 32-bit integers for easier manipulation
const imgData = ctx.getImageData(0, 0, w, h);
const data = new Uint32Array(imgData.data.buffer);
// initialize a grid that keeps track of the closest data point for each pixel
const closest = new Uint32Array(w * h).fill(65535);
// init data points as closest to themselves
for (let i = 0; i < closest.length; i++) if (data[i]) closest[i] = i;

let numUpdates = 0;
// starting flood step is the half of the grid size's closest power of two
const maxStep = 2 ** (Math.ceil(Math.log2(Math.max(w, h))) - 1);

if (preflood) yield* pass(1); // an optional 1-step pass that increases precision
for (let step = maxStep; step >= 1; step >>= 1) {
yield* pass(step); // run passes at e.g. 512, 256, 128, ..., 1
}
ctx.putImageData(imgData, 0, 0); yield ctx.canvas;

function* pass(step) {
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
// propagate the closest point & corresponding color from 8 directions
const k = y * w + x;
let bestSeed = -1;
for (let i = -1, bestDist = Infinity; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const y0 = y + i * step;
const x0 = x + j * step;
const m = y0 * w + x0;
if (!data[m] || x0 < 0 || x0 >= w || y0 < 0 || y0 >= h) continue; // skip empty pixels
const seed = closest[m];
const d = (x - seed % w) ** 2 + (y - seed / w | 0) ** 2;
if (d < bestDist) { // found a closer point
bestDist = d;
bestSeed = seed;
}
}
}
if (bestSeed === -1) continue;
closest[k] = bestSeed;
data[k] = data[bestSeed];
const newFrame = animate && ++numUpdates % (viewof iterationsPerFrame).value === 0;
if (newFrame) { ctx.putImageData(imgData, 0, 0); yield ctx.canvas; }
}
}
}
}
Insert cell
Insert cell
initialImgData = { // create a canvas with a bunch of points
const ctx = DOM.context2d(w, h, 1);
for (let i = 0; i < numPoints; i++) {
ctx.fillStyle = '#' + (Math.random() * 0xFFFFFF | 0).toString(16); // random color
ctx.fillRect(Math.random() * w | 0, Math.random() * h | 0, 1, 1); // random position
}
return ctx.getImageData(0, 0, w, h);
}
Insert cell
w = width >> zoom
Insert cell
h = 400 >> zoom
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more