Skip to content

Commit d0d072b

Browse files
Filmbostock
andauthored
delaunay X and Y are optional (#931)
* delaunay X and Y are optional A complement to #930 the X and Y channels should be optional in all the voronoi marks—since d3-delaunay can handle collinear points (and it's not just for the sake of it: the resulting voronoi can be useful, as illustrated in the test case). * adopt applyFrameAnchor * one fewer closure Co-authored-by: Mike Bostock <[email protected]>
1 parent 30eca95 commit d0d072b

File tree

4 files changed

+1820
-26
lines changed

4 files changed

+1820
-26
lines changed

src/marks/delaunay.js

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {create, group, path, select, Delaunay} from "d3";
22
import {Curve} from "../curve.js";
3-
import {maybeTuple, maybeZ} from "../options.js";
3+
import {constant, maybeTuple, maybeZ} from "../options.js";
44
import {Mark} from "../plot.js";
5-
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
5+
import {applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, offset} from "../style.js";
66
import {markers, applyMarkers} from "./marker.js";
77

88
const delaunayLinkDefaults = {
@@ -47,8 +47,8 @@ class DelaunayLink extends Mark {
4747
super(
4848
data,
4949
[
50-
{name: "x", value: x, scale: "x"},
51-
{name: "y", value: y, scale: "y"},
50+
{name: "x", value: x, scale: "x", optional: true},
51+
{name: "y", value: y, scale: "y", optional: true},
5252
{name: "z", value: z, optional: true}
5353
],
5454
options,
@@ -60,6 +60,9 @@ class DelaunayLink extends Mark {
6060
render(index, {x, y}, channels, dimensions) {
6161
const {x: X, y: Y, z: Z} = channels;
6262
const {dx, dy, curve} = this;
63+
const [cx, cy] = applyFrameAnchor(this, dimensions);
64+
const xi = X ? i => X[i] : constant(cx);
65+
const yi = Y ? i => Y[i] : constant(cy);
6366
const mark = this;
6467

6568
function links(index) {
@@ -76,14 +79,14 @@ class DelaunayLink extends Mark {
7679
ti = index[ti];
7780
tj = index[tj];
7881
newIndex.push(++i);
79-
X1[i] = X[ti];
80-
Y1[i] = Y[ti];
81-
X2[i] = X[tj];
82-
Y2[i] = Y[tj];
82+
X1[i] = xi(ti);
83+
Y1[i] = yi(ti);
84+
X2[i] = xi(tj);
85+
Y2[i] = yi(tj);
8386
for (const k in channels) newChannels[k].push(channels[k][tj]);
8487
}
8588

86-
const {halfedges, hull, triangles} = Delaunay.from(index, i => X[i], i => Y[i]);
89+
const {halfedges, hull, triangles} = Delaunay.from(index, xi, yi);
8790
for (let i = 0; i < halfedges.length; ++i) { // inner edges
8891
const j = halfedges[i];
8992
if (j > i) link(triangles[i], triangles[j]);
@@ -126,33 +129,37 @@ class AbstractDelaunayMark extends Mark {
126129
super(
127130
data,
128131
[
129-
{name: "x", value: x, scale: "x"},
130-
{name: "y", value: y, scale: "y"},
132+
{name: "x", value: x, scale: "x", optional: true},
133+
{name: "y", value: y, scale: "y", optional: true},
131134
{name: "z", value: zof(options), optional: true}
132135
],
133136
options,
134137
defaults
135138
);
136139
}
137-
render(index, {x, y}, {x: X, y: Y, z: Z, ...channels}, dimensions) {
140+
render(index, {x, y}, channels, dimensions) {
141+
const {x: X, y: Y, z: Z} = channels;
138142
const {dx, dy} = this;
143+
const [cx, cy] = applyFrameAnchor(this, dimensions);
144+
const xi = X ? i => X[i] : constant(cx);
145+
const yi = Y ? i => Y[i] : constant(cy);
139146
const mark = this;
140-
function mesh(render) {
141-
return function(index) {
142-
const delaunay = Delaunay.from(index, i => X[i], i => Y[i]);
143-
select(this).append("path")
144-
.datum(index[0])
145-
.call(applyDirectStyles, mark)
146-
.attr("d", render(delaunay, dimensions))
147-
.call(applyChannelStyles, mark, channels);
148-
};
147+
148+
function mesh(index) {
149+
const delaunay = Delaunay.from(index, xi, yi);
150+
select(this).append("path")
151+
.datum(index[0])
152+
.call(applyDirectStyles, mark)
153+
.attr("d", mark._render(delaunay, dimensions))
154+
.call(applyChannelStyles, mark, channels);
149155
}
156+
150157
return create("svg:g")
151158
.call(applyIndirectStyles, this, dimensions)
152159
.call(applyTransform, x, y, offset + dx, offset + dy)
153160
.call(Z
154-
? g => g.selectAll().data(group(index, i => Z[i]).values()).enter().append("g").each(mesh(this._render))
155-
: g => g.datum(index).each(mesh(this._render)))
161+
? g => g.selectAll().data(group(index, i => Z[i]).values()).enter().append("g").each(mesh)
162+
: g => g.datum(index).each(mesh))
156163
.node();
157164
}
158165
}
@@ -182,8 +189,8 @@ class Voronoi extends Mark {
182189
super(
183190
data,
184191
[
185-
{name: "x", value: x, scale: "x"},
186-
{name: "y", value: y, scale: "y"},
192+
{name: "x", value: x, scale: "x", optional: true},
193+
{name: "y", value: y, scale: "y", optional: true},
187194
{name: "z", value: z, optional: true}
188195
],
189196
options,
@@ -193,9 +200,12 @@ class Voronoi extends Mark {
193200
render(index, {x, y}, channels, dimensions) {
194201
const {x: X, y: Y, z: Z} = channels;
195202
const {dx, dy} = this;
203+
const [cx, cy] = applyFrameAnchor(this, dimensions);
204+
const xi = X ? i => X[i] : constant(cx);
205+
const yi = Y ? i => Y[i] : constant(cy);
196206

197207
function cells(index) {
198-
const delaunay = Delaunay.from(index, i => X[i], i => Y[i]);
208+
const delaunay = Delaunay.from(index, xi, yi);
199209
const voronoi = voronoiof(delaunay, dimensions);
200210
select(this)
201211
.selectAll()

0 commit comments

Comments
 (0)