function chart() {
const context = DOM.context2d(width, height);
const hoverDistance = 25;
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();
}
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;
}