Skip to content

Commit

Permalink
feat(interaction): add scrollbarFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini committed Jun 2, 2023
1 parent ef2a31d commit a0ce90b
Show file tree
Hide file tree
Showing 19 changed files with 386 additions and 56 deletions.
76 changes: 76 additions & 0 deletions __tests__/integration/api-chart-emit-scrollbar-filter.spec.ts
@@ -0,0 +1,76 @@
import { chartEmitScrollbarFilter as render } from '../plots/api/chart-emit-scrollbar-filter';
import { SCROLLBAR_CLASS_NAME } from '../../src/interaction/scrollbarFilter';
import { dispatchValueChange } from '../plots/tooltip/appl-line-slider-filter';
import { createNodeGCanvas } from './utils/createNodeGCanvas';
import { sleep } from './utils/sleep';
import { kebabCase } from './utils/kebabCase';
import { createPromise } from './utils/event';
import './utils/useSnapshotMatchers';

describe('chart.emit', () => {
const dir = `${__dirname}/snapshots/api/${kebabCase(render.name)}`;
const canvas = createNodeGCanvas(800, 500);

it('chart.on("scrollbar:filter") should receive expected data.', async () => {
const { chart, finished } = render({
canvas,
container: document.createElement('div'),
});
await finished;
await sleep(20);

const [SX, SY] = Array.from(
canvas.document.getElementsByClassName(SCROLLBAR_CLASS_NAME),
);

// chart.emit('scrollbarX:filter', options) should trigger scrollbar.
const X = ['2001-03'];
chart.emit('scrollbarX:filter', {
data: { selection: [X, undefined] },
});
await sleep(20);
await expect(canvas).toMatchCanvasSnapshot(dir, 'step0');

// chart.emit('scrollbarY:filter', options) should trigger scrollbar.
const Y = [50, 550];
chart.emit('scrollbarY:filter', {
data: { selection: [undefined, Y] },
});
await sleep(20);
await expect(canvas).toMatchCanvasSnapshot(dir, 'step1');

chart.off();

// chart.on("scrollbarX:filter") should receive expected data.
const [filteredX, resolveX] = createPromise();
chart.on('scrollbarX:filter', (event) => {
if (!event.nativeEvent) return;
expect(event.data.selection).toEqual([
['2001-05', '2002-03'],
[50, 500],
]);
resolveX();
});
dispatchValueChange(SX);
await sleep(20);
await filteredX;

// chart.on("scrollbarY:filter") should receive expected data.
const [filteredY, resolveY] = createPromise();
chart.on('scrollbarY:filter', (event) => {
if (!event.nativeEvent) return;
expect(event.data.selection).toEqual([
['2001-05', '2002-03'],
[150, 450],
]);
resolveY();
});
dispatchValueChange(SY);
await sleep(20);
await filteredY;
});

afterAll(() => {
canvas?.destroy();
});
});
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/integration/snapshots/static/aaplLineScrollbar.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions __tests__/plots/api/chart-emit-scrollbar-filter.ts
@@ -0,0 +1,83 @@
import { Chart } from '../../../src';

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

// button
const buttonX = document.createElement('button');
buttonX.innerText = 'FilterX';
container.appendChild(buttonX);

const buttonY = document.createElement('button');
buttonY.innerText = 'FilterY';
container.appendChild(buttonY);

// wrapperDiv
const wrapperDiv = document.createElement('div');
container.appendChild(wrapperDiv);

const chart = new Chart({
theme: 'classic',
container: wrapperDiv,
paddingBottom: 120,
width: 400,
canvas,
clip: true,
});

