Skip to content

Commit afed24b

Browse files
committed
node[Plot.selection]
1 parent 76e8ffa commit afed24b

File tree

4 files changed

+38
-30
lines changed

4 files changed

+38
-30
lines changed

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export {RuleX, RuleY, ruleX, ruleY} from "./marks/rule.js";
1414
export {Text, text, textX, textY} from "./marks/text.js";
1515
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
1616
export {Vector, vector} from "./marks/vector.js";
17+
export {selection} from "./selection.js";
1718
export {valueof} from "./options.js";
1819
export {filter, reverse, sort, shuffle} from "./transforms/basic.js";
1920
export {bin, binX, binY} from "./transforms/bin.js";

src/marks/brush.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {brush as brusher, brushX as brusherX, brushY as brusherY, create, select} from "d3";
22
import {identity, maybeTuple} from "../options.js";
3-
import {Mark, selectionEquals} from "../plot.js";
3+
import {Mark} from "../plot.js";
4+
import {selection, selectionEquals} from "../selection.js";
45
import {applyDirectStyles, applyIndirectStyles} from "../style.js";
56

67
const defaults = {
@@ -32,33 +33,33 @@ export class Brush extends Mark {
3233
.call((X && Y ? brusher : X ? brusherX : brusherY)()
3334
.extent([[marginLeft, marginTop], [width - marginRight, height - marginBottom]])
3435
.on("start brush end", function(event) {
35-
const {type, selection} = event;
36+
const {type, selection: extent} = event;
3637
// For faceting, when starting a brush in a new facet, clear the
3738
// brush and selection on the old facet. In the future, we might
3839
// allow independent brushes across facets by disabling this?
3940
if (type === "start" && brush.currentElement !== this) {
4041
if (brush.currentElement !== null) {
4142
select(brush.currentElement).call(event.target.clear, event);
42-
brush.currentElement.selection = null;
43+
brush.currentElement[selection] = null;
4344
}
4445
brush.currentElement = this;
4546
}
4647
let S = null;
47-
if (selection) {
48+
if (extent) {
4849
S = index;
4950
if (X) {
50-
let [x0, x1] = Y ? [selection[0][0], selection[1][0]] : selection;
51+
let [x0, x1] = Y ? [extent[0][0], extent[1][0]] : extent;
5152
if (x.bandwidth) x0 -= x.bandwidth();
5253
S = S.filter(i => x0 <= X[i] && X[i] <= x1);
5354
}
5455
if (Y) {
55-
let [y0, y1] = X ? [selection[0][1], selection[1][1]] : selection;
56+
let [y0, y1] = X ? [extent[0][1], extent[1][1]] : extent;
5657
if (y.bandwidth) y0 -= y.bandwidth();
5758
S = S.filter(i => y0 <= Y[i] && Y[i] <= y1);
5859
}
5960
}
60-
if (!selectionEquals(this.selection, S)) {
61-
this.selection = S;
61+
if (!selectionEquals(this[selection], S)) {
62+
this[selection] = S;
6263
this.dispatchEvent(new Event("input", {bubbles: true}));
6364
}
6465
}))
@@ -67,7 +68,7 @@ export class Brush extends Mark {
6768
.call(applyIndirectStyles, options)
6869
.call(applyDirectStyles, options))
6970
.node();
70-
g.selection = null;
71+
g[selection] = null;
7172
return g;
7273
}
7374
}

src/plot.js

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Dimensions} from "./dimensions.js";
66
import {Legends, exposeLegends} from "./legends.js";
77
import {arrayify, isOptions, keyword, range, first, second, where, take} from "./options.js";
88
import {Scales, ScaleFunctions, autoScaleRange, applyScales, exposeScales} from "./scales.js";
9+
import {selection} from "./selection.js";
910
import {applyInlineStyles, maybeClassName, styles} from "./style.js";
1011
import {basic} from "./transforms/basic.js";
1112

@@ -102,11 +103,10 @@ export function plot(options = {}) {
102103
const index = filter(markIndex.get(mark), channels, values);
103104
const node = mark.render(index, scales, values, dimensions, axes);
104105
if (node != null) {
105-
// TODO A more explicit indication that a mark defines a value (e.g., a symbol)?
106-
if (node.selection !== undefined) {
107-
initialValue = markValue(mark, node.selection);
106+
if (node[selection] !== undefined) {
107+
initialValue = markValue(mark, node[selection]);
108108
node.addEventListener("input", () => {
109-
figure.value = markValue(mark, node.selection);
109+
figure.value = markValue(mark, node[selection]);
110110
});
111111
}
112112
svg.appendChild(node);
@@ -204,18 +204,6 @@ function markValue(mark, selection) {
204204
return selection === null ? mark.data : take(mark.data, selection);
205205
}
206206

207-
// Given two (possibly null, possibly an index, but not undefined) selections,
208-
// returns true if the two represent the same selection, and false otherwise.
209-
// This assumes that the selection is a in-order subset of the original index.
210-
export function selectionEquals(s1, s2) {
211-
if (s1 === s2) return true;
212-
if (s1 == null || s2 == null) return false;
213-
const n = s1.length;
214-
if (n !== s2.length) return false;
215-
for (let i = 0; i < n; ++i) if (s1[i] !== s2[i]) return false;
216-
return true;
217-
}
218-
219207
class Render extends Mark {
220208
constructor(render) {
221209
super();
@@ -345,13 +333,13 @@ class Facet extends Mark {
345333
const index = filter(marksFacetIndex[i], marksChannels[i], values);
346334
const node = marks[i].render(index, scales, values, subdimensions);
347335
if (node != null) {
348-
if (node.selection !== undefined) {
336+
if (node[selection] !== undefined) {
349337
if (marks[i].data !== data) throw new Error("selection must use facet data");
350338
if (selectionByFacet === undefined) selectionByFacet = facetMap(channels);
351-
selectionByFacet.set(key, node.selection);
339+
selectionByFacet.set(key, node[selection]);
352340
node.addEventListener("input", () => {
353-
selectionByFacet.set(key, node.selection);
354-
parent.selection = facetSelection(selectionByFacet);
341+
selectionByFacet.set(key, node[selection]);
342+
parent[selection] = facetSelection(selectionByFacet);
355343
});
356344
}
357345
this.appendChild(node);
@@ -360,7 +348,7 @@ class Facet extends Mark {
360348
}))
361349
.node();
362350
if (selectionByFacet !== undefined) {
363-
parent.selection = facetSelection(selectionByFacet);
351+
parent[selection] = facetSelection(selectionByFacet);
364352
}
365353
return parent;
366354
}

src/selection.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This symbol is used by interactive marks to define which data are selected. A
2+
// node returned by mark.render may expose a selection as node[selection], whose
3+
// value may be an array of numbers (e.g., [0, 1, 2, …]) representing an
4+
// in-order subset of the rendered index, or null if the selection is undefined.
5+
// The selection can be updated during interaction by emitting an input event.
6+
export const selection = Symbol("selection");
7+
8+
// Given two (possibly null, possibly an index, but not undefined) selections,
9+
// returns true if the two represent the same selection, and false otherwise.
10+
// This assumes that the selection is a in-order subset of the original index.
11+
export function selectionEquals(s1, s2) {
12+
if (s1 === s2) return true;
13+
if (s1 == null || s2 == null) return false;
14+
const n = s1.length;
15+
if (n !== s2.length) return false;
16+
for (let i = 0; i < n; ++i) if (s1[i] !== s2[i]) return false;
17+
return true;
18+
}

0 commit comments

Comments
 (0)