diff --git a/src/marks/delaunay.js b/src/marks/delaunay.js index 16d4d85287..a373929de2 100644 --- a/src/marks/delaunay.js +++ b/src/marks/delaunay.js @@ -1,8 +1,8 @@ import {create, group, path, select, Delaunay} from "d3"; import {Curve} from "../curve.js"; -import {maybeTuple, maybeZ} from "../options.js"; +import {constant, maybeTuple, maybeZ} from "../options.js"; import {Mark} from "../plot.js"; -import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; +import {applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, offset} from "../style.js"; import {markers, applyMarkers} from "./marker.js"; const delaunayLinkDefaults = { @@ -47,8 +47,8 @@ class DelaunayLink extends Mark { super( data, [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y"}, + {name: "x", value: x, scale: "x", optional: true}, + {name: "y", value: y, scale: "y", optional: true}, {name: "z", value: z, optional: true} ], options, @@ -60,6 +60,9 @@ class DelaunayLink extends Mark { render(index, {x, y}, channels, dimensions) { const {x: X, y: Y, z: Z} = channels; const {dx, dy, curve} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? i => X[i] : constant(cx); + const yi = Y ? i => Y[i] : constant(cy); const mark = this; function links(index) { @@ -76,14 +79,14 @@ class DelaunayLink extends Mark { ti = index[ti]; tj = index[tj]; newIndex.push(++i); - X1[i] = X[ti]; - Y1[i] = Y[ti]; - X2[i] = X[tj]; - Y2[i] = Y[tj]; + X1[i] = xi(ti); + Y1[i] = yi(ti); + X2[i] = xi(tj); + Y2[i] = yi(tj); for (const k in channels) newChannels[k].push(channels[k][tj]); } - const {halfedges, hull, triangles} = Delaunay.from(index, i => X[i], i => Y[i]); + const {halfedges, hull, triangles} = Delaunay.from(index, xi, yi); for (let i = 0; i < halfedges.length; ++i) { // inner edges const j = halfedges[i]; if (j > i) link(triangles[i], triangles[j]); @@ -126,33 +129,37 @@ class AbstractDelaunayMark extends Mark { super( data, [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y"}, + {name: "x", value: x, scale: "x", optional: true}, + {name: "y", value: y, scale: "y", optional: true}, {name: "z", value: zof(options), optional: true} ], options, defaults ); } - render(index, {x, y}, {x: X, y: Y, z: Z, ...channels}, dimensions) { + render(index, {x, y}, channels, dimensions) { + const {x: X, y: Y, z: Z} = channels; const {dx, dy} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? i => X[i] : constant(cx); + const yi = Y ? i => Y[i] : constant(cy); const mark = this; - function mesh(render) { - return function(index) { - const delaunay = Delaunay.from(index, i => X[i], i => Y[i]); - select(this).append("path") - .datum(index[0]) - .call(applyDirectStyles, mark) - .attr("d", render(delaunay, dimensions)) - .call(applyChannelStyles, mark, channels); - }; + + function mesh(index) { + const delaunay = Delaunay.from(index, xi, yi); + select(this).append("path") + .datum(index[0]) + .call(applyDirectStyles, mark) + .attr("d", mark._render(delaunay, dimensions)) + .call(applyChannelStyles, mark, channels); } + return create("svg:g") .call(applyIndirectStyles, this, dimensions) .call(applyTransform, x, y, offset + dx, offset + dy) .call(Z - ? g => g.selectAll().data(group(index, i => Z[i]).values()).enter().append("g").each(mesh(this._render)) - : g => g.datum(index).each(mesh(this._render))) + ? g => g.selectAll().data(group(index, i => Z[i]).values()).enter().append("g").each(mesh) + : g => g.datum(index).each(mesh)) .node(); } } @@ -182,8 +189,8 @@ class Voronoi extends Mark { super( data, [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y"}, + {name: "x", value: x, scale: "x", optional: true}, + {name: "y", value: y, scale: "y", optional: true}, {name: "z", value: z, optional: true} ], options, @@ -193,9 +200,12 @@ class Voronoi extends Mark { render(index, {x, y}, channels, dimensions) { const {x: X, y: Y, z: Z} = channels; const {dx, dy} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? i => X[i] : constant(cx); + const yi = Y ? i => Y[i] : constant(cy); function cells(index) { - const delaunay = Delaunay.from(index, i => X[i], i => Y[i]); + const delaunay = Delaunay.from(index, xi, yi); const voronoi = voronoiof(delaunay, dimensions); select(this) .selectAll() diff --git a/test/output/penguinVoronoi1D.svg b/test/output/penguinVoronoi1D.svg new file mode 100644 index 0000000000..09eeac6353 --- /dev/null +++ b/test/output/penguinVoronoi1D.svg @@ -0,0 +1,1756 @@ + + + + + 3,000 + + + 3,500 + + + 4,000 + + + 4,500 + + + 5,000 + + + 5,500 + + + 6,000 + body_mass_g → + + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (null) + Torgersen + + + Adelie (null) + Torgersen + + + Adelie (null) + Torgersen + + + Adelie (null) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (null) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Biscoe + + + Adelie (MALE) + Biscoe + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Torgersen + + + Adelie (MALE) + Torgersen + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Adelie (FEMALE) + Dream + + + Adelie (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (MALE) + Dream + + + Chinstrap (FEMALE) + Dream + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (null) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (null) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (null) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (null) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + Gentoo (FEMALE) + Biscoe + + + Gentoo (MALE) + Biscoe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index c0665b8749..bc3916270e 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -126,6 +126,7 @@ export {default as penguinCulmenDelaunay} from "./penguin-culmen-delaunay.js"; export {default as penguinCulmenDelaunayMesh} from "./penguin-culmen-delaunay-mesh.js"; export {default as penguinCulmenDelaunaySpecies} from "./penguin-culmen-delaunay-species.js"; export {default as penguinCulmenVoronoi} from "./penguin-culmen-voronoi.js"; +export {default as penguinVoronoi1D} from "./penguin-voronoi-1d.js"; export {default as penguinDodge} from "./penguin-dodge.js"; export {default as penguinDodgeHexbin} from "./penguin-dodge-hexbin.js"; export {default as penguinFacetDodge} from "./penguin-facet-dodge.js"; diff --git a/test/plots/penguin-voronoi-1d.js b/test/plots/penguin-voronoi-1d.js new file mode 100644 index 0000000000..8ee850439f --- /dev/null +++ b/test/plots/penguin-voronoi-1d.js @@ -0,0 +1,27 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + inset: 10, + marks: [ + Plot.voronoi(penguins, { + x: "body_mass_g", + fill: "species", + fillOpacity: 0.5, + title: d => `${d.species} (${d.sex})\n${d.island}` + }), + Plot.dot(penguins, { + x: "body_mass_g", + fill: "currentColor", + r: 1.5, + pointerEvents: "none" + }), + Plot.voronoiMesh(penguins, { + x: "body_mass_g", + pointerEvents: "none" + }) + ] + }); +}