Public
Edited
Apr 17, 2024
6 forks
54 stars
Insert cell
Insert cell
canvas()
.on("mousemove", (event, context) =>
draw(context, delaunay.find(...d3.pointer(event)))
)
.node()
Insert cell
Insert cell
radius = 50
Insert cell
canvas()
.on("mousemove", (event, context) => {
const [mx, my] = d3.pointer(event); // target coordinates
const p = delaunay.find(mx, my); // closest point
const d = Math.hypot(mx - x(data[p][xDim]), my - y(data[p][yDim]));
draw(context, d > radius ? undefined : p);
context.save();
context.beginPath();
context.arc(mx, my, radius, 0, 2 * Math.PI);
context.strokeStyle = "red";
context.stroke();
context.restore();
})
.node()
Insert cell
Insert cell
{
let activePath = null;
let start = 0;
return canvas()
.on("click", (event, context) => {
start = delaunay.find(...d3.pointer(event));
context.save();
context.beginPath();
context.arc(pts[2 * start], pts[2 * start + 1], 7, 0, 2 * Math.PI);
context.strokeStyle = "red";
context.stroke();
context.restore();
})
.on("mousemove", (event, context) => {
const m = d3.pointer(event); // target

// Copied from delaunay.find.
let path = [start], i = start, c;
while ((c = delaunay._step(i, ...m)) >= 0 && c !== i && c !== start) {
path.push(i = c);
}

path = activePath = drawPath(context, path);
(function animate() {
if (path !== activePath) return;
if (path.next().done) return;
requestAnimationFrame(animate);
})();
})
.node();
}
Insert cell
Insert cell
delaunay.find(201, 408) // search starts from point 0
Insert cell
i = delaunay.find(200, 400)
Insert cell
delaunay.find(201, 408, i) // start from point i, a bit faster since point i is close to [200,400]
Insert cell
Insert cell
Insert cell
delaunay = d3.Delaunay.from(data, d => x(d[xDim]), d => y(d[yDim]))
Insert cell
pts = delaunay.points
Insert cell
height = 500
Insert cell
xDim = "Calories"
Insert cell
yDim = "Protein"
Insert cell
x = d3.scaleLinear()
.domain(d3.extent(data, d => d[xDim]))
.range([10, width - 10])
Insert cell
y = d3.scaleSqrt()
.domain(d3.extent(data, d => d[yDim]))
.range([height - 10, 10])
Insert cell
data = d3.csvParse(await FileAttachment("foods.csv").text(), d3.autoType)
Insert cell
function canvas() {
const context = DOM.context2d(width, height);
return d3.select(context.canvas)
.datum(context)
.style("cursor", "crosshair")
.each(draw);
}
Insert cell
function draw(context, p) {
context.clearRect(0, 0, width, height);
context.save();
context.beginPath();
delaunay.render(context);
context.strokeStyle = "#ccc";
context.stroke();
context.restore();
context.save();
context.beginPath();
for (let i = 0; i < data.length; i++) {
const cx = x(data[i][xDim]);
const cy = y(data[i][yDim]);
context.moveTo(cx + 1.5, cy);
context.arc(cx, cy, 1.5, 0, 2 * Math.PI);
}
context.fill();
if (p > -1) {
context.beginPath();
const cx = x(data[p][xDim]);
const cy = y(data[p][yDim]);
context.moveTo(cx + 6, cy);
context.arc(cx, cy, 6, 0, 2 * Math.PI);
context.fillStyle = "red";
context.fill();
context.fillStyle = "black";
context.fillText(data[p].name, 10, 10);
}
context.restore();
}
Insert cell
function* drawPath(context, path) {
draw(context);
context.fillStyle = context.strokeStyle = "black";
context.fillText("Click to select the starting point", 10, 10);

let p = path[0];
context.save();
context.beginPath();
context.arc(pts[2 * p], pts[2 * p + 1], 2, 0, 2 * Math.PI);
context.fillStyle = "red";
context.fill();
context.restore();
yield p;

for (const next of path) {
context.save();
context.beginPath();
context.moveTo(pts[2 * p], pts[2 * p + 1]);
p = next;
context.lineTo(pts[2 * p], pts[2 * p + 1]);
context.lineWidth = 2;
context.strokeStyle = "red";
context.stroke();
context.restore();
yield p;
}
}
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