Skip to content

Commit 377fff8

Browse files
committed
rebase
1 parent a3c84f0 commit 377fff8

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {plot} from "./plot.js";
44
export {Mark, valueof} from "./mark.js";
55
export {Area, area, areaX, areaY} from "./marks/area.js";
66
export {BarX, BarY, barX, barY} from "./marks/bar.js";
7+
export {brush, brushX, brushY} from "./marks/brush.js";
78
export {Cell, cell, cellX, cellY} from "./marks/cell.js";
89
export {Dot, dot, dotX, dotY} from "./marks/dot.js";
910
export {Frame, frame} from "./marks/frame.js";

src/marks/brush.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {brush as brusher, brushX as brusherX, brushY as brusherY, create, extent} from "d3";
2+
import {filter} from "../defined.js";
3+
import {Mark, identity, first, second} from "../mark.js";
4+
import {Style} from "../style.js";
5+
const {max, min} = Math;
6+
7+
export class Brush extends Mark {
8+
constructor(
9+
data,
10+
{
11+
x = first,
12+
y = second,
13+
selection,
14+
transform,
15+
...style
16+
} = {}
17+
) {
18+
super(
19+
data,
20+
[
21+
{name: "picker", value: identity},
22+
{name: "x", value: x, optional: true},
23+
{name: "y", value: y, optional: true}
24+
],
25+
transform
26+
);
27+
Style(this, style);
28+
this.initialSelection = selection;
29+
}
30+
render(
31+
I,
32+
{x, y},
33+
{x: X, y: Y, picker: J},
34+
{marginLeft, width, marginRight, marginTop, height, marginBottom}
35+
) {
36+
let svg;
37+
const g = create("svg:g");
38+
const data = this.data;
39+
40+
const bounds = [
41+
[Math.floor(marginLeft), Math.floor(marginTop)],
42+
[Math.ceil(width - marginRight), Math.ceil(height - marginBottom)]
43+
];
44+
const brush = (x && y ? brusher : x ? brusherX : brusherY)()
45+
.extent(bounds)
46+
.on("start brush end", ({type, selection, sourceEvent}) => {
47+
let index = filter(I, X, Y);
48+
if (selection) {
49+
if (x) {
50+
const e = y ? [selection[0][0], selection[1][0]] : selection;
51+
const [x0, x1] = extent(e.map(x.invert));
52+
index = index.filter(i => X[i] >= x0 && X[i] <= x1);
53+
}
54+
if (y) {
55+
const e = x ? [selection[0][1], selection[1][1]] : selection;
56+
const [y0, y1] = extent(e.map(y.invert));
57+
index = index.filter(i => Y[i] >= y0 && Y[i] <= y1);
58+
}
59+
}
60+
const dots = selection ? Array.from(index, i => J[i]) : data;
61+
62+
if (svg) {
63+
svg.value = dots;
64+
svg.dispatchEvent(new CustomEvent('input'));
65+
if (sourceEvent && type === "start") {
66+
for (const {b, g} of svg.__brushes) {
67+
if (b !== brush) g.call(b.clear);
68+
}
69+
}
70+
}
71+
});
72+
73+
g.call(brush);
74+
75+
/* 🌶 async
76+
* wait for the ownerSVGElement to:
77+
* - send the first signal
78+
* - register the multiple brushes (for faceting)
79+
*/
80+
setTimeout(() => {
81+
svg = g.node().ownerSVGElement;
82+
if (!svg.__brushes) svg.__brushes = [];
83+
svg.__brushes.push({b: brush, g});
84+
85+
// initial setup works only on one facet
86+
if (svg.__brushes.length === 1) {
87+
if (this.initialSelection) {
88+
const s = this.initialSelection;
89+
if (x && y) {
90+
const [x0, x1] = extent([x(s[0][0]), x(s[1][0])]);
91+
const [y0, y1] = extent([y(s[0][1]), y(s[1][1])]);
92+
g.call(brush.move, [
93+
[ max(x0, bounds[0][0]), max(y0, bounds[0][1]) ],
94+
[ min(x1, bounds[1][0]), min(y1, bounds[1][1]) ]
95+
]);
96+
} else if (x) {
97+
const [x0, x1] = extent(s.map(x));
98+
g.call(brush.move, [ max(x0, bounds[0][0]), min(x1, bounds[1][0]) ]);
99+
} else if (y) {
100+
const [y0, y1] = extent(s.map(y));
101+
g.call(brush.move, [ max(y0, bounds[0][1]), min(y1, bounds[1][1]) ]);
102+
}
103+
} else {
104+
g.call(brush.clear);
105+
}
106+
}
107+
}, 1);
108+
109+
return g.node();
110+
}
111+
}
112+
113+
export function brush(data, options) {
114+
return new Brush(data, options);
115+
}
116+
117+
export function brushX(data, {x = identity, ...options} = {}) {
118+
return new Brush(data, {...options, x, y: null});
119+
}
120+
121+
export function brushY(data, {y = identity, ...options} = {}) {
122+
return new Brush(data, {...options, x: null, y});
123+
}

0 commit comments

Comments
 (0)