Skip to content

Commit 81ad4a2

Browse files
committed
feat: add the ability to brush historical
1 parent 9f6cdc1 commit 81ad4a2

9 files changed

Lines changed: 3568 additions & 47 deletions

File tree

__tests__/integration/snapshots/interaction/penguins-point-brush-filter-with-history/step0.svg

Lines changed: 1140 additions & 0 deletions
Loading

__tests__/integration/snapshots/interaction/penguins-point-brush-filter-with-history/step1.svg

Lines changed: 1141 additions & 0 deletions
Loading

__tests__/integration/snapshots/interaction/penguins-point-brush-filter-with-history/step2.svg

Lines changed: 1142 additions & 0 deletions
Loading

__tests__/plots/interaction/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { cars3LineBrushAxis } from './cars3-line-brush-axis';
2424
export { cars3LineVerticalBrushAxis } from './cars3-line-vertical-brush-axis';
2525
export { penguinsPointBrushAxis } from './penguins-point-brush-axis';
2626
export { penguinsPointBrushFilter } from './penguins-point-brush-filter';
27+
export { penguinsPointBrushFilterWithHistory } from './penguins-point-brush-filter-with-history';
2728
export { settleWeatherCellBrushFilter } from './seattle-weather-cell-brush-filter';
2829
export { profitIntervalBrushFilter } from './profit-interval-brush-filter';
2930
export { penguinsPointBrushXFilter } from './penguins-point-brush-x-filter';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { G2Spec, PLOT_CLASS_NAME } from '../../../src';
2+
import { brush, dblclick } from './penguins-point-brush';
3+
4+
export function penguinsPointBrushFilterWithHistory(): G2Spec {
5+
return {
6+
type: 'point',
7+
data: {
8+
type: 'fetch',
9+
value: 'data/penguins.csv',
10+
},
11+
axis: { x: { animate: false }, y: { animate: false } },
12+
encode: {
13+
color: 'species',
14+
x: 'culmen_length_mm',
15+
y: 'culmen_depth_mm',
16+
},
17+
state: {
18+
inactive: { stroke: 'gray' },
19+
},
20+
interaction: {
21+
brushFilter: {
22+
history: true,
23+
},
24+
},
25+
};
26+
}
27+
28+
penguinsPointBrushFilterWithHistory.steps = ({ canvas }) => {
29+
const { document } = canvas;
30+
const plot = document.getElementsByClassName(PLOT_CLASS_NAME)[0];
31+
return [
32+
{
33+
changeState: () => {
34+
brush(plot, 100, 100, 300, 300);
35+
},
36+
},
37+
{
38+
changeState: () => {
39+
brush(plot, 100, 100, 300, 300);
40+
},
41+
},
42+
{
43+
changeState: () => {
44+
dblclick(plot);
45+
},
46+
},
47+
];
48+
};

