Published
Edited
Sep 15, 2021
1 fork
1 star
Insert cell
Insert cell
canvas = {
const context = DOM.context2d(width, height);

while (true) {
context.fillStyle = "#00000002";
context.fillRect(0, 0, width, height);

context.beginPath();
for (const cell of voronoi.cellPolygons()) {
const [x, y, r] = polygonIncircle(cell);
if (r < 1.5) continue;
context.moveTo(x + r - 1.5, y);
context.arc(x, y, r - 1.5, 0, 2 * Math.PI);
}
context.fillStyle = "#fff";
context.fill();

context.beginPath();
//voronoi.render(context);
//voronoi.renderBounds(context);
context.strokeStyle = "#bbb";
context.stroke();

context.beginPath();
//voronoi.delaunay.renderPoints(context);
context.fillStyle = "#000";
context.fill();
yield context.canvas;

for (let i = 0; i < n; ++i) {
const x = i << 1;
const y = x + 1;
positions[x] += velocities[x];
positions[y] += velocities[y];
if (positions[x] < -margin) positions[x] += width + margin * 2;
else if (positions[x] > width + margin) positions[x] -= width + margin * 2;
if (positions[y] < -margin) positions[y] += height + margin * 2;
else if (positions[y] > height + margin) positions[y] -= height + margin * 2;
velocities[x] += 0.2 * (Math.random() - 0.5) - 0.01 * velocities[x];
velocities[y] += 0.2 * (Math.random() - 0.5) - 0.01 * velocities[y];
}
voronoi.update();
}
}
Insert cell
positions = Float64Array.from({length: n * 2}, (_, i) => Math.random() * (i & 1 ? height : width))
Insert cell
velocities = new Float64Array(n * 2)
Insert cell
voronoi = new d3.Delaunay(positions).voronoi([0.5, 0.5, width - 0.5, height - 0.5])
Insert cell
function polygonIncircle(polygon) {
let circle = [NaN, NaN, 0];
for (let i = 0, n = polygon.length - 1; i < n; ++i) {
const pi0 = polygon[i], pi1 = polygon[i + 1];
for (let j = i + 1; j < n; ++j) {
const pj0 = polygon[j], pj1 = polygon[j + 1];
search: for (let k = j + 1; k < n; ++k) {
const pk0 = polygon[k], pk1 = polygon[k + 1];
const c = circleTangent(pi0, pi1, pj0, pj1, pk0, pk1);
if (!(c[2] > circle[2])) continue;
for (let l = 0; l < n; ++l) {
if (l === i || l === j || l === k) continue;
const d = pointLineDistance(c, polygon[l], polygon[l + 1]);
if (d + 1e-6 < c[2]) continue search;
}
circle = c;
}
}
}
return circle;
}
Insert cell
function circleTangent(p0, p1, p2, p3, p4, p5) {
const b0 = lineLineBisect(p0, p1, p3, p2);
const b1 = lineLineBisect(p2, p3, p5, p4);
const i = lineLineIntersect(...b0, ...b1);
return [...i, pointLineDistance(i, p0, p1)];
}
Insert cell
function pointLineDistance([x0, y0], [x2, y2], [x1, y1]) {
const x21 = x2 - x1, y21 = y2 - y1;
return (y21 * x0 - x21 * y0 + x2 * y1 - y2 * x1) / Math.sqrt(y21 * y21 + x21 * x21);
}
Insert cell
function lineLineBisect([x0, y0], [x1, y1], [x2, y2], [x3, y3]) {
const x02 = x0 - x2, y02 = y0 - y2;
const x10 = x1 - x0, y10 = y1 - y0, l10 = Math.sqrt(x10 ** 2 + y10 ** 2);
const x32 = x3 - x2, y32 = y3 - y2, l32 = Math.sqrt(x32 ** 2 + y32 ** 2);
const ti = (x32 * y02 - y32 * x02) / (y32 * x10 - x32 * y10);
const xi = x0 + ti * x10, yi = y0 + ti * y10;
return [[xi, yi], [xi + x10 / l10 + x32 / l32, yi + y10 / l10 + y32 / l32]];
}
Insert cell
function lineLineIntersect([x0, y0], [x1, y1], [x2, y2], [x3, y3]) {
const x02 = x0 - x2, y02 = y0 - y2;
const x10 = x1 - x0, y10 = y1 - y0;
const x32 = x3 - x2, y32 = y3 - y2;
const t = (x32 * y02 - y32 * x02) / (y32 * x10 - x32 * y10);
return [x0 + t * x10, y0 + t * y10];
}
Insert cell
height = 600
Insert cell
margin = 60
Insert cell
n = 100
Insert cell
d3 = require("d3-delaunay@5")
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