Published unlisted
Edited
Jun 6, 2021
Insert cell
Insert cell
mutable found = undefined
Insert cell
Plot.plot({
marks: [
Plot.ruleX([0]),
Plot.dot(
data,
{
x: "bill_length",
y: "flipper_length",
fill: "sex",
stroke: "currentColor",
strokeWidth: 0.5
}
),
new Voronoi(
data,
{
x: "bill_length",
y: "flipper_length",
fill: "sex"
}
)
],
width,
height: 640
})
Insert cell
import {data} from "@observablehq/plot-exploration-penguins"
Insert cell
mutable debugI = undefined
Insert cell
mutable debugR = undefined
Insert cell
mutable debug = undefined
Insert cell
class Voronoi extends Plot.Mark {
constructor(
data,
{
x = first,
y = second,
z,
r,
transform,
...style
} = {}
) {
super(
data,
[
{name: "x", value: x, scale: "x"},
{name: "y", value: y, scale: "y"},
{name: "z", value: z, optional: true},
],
transform
);
Plot.Style && Plot.Style(this) // todo in code (Style is not exposed)
}
initialize (data) {
if (data !== undefined) data = this.transform(data);
this.data = data; // for mousemove
return {
index: data === undefined ? undefined : Float32Array.from(data, indexOf),
channels: this.channels.map(channel => {
const {name} = channel;
return [name == null ? undefined : name + "", Channel(data, channel)];
})
};
}

render (I,
{ x, y },
{ x: X, y: Y },
{ width, height, marginLeft, marginTop, marginRight, marginBottom }
) {
mutable debugR = arguments;

const g = create("svg:g");
const v = d3.Delaunay.from(I, i => x(X[i]), i => y(Y[i]))
.voronoi([marginLeft, marginTop, width - marginRight, height - marginBottom]);
const paths = g.selectAll("path")
.data(I)
.join("path")
.attr("d", i => v.renderCell(i))
.style("fill", "none")
.style("stroke", "red");
// ALTERNATIVELY
g.append("rect")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginRight - marginLeft)
.attr("height", height - marginBottom-marginTop)
.style("fill", "none")
.style("pointer-events", "all")
.on("pointermove", (event) => {
const p = d3.pointers(event)[0],
i = v.delaunay.find(...p),
cx = x(X[i]),
cy = y(Y[i]),
dist = Math.hypot(cx - p[0], cy - p[1]);
if (dist < 20) {
mutable found = this.data[i];
finder.attr("r", 8)
.attr("cx", cx)
.attr("cy", cy);
} else {
mutable found = undefined;
finder.attr("r", 0);
}
});

const finder = g.append("circle").attr("fill", "none").attr("stroke", "black").attr("stroke-width", 2);

return g.node();
}
}
Insert cell
d3 = require("d3@6")
Insert cell
Plot = require(await FileAttachment("plot.umd.js").url())
Insert cell
create = d3.create
Insert cell
first = d => d[0]
Insert cell
second = d => d[1]
Insert cell
indexOf = (d, i) => i
Insert cell
Channel = {
const field = value => d => d[value];
const identity = d => d;
const zero = () => 0;
const string = x => x == null ? undefined : x + "";
const number = x => x == null ? undefined : +x;
const first = d => d[0];
const second = d => d[1];
// TODO Type coercion?
return function Channel(data, {scale, type, value, label}) {
if (typeof value === "string") label = value, value = Array.from(data, field(value));
else if (typeof value === "function") value = Array.from(data, value);
else if (typeof value.length !== "number") value = Array.from(value);
return {scale, type, value, label};
}
}
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