site/docs/manual/core/interaction/brushFilter.en.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ It can also be configured at the View level. Interactions declared on the view w
9494
| Property | Description | Type | Default | Required |
9595
| -------- | ------------------------ | ------------- | ----------------- | -------- |
9696
| reverse | Whether to reverse brush | boolean | false | |
97+
| history | Whether to histroy brush | boolean | false | |
9798
| mask | Style of brush area mask | [mask](#mask) | See [mask](#mask) | |
9899

99100
### mask

site/docs/manual/core/interaction/brushFilter.zh.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ chart.render();
9494
| 属性 | 描述 | 类型 | 默认值 | 必选 |
9595
| ------- | ------------------ | ------------- | ------------------ | ---- |
9696
| reverse | brush 是否反转 | boolean | false | |
97+
| history | brush 框选记录 | boolean | false | |
9798
| mask | 框选区域的蒙版样式 | [mask](#mask) | 详见 [mask](#mask) | |
9899

99100
### mask

src/interaction/brushFilter.ts

Lines changed: 93 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ export function brushFilter(
8181
};
8282
}
8383

84-
export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
84+
export function BrushFilter({
85+
hideX = true,
86+
hideY = true,
87+
history = false,
88+
...rest
89+
}) {
8590
return (target, viewInstances, emitter) => {
8691
const { container, view, options: viewOptions, update, setState } = target;
8792
const plotArea = selectPlotArea(container);
@@ -96,8 +101,36 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
96101
let filtered = false;
97102
let filtering = false;
98103
let newView = view;
99-
104+
const brushHistory = [];
100105
const { scale, coordinate } = view;
106+
const updateScale = (options, domainX, domainY) => {
107+
const { marks } = options;
108+
const newMarks = marks.map((mark) =>
109+
deepMix(
110+
{
111+
// Hide label to keep smooth transition.
112+
axis: {
113+
...(hideX && { x: { transform: [{ type: 'hide' }] } }),
114+
...(hideY && { y: { transform: [{ type: 'hide' }] } }),
115+
},
116+
},
117+
mark,
118+
{
119+
// Set nice to false to avoid modify domain.
120+
scale: {
121+
x: { domain: domainX, nice: false },
122+
y: { domain: domainY, nice: false },
123+
},
124+
},
125+
),
126+
);
127+
128+
return {
129+
...viewOptions,
130+
marks: newMarks,
131+
clip: true, // Clip shapes out of plot area.
132+
};
133+
};
101134
return brushFilter(plotArea, {
102135
brushRegion: (x, y, x1, y1) => [x, y, x1, y1],
103136
selection: (x, y, x1, y1) => {
@@ -112,62 +145,75 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
112145
// Update the domain of x and y scale to filter data.
113146
const [domainX, domainY] = selection;
114147

115-
setState('brushFilter', (options) => {
116-
const { marks } = options;
117-
const newMarks = marks.map((mark) =>
118-
deepMix(
119-
{
120-
// Hide label to keep smooth transition.
121-
axis: {
122-
...(hideX && { x: { transform: [{ type: 'hide' }] } }),
123-
...(hideY && { y: { transform: [{ type: 'hide' }] } }),
124-
},
125-
},
126-
mark,
127-
{
128-
// Set nice to false to avoid modify domain.
129-
scale: {
130-
x: { domain: domainX, nice: false },
131-
y: { domain: domainY, nice: false },
132-
},
133-
},
134-
),
135-
);
136-
137-
return {
138-
...viewOptions,
139-
marks: newMarks,
140-
clip: true, // Clip shapes out of plot area.
141-
};
142-
});
143-
148+
setState('brushFilter', (options) =>
149+
updateScale(options, domainX, domainY),
150+
);
151+
if (history) {
152+
brushHistory.push({
153+
domain: selection,
154+
view: newView,
155+
});
156+
}
144157
// Emit event.
145158
emitter.emit('brush:filter', {
146159
...event,
147-
data: { selection: [domainX, domainY] },
160+
data: {
161+
...(history ? { history: brushHistory } : {}),
162+
selection: [domainX, domainY],
163+
},
148164
});
149-
150165
const newState = await update();
151166
newView = newState.view;
152167
filtering = false;
153168
filtered = true;
154169
},
155-
reset: (event) => {
170+
reset: async (event) => {
156171
if (filtering || !filtered) return;
172+
event.nativeEvent = true;
157173

158-
// Emit event.
159-
const { scale } = view;
160-
const { x: scaleX, y: scaleY } = scale;
161-
const domainX = scaleX.getOptions().domain;
162-
const domainY = scaleY.getOptions().domain;
163-
emitter.emit('brush:filter', {
164-
...event,
165-
data: { selection: [domainX, domainY] },
166-
});
167-
filtered = false;
168-
newView = view;
169-
setState('brushFilter');
170-
update();
174+
// Restore previous brush state
175+
if (brushHistory.length > 1) {
176+
brushHistory.pop();
177+
178+
// Get the last brush state
179+
const { domain, view: lastView } =
180+
brushHistory[brushHistory.length - 1];
181+
182+
const [domainX, domainY] = domain;
183+
newView = lastView;
184+
185+
// update scale
186+
setState('brushFilter', (options) =>
187+
updateScale(options, domainX, domainY),
188+
);
189+
190+
emitter.emit('brush:filter', {
191+
...event,
192+
data: {
193+
...(history ? { history: brushHistory } : {}),
194+
selection: [domainX, domainY],
195+
},
196+
});
197+
198+
await update();
199+
} else {
200+
// Reset to initial state
201+
brushHistory.length = 0;
202+
emitter.emit('brush:filter', {
203+
...event,
204+
data: {
205+
...(history ? { history: brushHistory } : {}),
206+
selection: [
207+
scale.x.getOptions().domain,
208+
scale.y.getOptions().domain,
209+
],
210+
},
211+
});
212+
setState('brushFilter');
213+
newView = view;
214+
filtered = false;
215+
await update();
216+
}
171217
},
172218
extent: undefined,
173219
emitter,

src/spec/interaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export type BrushAxisHighlightInteraction = {
8787
export type BrushFilterInteraction = {
8888
type?: 'brushFilter';
8989
reverse?: boolean;
90+
history?: boolean;
9091
} & Record<`${'mask'}${any}`, any>;
9192

9293
export type BrushXFilterInteraction = {

0 commit comments

Comments
 (0)