chart
.interval()
.data([
{ date: '2001-01', value: 100 },
{ date: '2001-02', value: 400 },
{ date: '2001-03', value: 500 },
{ date: '2001-04', value: 600 },
{ date: '2001-05', value: 300 },
{ date: '2001-06', value: 600 },
{ date: '2001-07', value: 300 },
{ date: '2001-08', value: 600 },
{ date: '2001-09', value: 109 },
{ date: '2001-10', value: 100 },
{ date: '2001-11', value: 102 },
{ date: '2001-12', value: 103 },
{ date: '2002-01', value: 102 },
{ date: '2002-02', value: 101 },
{ date: '2002-03', value: 200 },
{ date: '2002-04', value: 500 },
{ date: '2002-05', value: 100 },
{ date: '2002-06', value: 100 },
{ date: '2002-07', value: 102 },
{ date: '2002-08', value: 109 },
])
.encode('x', 'date')
.encode('y', 'value')
.axis('x', { size: 80, style: { labelTransform: 'rotate(90deg)' } })
.scrollbar('x', { ratio: 0.25 })
.scrollbar('y', { ratio: 0.75 });

const finished = chart.render();

chart.on('scrollbarX:filter', (event) => {
const { data, nativeEvent } = event;
if (nativeEvent) console.log('scrollbarX:filter', data);
});

chart.on('scrollbarY:filter', (event) => {
const { data, nativeEvent } = event;
if (nativeEvent) console.log('scrollbarY:filter', data);
});

buttonX.onclick = () => {
chart.emit('scrollbarX:filter', {
data: { selection: [['2001-03'], undefined] },
});
};

buttonY.onclick = () => {
chart.emit('scrollbarY:filter', {
data: { selection: [undefined, [50]] },
});
};

