Published
Edited
Oct 28, 2020
26 stars
Also listed in…
Voronoi
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function chart() {
const context = DOM.context2d(width, height);

const hoverDistance = 25; // pixels

function draw(highlight, pointer, found) {
context.clearRect(0, 0, width, height);

const { delaunay } = context.canvas;
if (debug && delaunay) {
context.beginPath();
delaunay.voronoi([0, 0, width, height]).render(context);
context.strokeStyle = "lightblue";
context.lineWidth = 0.5;
context.stroke();

/*context.beginPath();
delaunay.renderPoints(context, 1);
context.fillStyle = "#888";
context.fill();
*/
}

let i = 0;
for (const p of paths) {
context.beginPath();
context.lineWidth = i === highlight ? 4 : 0.7;
context.strokeStyle = color(i++);
context.stroke(new Path2D(p));
}

if (pointer) {
context.beginPath();
context.arc(...pointer, hoverDistance, 0, 6.4);
if (found) context.moveTo(...found), context.arc(...found, 3, 0, 6.4);

context.lineWidth = 2.5;
context.strokeStyle = "#aaa";
context.stroke();
}
}
draw();

d3.select(context.canvas).on("pointermove", event => {
const parent = context.canvas;
const timer = performance.now();
if (!parent.delaunay) {
const points = paths.map(path => {
const p = useSvgPathProperties
? new svgPathProperties(path)
: document.createElementNS("http://www.w3.org/2000/svg", "path");
p.setAttribute && p.setAttribute("d", path);

const l = p.getTotalLength();
const sample = Math.ceil(l / sample_pixels);
return !sample
? []
: d3.range(sample + 1).map(i => p.getPointAtLength((i / sample) * l));
});
parent.delaunay = d3.Delaunay.from(points.flat(), d => d.x, d => d.y);
parent.delaunay.sizes = d3.cumsum(points.map(p => p.length));
mutable init_time = performance.now() - timer;
}
const pointer = d3.pointer(event);
const p_i = parent.delaunay.find(...pointer);
const found = [
parent.delaunay.points[2 * p_i],
parent.delaunay.points[2 * p_i + 1]
];
const delta = Math.hypot(pointer[0] - found[0], pointer[1] - found[1]);
if (delta > hoverDistance) {
draw(null, pointer);
} else {
const i = d3.bisect(parent.delaunay.sizes, p_i);
draw(i, pointer, found);
}
});

return context.canvas;
}
Insert cell
function randompath() {
let x = width / 2 + random() * 5,
y = height / 2 + random() * 5;
let dx = 0,
dy = 0;
return d3.line().curve(d3.curveCardinal)(
Array.from({ length: 30 * Math.random() }, () => [
(x += random() + ((dx += random() / 4), (dx *= 0.8))),
(y += random() + ((dy += random() / 4), (dy *= 0.8)))
])
);
}
Insert cell
random = d3.randomNormal(0, 13)
Insert cell
paths = Array.from({ length: 50 }, randompath)
Insert cell
// Using this library is faster than creating the paths as svg elements
svgPathProperties = (await require("svg-path-properties@^1.0.9"))
.svgPathProperties
Insert cell
mutable init_time = undefined
Insert cell
height = 600
Insert cell
d3 = require("d3@6")
Insert cell
color = d3.scaleOrdinal(d3.schemeCategory10)
Insert cell
import { checkbox, select } from "@jashkenas/inputs"
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