Published unlisted
Edited
Mar 28, 2022
Insert cell
Insert cell
Insert cell
data = require("@observablehq/iris")
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.link(
data,
delaunay({
x: "sepalLength",
y: "sepalWidth",
stroke: "hull"
})
)
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.link(
data,
delaunay({
x: "sepalLength",
y: "sepalWidth",
stroke: (d) => d3.mean([d.source.sepalLength, d.target.sepalLength])
})
)
]
})
Insert cell
Insert cell
Plot.plot({
facet: { data, x: "species" },
inset: 10,
height: 250,
x: { ticks: 5, nice: true },
marks: [
Plot.frame(),
Plot.link(
data,
delaunay({
x: "sepalLength",
y: "sepalWidth"
})
),
Plot.dot(data, {
x: "sepalLength",
y: "sepalWidth",
fill: "species"
})
],
color: { legend: true }
})
Insert cell
delaunay = ({ x, y, ...options }) => {
const [X1, setX1] = Plot.channel(x);
const [X2, setX2] = Plot.channel(x);
const [Y1, setY1] = Plot.channel(y);
const [Y2, setY2] = Plot.channel(y);
options = Plot.transform(options, function (data, facets) {
const X = Plot.valueof(data, x);
const Y = Plot.valueof(data, y);

// cheap way of inferring the magnitude
const normX = (([a, b]) => 1 / Math.max(1e-24, b - a))(d3.extent(X));
const normY = (([a, b]) => 1 / Math.max(1e-24, b - a))(d3.extent(Y));

const X1 = [];
const X2 = [];
const Y1 = [];
const Y2 = [];
setX1(X1);
setX2(X2);
setY1(Y1);
setY2(Y2);

let k = -1;
const newFacets = [];
const newData = [];
for (const I of facets) {
const f = [];
const delaunay = d3.Delaunay.from(
I,
(i) => X[i] * normX,
(i) => Y[i] * normY
);

// inner edges
for (let i = 0; i < delaunay.halfedges.length; ++i) {
const j = delaunay.halfedges[i];
if (j < i) continue;
const ti = delaunay.triangles[i];
const tj = delaunay.triangles[j];
f.push(++k);
newData.push({
source: data[I[ti]],
target: data[I[tj]],
index: k,
hull: false
});
X1.push(X[I[ti]]);
Y1.push(Y[I[ti]]);
X2.push(X[I[tj]]);
Y2.push(Y[I[tj]]);
}

// hull
for (let i = 0; i < delaunay.hull.length; ++i) {
const ti = delaunay.hull[i];
const tj = delaunay.hull[(i + 1) % delaunay.hull.length];
f.push(++k);
newData.push({
source: data[I[ti]],
target: data[I[tj]],
index: k,
hull: true
});
X1.push(X[I[ti]]);
Y1.push(Y[I[ti]]);
X2.push(X[I[tj]]);
Y2.push(Y[I[tj]]);
}
newFacets.push(f);
}
return { data: newData, facets: newFacets };
});
return { x1: X1, x2: X2, y1: Y1, y2: Y2, ...options };
}
Insert cell
Insert cell
Insert cell
Plot.plot({
width: 420,
marks: [
Plot.line(
data,
voronoi({
x: "sepalLength",
y: "sepalWidth",
z: "index",
fill: "species",
stroke: "white",
curve: `${curve}-closed`
})
),
Plot.dot(data, {
x: "sepalLength",
y: "sepalWidth",
fill: "white"
})
],
color: { legend: true }
})
Insert cell
// this definitely needs an initializer/layout (#801), since we want the frame's dimensions;
// in the meantime, we take an arbitrary 2% margin on each side
voronoi = ({ x, y, ...options }) => {
const [X, setX] = Plot.channel(x);
const [Y, setY] = Plot.channel(y);
options = Plot.transform(options, function (data, facets) {
const X = Plot.valueof(data, x);
const Y = Plot.valueof(data, y);

// cheap way of inferring the magnitude + extent
const eX = d3.extent(X);
const eY = d3.extent(Y);
const normX = (([a, b]) => 1 / Math.max(1e-24, b - a))(eX);
const normY = (([a, b]) => 1 / Math.max(1e-24, b - a))(eY);

const X0 = [];
const Y0 = [];
setX(X0);
setY(Y0);

let k = -1;
const newFacets = [];
const newData = [];
for (const I of facets) {
const f = [];
const voronoi = d3.Delaunay.from(
I,
(i) => X[i] * normX,
(i) => Y[i] * normY
).voronoi([
eX[0] * normX - 0.02,
eY[0] * normY - 0.02,
eX[1] * normX + 0.02,
eY[1] * normY + 0.02
]);

for (const c of voronoi.cellPolygons()) {
for (const p of c.slice(1)) {
f.push(++k);
newData.push({
...data[I[c.index]],
index: c.index
});
X0.push(p[0] / normX);
Y0.push(p[1] / normY);
}
}

newFacets.push(f);
}
return { data: newData, facets: newFacets };
});
return { x: X, y: Y, ...options };
}
Insert cell
import { Plot } from "@fil/plot-splom" // for Plot.transform and Plot.channel
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