Published unlisted
Edited
Jun 1, 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])
})
)
],
color: { legend: true }
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.link(
data,
delaunay({
x: "sepalLength",
y: "sepalWidth",
z: "species",
stroke: (d) => d.source.species,
strokeWidth: "hull", // TODO?? 🌶
filter: "hull" // TODO?? 🌶🌶
})
)
],
color: { legend: true }
})
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",
// z: "species", // not necessary when faceting by species…
stroke: (d) => d.source.species
})
),
Plot.dot(data, {
x: "sepalLength",
y: "sepalWidth",
fill: "species"
})
],
color: { legend: true }
})
Insert cell
Insert cell
Insert cell
Plot.plot({
width: 420,
inset: 40,
marks: [
Plot.line(
data,
voronoi({
x: "sepalLength",
y: "sepalWidth",
fill: "species",
stroke: "white",
curve: `${curve}-closed`
})
),
Plot.dot(data, {
x: "sepalLength",
y: "sepalWidth",
fill: "white",
r: 2
})
],
color: { legend: true }
})
Insert cell
delaunay = ({ x, y, stroke: tstroke, z, options } = {}) => {
if (tstroke === "hull") tstroke = (d) => d.hull;
const S = tstroke || z ? [] : null;
const stroke = typeof tstroke === "function" ? undefined : tstroke;

return Plot.initializer({x, y, stroke, z, ...options}, (
data,
facets,
{ x1: X, y1: Y, stroke: ZS },
scales,
dimensions
) => {
if (X.scale == null) X = X.value;
else X = X.value.map(scales[X.scale]);
if (Y.scale == null) Y = Y.value;
else Y = Y.value.map(scales[Y.scale]);

const Z = z ? Plot.valueof(data, z) : ZS ? ZS : {};

const X1 = [];
const X2 = [];
const Y1 = [];
const Y2 = [];

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

// 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);
X1.push(X[I[ti]]);
Y1.push(Y[I[ti]]);
X2.push(X[I[tj]]);
Y2.push(Y[I[tj]]);
newData.push({
source: data[I[ti]],
target: data[I[tj]],
index: k,
hull: false
});
}

// 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);
X1.push(X[I[ti]]);
Y1.push(Y[I[ti]]);
X2.push(X[I[tj]]);
Y2.push(Y[I[tj]]);
newData.push({
source: data[I[ti]],
target: data[I[tj]],
index: k,
hull: true
});
}
newFacets.push(f);
}
}
return {
data: newData,
facets: newFacets,
channels: {
x1: { value: X1 },
x2: { value: X2 },
y1: { value: Y1 },
y2: { value: Y2 },
...(S && {
stroke: { value: Plot.valueof(newData, tstroke), scale: "color" }
})
}
};
})
}
Insert cell
voronoi = ({ x, y, stroke: tstroke, fill: tfill, z, ...options } = {}) => {
// todo: not sure about this mess
const S = (tstroke && isNaN(d3.rgb(tstroke).r)) || z ? [] : null;
const stroke = S ? undefined : tstroke;
const F = (tfill && isNaN(d3.rgb(tfill).r)) || z ? [] : null;
const fill = F ? undefined : tfill;

return Plot.initializer(
{ x, y, stroke, fill, z, ...options },
(
data,
facets,
{ x: X, y: Y, stroke: ZS, fill: ZF },
scales,
dimensions
) => {
if (X.scale == null) X = X.value;
else X = X.value.map(scales[X.scale]);
if (Y.scale == null) Y = Y.value;
else Y = Y.value.map(scales[Y.scale]);

const Z = z ? Plot.valueof(data, z) : ZS ? ZS : ZF ? ZF : {};

const P = []; // index for polygons
const X0 = [];
const Y0 = [];

let k = -1;
const newFacets = [];
const newData = [];
for (const facet of facets) {
const f = [];
for (const [z, I] of d3.group(facet, (i) => Z[i])) {
const voronoi = d3.Delaunay.from(
I,
(i) => X[i],
(i) => Y[i]
).voronoi([
dimensions.marginLeft,
dimensions.marginTop,
dimensions.width - dimensions.marginRight,
dimensions.height - dimensions.marginBottom
]);

for (const c of voronoi.cellPolygons()) {
for (const p of c.slice(1)) {
f.push(++k);
X0.push(p[0]);
Y0.push(p[1]);
P.push(c.index);
newData.push(data[c.index]);
}
}
newFacets.push(f);
}
}
return {
data: newData,
facets: newFacets,
channels: {
x: { value: X0 },
y: { value: Y0 },
z: { value: P }, // split by Polygon
...(S && {
stroke: { value: Plot.valueof(newData, tstroke), scale: "color" }
}),
...(F && {
fill: { value: Plot.valueof(newData, tfill), scale: "color" }
})
}
};
}
);
}
Insert cell
// branch fil/reinitialize-all; https://github.com/observablehq/plot/pull/823
// commit bddf2749ad81d45bda475cd2d1c221c00dd24178
//Plot = FileAttachment("plot.umd.js").url().then(require)
Insert cell
// TODO: remove after Plot 0.5 is released
import {Plot} from "@observablehq/temp-blot-build-801"
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