Skip to content

Commit a576ab3

Browse files
Filmbostock
andauthored
Voronoi initializer (for pointer interactions) (#1623)
* selectively generate one voronoi cell with the pointer * voronoi initializer * defined * the voronoi cell depends on i and fi (e.g. facet: exclude) * transpose cells channel * prettier * undo comment edits * facet reindexation to be handled separately (#1648) * update test * voronoi mark + reindexed facets * Apply the transform option before any initializer * simpler! * more tests * update tests * missing import * remove maybeClip (bad merge operation) * only voronoi needs exclusiveFacets --------- Co-authored-by: Mike Bostock <[email protected]>
1 parent 6f0fe3f commit a576ab3

15 files changed

+8082
-839
lines changed

src/marks/delaunay.js

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import {group, pathRound as path, select, Delaunay} from "d3";
22
import {create} from "../context.js";
33
import {maybeCurve} from "../curve.js";
4+
import {defined} from "../defined.js";
45
import {Mark} from "../mark.js";
56
import {markers, applyMarkers} from "../marker.js";
67
import {constant, maybeTuple, maybeZ} from "../options.js";
7-
import {
8-
applyChannelStyles,
9-
applyDirectStyles,
10-
applyFrameAnchor,
11-
applyIndirectStyles,
12-
applyTransform
13-
} from "../style.js";
8+
import {applyPosition} from "../projection.js";
9+
import {applyFrameAnchor, applyTransform} from "../style.js";
10+
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles} from "../style.js";
11+
import {basic, initializer} from "../transforms/basic.js";
12+
import {exclusiveFacets} from "../transforms/exclusiveFacets.js";
13+
import {maybeGroup} from "../transforms/group.js";
1414

1515
const delaunayLinkDefaults = {
1616
ariaLabel: "delaunay link",
@@ -221,45 +221,45 @@ class Voronoi extends Mark {
221221
y: {value: y, scale: "y", optional: true},
222222
z: {value: z, optional: true}
223223
},
224-
options,
224+
initializer(options, function (data, facets, channels, scales, dimensions, context) {
225+
let {x: X, y: Y, z: Z} = channels;
226+
({x: X, y: Y} = applyPosition(channels, scales, context));
227+
Z = Z?.value;
228+
const C = new Array((X ?? Y).length).fill(null);
229+
const [cx, cy] = applyFrameAnchor(this, dimensions);
230+
const xi = X ? (i) => X[i] : constant(cx);
231+
const yi = Y ? (i) => Y[i] : constant(cy);
232+
for (let I of facets) {
233+
if (X) I = I.filter((i) => defined(xi(i)));
234+
if (Y) I = I.filter((i) => defined(yi(i)));
235+
for (const [, J] of maybeGroup(I, Z)) {
236+
const delaunay = Delaunay.from(J, xi, yi);
237+
const voronoi = voronoiof(delaunay, dimensions);
238+
for (let i = 0, n = J.length; i < n; ++i) {
239+
C[J[i]] = voronoi.renderCell(i);
240+
}
241+
}
242+
}
243+
return {data, facets, channels: {cells: {value: C}}};
244+
}),
225245
voronoiDefaults
226246
);
227247
}
228248
render(index, scales, channels, dimensions, context) {
229249
const {x, y} = scales;
230-
const {x: X, y: Y, z: Z} = channels;
231-
const [cx, cy] = applyFrameAnchor(this, dimensions);
232-
const xi = X ? (i) => X[i] : constant(cx);
233-
const yi = Y ? (i) => Y[i] : constant(cy);
234-
const mark = this;
235-
236-
function cells(index) {
237-
const delaunay = Delaunay.from(index, xi, yi);
238-
const voronoi = voronoiof(delaunay, dimensions);
239-
select(this)
240-
.selectAll()
241-
.data(index)
242-
.enter()
243-
.append("path")
244-
.call(applyDirectStyles, mark)
245-
.attr("d", (_, i) => voronoi.renderCell(i))
246-
.call(applyChannelStyles, mark, channels);
247-
}
248-
250+
const {x: X, y: Y, cells: C} = channels;
249251
return create("svg:g", context)
250252
.call(applyIndirectStyles, this, dimensions, context)
251253
.call(applyTransform, this, {x: X && x, y: Y && y})
252-
.call(
253-
Z
254-
? (g) =>
255-
g
256-
.selectAll()
257-
.data(group(index, (i) => Z[i]).values())
258-
.enter()
259-
.append("g")
260-
.each(cells)
261-
: (g) => g.datum(index).each(cells)
262-
)
254+
.call((g) => {
255+
g.selectAll()
256+
.data(index)
257+
.enter()
258+
.append("path")
259+
.call(applyDirectStyles, this)
260+
.attr("d", (i) => C[i])
261+
.call(applyChannelStyles, this, channels);
262+
})
263263
.node();
264264
}
265265
}
@@ -296,8 +296,8 @@ export function hull(data, options) {
296296
return delaunayMark(Hull, data, options);
297297
}
298298

299-
export function voronoi(data, options) {
300-
return delaunayMark(Voronoi, data, options);
299+
export function voronoi(data, {x, y, initializer, ...options} = {}) {
300+
return delaunayMark(Voronoi, data, {...basic({...options, x, y}, exclusiveFacets), initializer});
301301
}
302302

303303
export function voronoiMesh(data, options) {

src/style.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ export function* groupIndex(I, position, mark, channels) {
297297
}
298298
}
299299

300+
// TODO avoid creating a new clip-path each time?
300301
// Note: may mutate selection.node!
301302
function applyClip(selection, mark, dimensions, context) {
302303
let clipUrl;

test/output/penguinCulmenVoronoi.svg

Lines changed: 1 addition & 4 deletions
Loading

0 commit comments

Comments
 (0)