return { chart, finished };
}
1 change: 1 addition & 0 deletions __tests__/plots/api/index.ts
Expand Up @@ -33,3 +33,4 @@ export { chartEmitLegendHighlight } from './chart-emit-legend-highlight';
export { chartEmitBrushHighlightAxisVertical } from './chart-emit-brush-highlight-axis-vertical';
export { chartEmitBrushHighlightAxisHorizontal } from './chart-emit-brush-highlight-axis-horizontal';
export { chartEmitBrushHighlightAxisCross } from './chart-emit-brush-highlight-axis-cross';
export { chartEmitScrollbarFilter } from './chart-emit-scrollbar-filter';
16 changes: 2 additions & 14 deletions __tests__/unit/stdlib/index.spec.ts
Expand Up @@ -168,23 +168,10 @@ import {
BrushYFilter,
BrushXFilter,
SliderFilter,
ScrollbarFilter,
LegendHighlight,
Poptip,
Event,
// ElementSelected,
// Tooltip,
// Fisheye as FisheyeInteraction,
// ElementHighlightByColor,
// ElementHighlightByX,
// ElementHighlight,
// ElementListHighlight,
// LegendActive,
// LegendHighlight,
// Brush,
// BrushHighlight,
// BrushVisible,
// ActiveRegion,
// EllipsisText,
} from '../../../src/interaction';
import {
SpaceLayer,
Expand Down Expand Up @@ -504,6 +491,7 @@ describe('stdlib', () => {
'interaction.brushXFilter': BrushXFilter,
'interaction.brushFilter': BrushFilter,
'interaction.sliderFilter': SliderFilter,
'interaction.scrollbarFilter': ScrollbarFilter,
'interaction.poptip': Poptip,
'interaction.event': Event,
'composition.spaceLayer': SpaceLayer,
Expand Down
5 changes: 5 additions & 0 deletions site/docs/spec/interaction/scrollbarFilter.en.md
@@ -0,0 +1,5 @@
---
title: scrollbarFilter
---

<embed src="@/docs/spec/interaction/scrollbarFilter.zh.md"></embed>
59 changes: 59 additions & 0 deletions site/docs/spec/interaction/scrollbarFilter.zh.md
@@ -0,0 +1,59 @@
---
title: scrollbarFilter
order: 1
---

滚动条筛选是一个默认交互,当设置了 scrollbar 默认就会有这个交互。

## 开始使用

```js
import { Chart } from '@antv/g2';

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

chart
.line()
.data({
type: 'fetch',
value:
'https://gw.alipayobjects.com/os/bmw-prod/551d80c6-a6be-4f3c-a82a-abd739e12977.csv',
})
.encode('x', 'date')
.encode('y', 'close')
.scrollbar('y', { ratio: 0.3 }) // y domain 的比例
.scrollbar('x', { ratio: 0.5 }); // x domain 的比例

chart.render();
```

## 案例

### 触发事件

```js
chart.emit('scrollbarX:filter', {
data: { selection: [['2001-03'], undefined] },
});

chart.emit('scrollbarY:filter', {
data: { selection: [undefined, [50, 550]] },
});
```

### 监听数据

```js
chart.on('scrollbarX:filter', (event) => {
const { data, nativeEvent } = event;
if (nativeEvent) console.log('scrollbarX:filter', data.selection);
});

chart.on('scrollbarY:filter', (event) => {
const { data, nativeEvent } = event;
if (nativeEvent) console.log('scrollbarY:filter', data.selection);
});
```
19 changes: 11 additions & 8 deletions src/component/scrollbar.ts
Expand Up @@ -12,24 +12,27 @@ export type ScrollbarOptions = {
export const Scrollbar: GCC<ScrollbarOptions> = (options) => {
const { orientation, labelFormatter, style, ...rest } = options;

return ({ value, theme }) => {
return ({ scales: [scale], value, theme }) => {
const { bbox } = value;
const { x, y, width, height } = bbox;
const { scrollbar: scrollbarTheme = {} } = theme;

const { ratio, range } = scale.getOptions();
const mainSize = orientation === 'horizontal' ? width : height;
const actualSize = mainSize / ratio;
const [r0, r1] = range;
const value1 = r1 > r0 ? 0 : 1;
return new ScrollbarComponent({
className: 'scrollbar',
className: 'g2-scrollbar',
style: Object.assign({}, scrollbarTheme, {
...style,
x,
y,
trackLength: orientation === 'horizontal' ? width : height,
trackLength: mainSize,
...rest,
orientation,
value: 0,
// @todo Get actual length of content.
contentLength: 1500,
viewportLength: orientation === 'horizontal' ? width : height,
value: value1,
contentLength: actualSize,
viewportLength: mainSize,
}),
});
};
Expand Down
1 change: 1 addition & 0 deletions src/interaction/index.ts
Expand Up @@ -17,5 +17,6 @@ export { BrushFilter } from './brushFilter';
export { BrushXFilter } from './brushXFilter';
export { BrushYFilter } from './brushYFilter';
export { SliderFilter } from './sliderFilter';
export { ScrollbarFilter } from './scrollbarFilter';
export { Poptip } from './poptip';
export { Event } from './event';
32 changes: 32 additions & 0 deletions src/interaction/scrollbarFilter.ts
@@ -0,0 +1,32 @@
import { SliderFilter } from './sliderFilter';

export const SCROLLBAR_CLASS_NAME = 'g2-scrollbar';

export function ScrollbarFilter(options: any = {}) {
return (context, _, emitter) => {
const { view, container } = context;
const scrollbars = container.getElementsByClassName(SCROLLBAR_CLASS_NAME);
if (!scrollbars.length) return () => {};
const { scale } = view;
const { x: scaleX, y: scaleY } = scale;
const channelDomain = {
x: scaleX.getOptions().domain,
y: scaleY.getOptions().domain,
};
scaleX.update({
domain: scaleX.getOptions().expectedDomain,
});
scaleY.update({
domain: scaleY.getOptions().expectedDomain,
});
const interaction = SliderFilter({
...options,
channelDomain,
className: SCROLLBAR_CLASS_NAME,
prefix: 'scrollbar',
hasState: true,
setValue: (component, values) => component.setValue(values[0]),
});
return interaction(context, _, emitter);
};
}

0 comments on commit a0ce90b

Please sign in to comment.