Skip to content

Commit

Permalink
feat(interaction): brushFilter emit filter event (#4933)
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini committed May 4, 2023
1 parent 835933b commit 0e8f2d9
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 74 deletions.
52 changes: 52 additions & 0 deletions __tests__/integration/chart-on-brush-filter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { chartOnBrushFilter as render } from '../plots/api/chart-on-brush-filter';
import { PLOT_CLASS_NAME } from '../../src';
import { dblclick, brush } from '../plots/interaction/penguins-point-brush';
import { createDOMGCanvas } from './utils/createDOMGCanvas';
import { createPromise, receiveExpectData } from './utils/event';
import './utils/useCustomFetch';

describe('chart.on', () => {
const canvas = createDOMGCanvas(640, 480);
const { finished, chart } = render({ canvas });

chart.off();

it('chart.on("element:filter", callback) should provide selection when filtering', async () => {
await finished;
const { document } = canvas;
const plot = document.getElementsByClassName(PLOT_CLASS_NAME)[0];

// Brush plot.
const [filtered, resolve] = createPromise();
chart.on(
'brush:filter',
receiveExpectData(resolve, {
selection: [
[34.99184225303586, 44.72635552737214],
[15.877014192597635, 20.13017874955966],
],
}),
);
brush(plot, 100, 100, 300, 300);
await filtered;

// Reset plot.
const [rested, resolve1] = createPromise();
chart.off();
chart.on(
'brush:filter',
receiveExpectData(resolve1, {
selection: [
[32.1, 59.6],
[13.1, 21.5],
],
}),
);
setTimeout(() => dblclick(plot), 1000);
await rested;
});

afterAll(() => {
canvas?.destroy();
});
});
126 changes: 64 additions & 62 deletions __tests__/integration/chart-on-series-element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,70 @@ import { createDOMGCanvas } from './utils/createDOMGCanvas';
import { dispatchEvent, createPromise, receiveExpectData } from './utils/event';
import './utils/useSnapshotMatchers';

const data = [
{
month: 'Jan',
city: 'Tokyo',
temperature: 7,
},
{
month: 'Feb',
city: 'Tokyo',
temperature: 6.9,
},
{
month: 'Mar',
city: 'Tokyo',
temperature: 9.5,
},
{
month: 'Apr',
city: 'Tokyo',
temperature: 14.5,
},
{
month: 'May',
city: 'Tokyo',
temperature: 18.4,
},
{
month: 'Jun',
city: 'Tokyo',
temperature: 21.5,
},
{
month: 'Jul',
city: 'Tokyo',
temperature: 25.2,
},
{
month: 'Aug',
city: 'Tokyo',
temperature: 26.5,
},
{
month: 'Sep',
city: 'Tokyo',
temperature: 23.3,
},
{
month: 'Oct',
city: 'Tokyo',
temperature: 18.3,
},
{
month: 'Nov',
city: 'Tokyo',
temperature: 13.9,
},
{
month: 'Dec',
city: 'Tokyo',
temperature: 9.6,
},
];
const data = {
data: [
{
month: 'Jan',
city: 'Tokyo',
temperature: 7,
},
{
month: 'Feb',
city: 'Tokyo',
temperature: 6.9,
},
{
month: 'Mar',
city: 'Tokyo',
temperature: 9.5,
},
{
month: 'Apr',
city: 'Tokyo',
temperature: 14.5,
},
{
month: 'May',
city: 'Tokyo',
temperature: 18.4,
},
{
month: 'Jun',
city: 'Tokyo',
temperature: 21.5,
},
{
month: 'Jul',
city: 'Tokyo',
temperature: 25.2,
},
{
month: 'Aug',
city: 'Tokyo',
temperature: 26.5,
},
{
month: 'Sep',
city: 'Tokyo',
temperature: 23.3,
},
{
month: 'Oct',
city: 'Tokyo',
temperature: 18.3,
},
{
month: 'Nov',
city: 'Tokyo',
temperature: 13.9,
},
{
month: 'Dec',
city: 'Tokyo',
temperature: 9.6,
},
],
};

describe('chart.on', () => {
const canvas = createDOMGCanvas(640, 480);
Expand Down
8 changes: 5 additions & 3 deletions __tests__/integration/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export function createPromise() {
export function receiveExpectData(
resolve,
data: any = {
genre: 'Sports',
sold: 275,
data: {
genre: 'Sports',
sold: 275,
},
},
) {
return (event) => {
expect(event.data.data).toEqual(data);
expect(event.data).toEqual(data);
resolve();
};
}
Expand Down
33 changes: 33 additions & 0 deletions __tests__/plots/api/chart-on-brush-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Chart } from '../../../src';

export function chartOnBrushFilter(context) {
const { container, canvas } = context;

const chart = new Chart({
theme: 'classic',
container,
canvas,
});

chart.options({
type: 'point',
data: {
type: 'fetch',
value: 'data/penguins.csv',
},
encode: {
color: 'species',
x: 'culmen_length_mm',
y: 'culmen_depth_mm',
},
interaction: { brushFilter: true },
});

chart.on('brush:filter', (event) => {
console.log(event.data.selection);
});

const finished = chart.render();

return { chart, finished };
}
1 change: 1 addition & 0 deletions __tests__/plots/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export { chartEmitLegendFilter } from './chart-emit-legend-filter';
export { chartChangeSizePolar } from './chart-change-size-polar';
export { chartChangeDataFacet } from './chart-change-data-facet';
export { chartRenderClearAnimation } from './chart-render-clear-animation';
export { chartOnBrushFilter } from './chart-on-brush-filter';
14 changes: 14 additions & 0 deletions site/docs/spec/interaction/brushFilter.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,17 @@ chart.render();
| ------------------- | -------------- | ------------------------------ | ------ |
| reverse | brush 是否反转 | `boolean` | false |
| `mask${StyleAttrs}` | brush 的样式 | `number \| string` | - |

## 案例

获得当前筛选数据,会在每次筛选和重置的时候触发以下事件:

```js
chart.on('brush:filter', (event) => {
const { selection } = event.data;
const [domainX, domainY] = selection;
const [minX, maxX] = domainX;
const [minY, maxY] = domainY;
console.log(minX, maxX, minY, maxY);
});
```
33 changes: 25 additions & 8 deletions src/interaction/brushFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export function brushFilter(
root.addEventListener('click', click);

// Filter when brush created.
function brushcreated(x, y, x1, y1) {
filter(x, y, x1, y1);
function brushcreated(x, y, x1, y1, event) {
filter(x, y, x1, y1, event);
brush.remove();
}

// Reset when dblclick.
function click(e) {
if (isDblclick(e)) reset();
if (isDblclick(e)) reset(e);
}

return () => {
Expand All @@ -65,7 +65,7 @@ export function brushFilter(
}

export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
return (target, viewInstances) => {
return (target, viewInstances, emitter) => {
const { container, view, options: viewOptions, update } = target;
const plotArea = selectPlotArea(container);
const defaultOptions = {
Expand All @@ -82,7 +82,7 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {

return brushFilter(plotArea, {
brushRegion: (x, y, x1, y1) => [x, y, x1, y1],
filter: async (x, y, x1, y1) => {
filter: async (x, y, x1, y1, event) => {
// Avoid redundant filter.
if (filtering) return;
filtering = true;
Expand All @@ -99,6 +99,8 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {

// Update the domain of x and y scale to filter data.
const { marks } = viewOptions;
const domainX = domainOf(scaleX, [p0[0], p1[0]]);
const domainY = domainOf(scaleY, [p0[1], p1[1]]);
const newMarks = marks.map((mark) =>
deepMix(
{
Expand All @@ -111,13 +113,18 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
mark,
{
scale: {
x: { domain: domainOf(scaleX, [p0[0], p1[0]]) },
y: { domain: domainOf(scaleY, [p0[1], p1[1]]) },
x: { domain: domainX },
y: { domain: domainY },
},
},
),
);

// Emit event.
event.data = event.data || {};
event.data.selection = [domainX, domainY];
emitter.emit('brush:filter', event);

// Rerender and update view.
const newOptions = {
...viewOptions,
Expand All @@ -129,8 +136,18 @@ export function BrushFilter({ hideX = true, hideY = true, ...rest }) {
filtering = false;
filtered = true;
},
reset: () => {
reset: (event) => {
if (filtering || !filtered) return;

// Emit event.
const { scale } = view;
const { x: scaleX, y: scaleY } = scale;
const domainX = scaleX.getOptions().domain;
const domainY = scaleY.getOptions().domain;
event.data = event.data || {};
event.data.selection = [domainX, domainY];
emitter.emit('brush:filter', event);

filtered = false;
newView = view;
update(viewOptions);
Expand Down
2 changes: 1 addition & 1 deletion src/interaction/brushHighlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export function brush(
end = brushMousePosition(root, event);
const [fx, fy, fx1, fy1] = updateMask(start, end);
resizing = false;
brushcreated(fx, fy, fx1, fy1);
brushcreated(fx, fy, fx1, fy1, event);
};

// Hide mask.
Expand Down

0 comments on commit 0e8f2d9

Please sign in to comment.