Skip to content

Commit

Permalink
Showing 40 changed files with 664 additions and 44 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions __tests__/plots/interaction/index.ts
Original file line number Diff line number Diff line change
@@ -58,3 +58,5 @@ export { populationIntervalDiverging } from './population-interval-diverging';
export { stateAgesIntervalNormalized } from './stateages-interval-normalized';
export { aaplLineSliderFilterTranspose } from './appl-line-slider-filter-transpose';
export { alphabetIntervalFunnelLegendFilter } from './alphabet-interval-funnel-legend-filter';
export { penguinsPointBrushHandleStyle } from './penguins-point-brush-handle-style';
export { penguinsPointBrushHandleCustom } from './penguins-point-brush-handle-custom';
102 changes: 102 additions & 0 deletions __tests__/plots/interaction/penguins-point-brush-handle-custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { G2Spec, PLOT_CLASS_NAME } from '../../../src';
import { brush, brushSteps } from './penguins-point-brush';

function createPathRender(path) {
return (group, options, document) => {
if (!group.handle) {
const path = document.createElement('path');
group.handle = path;
group.appendChild(group.handle);
}
const { handle } = group;
const { width, height, ...rest } = options;
if (width === undefined || height === undefined) return handle;
handle.style.d = path(width, height);
handle.attr(rest);
return handle;
};
}

export function penguinsPointBrushHandleCustom(): G2Spec {
return {
type: 'point',
data: {
type: 'fetch',
value: 'data/penguins.csv',
},
encode: {
color: 'species',
x: 'culmen_length_mm',
y: 'culmen_depth_mm',
},
state: {
inactive: { stroke: 'gray', opacity: 0.5 },
},
interaction: {
brushHighlight: {
maskHandleSize: 30,
maskHandleNRender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width / 2},${-height / 2}L${width},${
height / 2
},Z`,
),
maskHandleERender: createPathRender(
(width, height) =>
`M${width / 2},0L${(width * 3) / 2},${height / 2}L${
width / 2
},${height},Z`,
),
maskHandleSRender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width / 2},${(height / 2) * 3}L${width},${
height / 2
},Z`,
),
maskHandleWRender: createPathRender(
(width, height) =>
`M${width / 2},0L${-width},${height / 2}L${width / 2},${height},Z`,
),
maskHandleNWRender: createPathRender(
(width, height) =>
`M0,0L${width},${height / 2}L${width / 2},${height},Z`,
),
maskHandleNERender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width},0L${width / 2},${height},Z`,
),
maskHandleSERender: createPathRender(
(width, height) =>
`M${width / 2},0L${width},${height}L0,${height / 2},Z`,
),
maskHandleSWRender: createPathRender(
(width, height) =>
`M${width / 2},0L${width},${height / 2}L0,${height},Z`,
),
maskHandleNFill: 'blue',
maskHandleEFill: 'red',
maskHandleSFill: 'green',
maskHandleWFill: 'yellow',
maskHandleNWFill: 'black',
maskHandleNEFill: 'steelblue',
maskHandleSEFill: 'pink',
maskHandleSWFill: 'orange',
},
},
};
}

penguinsPointBrushHandleCustom.steps = ({ canvas }) => {
const { document } = canvas;
const plot = document.getElementsByClassName(PLOT_CLASS_NAME)[0];

return [
{
changeState: () => {
brush(plot, 100, 100, 200, 200);
},
},
];
};

penguinsPointBrushHandleCustom.steps = brushSteps;
44 changes: 44 additions & 0 deletions __tests__/plots/interaction/penguins-point-brush-handle-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { G2Spec, PLOT_CLASS_NAME } from '../../../src';
import { brush } from './penguins-point-brush';

export function penguinsPointBrushHandleStyle(): G2Spec {
return {
type: 'point',
data: {
type: 'fetch',
value: 'data/penguins.csv',
},
encode: {
color: 'species',
x: 'culmen_length_mm',
y: 'culmen_depth_mm',
},
state: {
inactive: { stroke: 'gray', opacity: 0.5 },
},
interaction: {
brushHighlight: {
maskHandleNFill: 'blue',
maskHandleEFill: 'red',
maskHandleSFill: 'green',
maskHandleWFill: 'yellow',
maskHandleNWFill: 'black',
maskHandleNEFill: 'steelblue',
maskHandleSEFill: 'pink',
maskHandleSWFill: 'orange',
},
},
};
}

penguinsPointBrushHandleStyle.steps = ({ canvas }) => {
const { document } = canvas;
const plot = document.getElementsByClassName(PLOT_CLASS_NAME)[0];
return [
{
changeState: () => {
brush(plot, 100, 100, 200, 200);
},
},
];
};
62 changes: 44 additions & 18 deletions __tests__/plots/interaction/penguins-point-brush.ts
Original file line number Diff line number Diff line change
@@ -40,21 +40,8 @@ export function brush(plot, x, y, x1, y1) {
}

export function dragMask(plot, x, y, x1, y1) {
const mask = plot.getElementsByClassName('mask')[0];
mask.dispatchEvent(
new CustomEvent('dragstart', {
// @ts-ignore
offsetX: x,
offsetY: y,
}),
);
mask.dispatchEvent(
new CustomEvent('drag', {
// @ts-ignore
offsetX: x1,
offsetY: y1,
}),
);
const mask = plot.getElementById('selection');
drag(mask, x, y, x1, y1);
}

export function dblclick(plot, x = 200, y = 200) {
@@ -76,10 +63,26 @@ export function dblclick(plot, x = 200, y = 200) {
);
}

penguinsPointBrush.steps = ({ canvas }) => {
export function drag(shape, x, y, x1, y1) {
shape.dispatchEvent(
new CustomEvent('dragstart', {
// @ts-ignore
offsetX: x,
offsetY: y,
}),
);
shape.dispatchEvent(
new CustomEvent('drag', {
// @ts-ignore
offsetX: x1,
offsetY: y1,
}),
);
}

export function brushSteps({ canvas }) {
const { document } = canvas;
const plot = document.getElementsByClassName(PLOT_CLASS_NAME)[0];

return [
{
changeState: () => {
@@ -101,5 +104,28 @@ penguinsPointBrush.steps = ({ canvas }) => {
dragMask(plot, 100, 100, 640, 480);
},
},
...resize(plot),
];
};
}

// Origin mask: [490, 330, 640, 480]
export function resize(plot) {
const handles = [
['handle-n', 500, 330, 500, 200], // [490, 200, 640, 480]
['handle-e', 640, 300, 600, 300], // [490, 200, 600, 480]
['handle-s', 500, 480, 500, 300], // [490, 200, 600, 300]
['handle-w', 490, 200, 400, 200], // [400, 200, 500, 300]
['handle-nw', 400, 200, 300, 300], // [300, 300, 500, 300]
['handle-ne', 500, 300, 600, 200], // [300, 200, 600, 300]
['handle-se', 600, 300, 500, 200], // [300, 200, 500, 200]
['handle-sw', 300, 200, 400, 300], // [400, 200, 500, 300]
] as const;
return handles.map(([id, x, y, x1, y1]) => ({
changeState: () => {
const handle = plot.getElementById(id);
drag(handle, x, y, x1, y1);
},
}));
}

penguinsPointBrush.steps = brushSteps;
167 changes: 167 additions & 0 deletions site/docs/spec/interaction/brushHighlight.zh.md
Original file line number Diff line number Diff line change
@@ -40,3 +40,170 @@ chart.render();
| series | 是否是系列元素 | `boolean` | false |
| facet | 是否跨分面 | `boolean` | false |
| `mask${StyleAttrs}` | brush 的样式 | `number\| string` | - |

# Brush

支持八个方向的 resize 和自定义对应的 handle。

## 案例

### 设置样式

八个方向的 handle 的名字分别如下(按照东南西北命名),按照 `mask[handleName][styleAttribute]` 格式设置对应的属性,也可以通过 `maskHandleSize` 设置宽度。

<img src="https://github.com/antvis/G2/assets/49330279/eb2d3951-7990-423c-97f3-e3a38b2baf68" width=640 alt="custom-style"/>

```js
chart.options({
type: 'point',
data: {
type: 'fetch',
value: 'data/penguins.csv',
},
encode: {
color: 'species',
x: 'culmen_length_mm',
y: 'culmen_depth_mm',
},
state: {
inactive: { stroke: 'gray', opacity: 0.5 },
},
interaction: {
brushHighlight: {
maskHandleNFill: 'blue',
maskHandleEFill: 'red',
maskHandleSFill: 'green',
maskHandleWFill: 'yellow',
maskHandleNWFill: 'black',
maskHandleNEFill: 'steelblue',
maskHandleSEFill: 'pink',
maskHandleSWFill: 'orange',
},
},
});
```

### 自定义 Handle

可以通过 `mask[handleName]Render` 指定 handle 的渲染函数,用于渲染自定义的 handle。其中该函数签名如下。

```js
function render(
g, // 挂载容器
options, // 样式属性,通过 mask[handleName][styleAttribute] 设置
document, // 画布 document,用于创建自图形
) {
// 需要返回创建的图形
}
```

下面是一个创建 path handle 的例子:

```js
function renderPath(group, options, document) {
// 创建逻辑
// 如果是第一次渲染,就创建并且挂在图形
if (!group.handle) {
// 通过 document.createElement 去新建图形
const path = document.createElement('path');
group.handle = path;
group.appendChild(group.handle);
}

// 更新逻辑
const { handle } = group;
const { width, height, ...rest } = options;
if (width === undefined || height === undefined) return handle;
handle.attr(rest);

// 返回对应的 shape
return handle;
}
```

<img src="https://github.com/antvis/G2/assets/49330279/d586fabe-4c34-4dfb-bffa-ef1a354b1333" width=640 alt="custom-brush"/>

```js
function createPathRender(path) {
return (group, options, document) => {
if (!group.handle) {
const path = document.createElement('path');
group.handle = path;
group.appendChild(group.handle);
}
const { handle } = group;
const { width, height, ...rest } = options;
if (width === undefined || height === undefined) return handle;
handle.style.d = path(width, height);
handle.attr(rest);
return handle;
};
}

chart.options({
type: 'point',
data: {
type: 'fetch',
value: 'data/penguins.csv',
},
encode: {
color: 'species',
x: 'culmen_length_mm',
y: 'culmen_depth_mm',
},
state: {
inactive: { stroke: 'gray', opacity: 0.5 },
},
interaction: {
brushHighlight: {
maskHandleSize: 30,
maskHandleNRender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width / 2},${-height / 2}L${width},${
height / 2
},Z`,
),
maskHandleERender: createPathRender(
(width, height) =>
`M${width / 2},0L${(width * 3) / 2},${height / 2}L${
width / 2
},${height},Z`,
),
maskHandleSRender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width / 2},${(height / 2) * 3}L${width},${
height / 2
},Z`,
),
maskHandleWRender: createPathRender(
(width, height) =>
`M${width / 2},0L${-width},${height / 2}L${width / 2},${height},Z`,
),
maskHandleNWRender: createPathRender(
(width, height) =>
`M0,0L${width},${height / 2}L${width / 2},${height},Z`,
),
maskHandleNERender: createPathRender(
(width, height) =>
`M0,${height / 2}L${width},0L${width / 2},${height},Z`,
),
maskHandleSERender: createPathRender(
(width, height) =>
`M${width / 2},0L${width},${height}L0,${height / 2},Z`,
),
maskHandleSWRender: createPathRender(
(width, height) =>
`M${width / 2},0L${width},${height / 2}L0,${height},Z`,
),
maskHandleNFill: 'blue',
maskHandleEFill: 'red',
maskHandleSFill: 'green',
maskHandleWFill: 'yellow',
maskHandleNWFill: 'black',
maskHandleNEFill: 'steelblue',
maskHandleSEFill: 'pink',
maskHandleSWFill: 'orange',
},
},
});
```
33 changes: 31 additions & 2 deletions site/examples/interaction/interaction/demo/focus-context.ts
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ const context = new Chart({
paddingLeft: 40,
paddingTop: 0,
paddingBottom: 0,
height: 80,
height: 60,
});

context
@@ -51,10 +51,39 @@ context
.animate(false)
.axis(false)
.interaction('tooltip', false)
.interaction('brushXHighlight', true);
.interaction('brushXHighlight', {
series: true,
maskOpacity: 0.3,
maskFill: '#777',
maskHandleWRender: createPathRender((width, height) => ({
d: 'M-0.5,31.5c-2.5,0,-4.5,2,-4.5,4.5v30c0,2.5,2,4.5,4.5,4.5V31.5z',
transform: `translate(${width / 2}, ${-height / 2})`,
})),
maskHandleERender: createPathRender((width, height) => ({
d: 'M0.5,31.5c2.5,0,4.5,2,4.5,4.5v30c0,2.5,-2,4.5,-4.5,4.5V31.5z',
transform: `translate(${width / 2}, ${-height / 2})`,
})),
maskHandleEFill: '#D3D8E0',
maskHandleWFill: '#D3D8E0',
});

context.render();

function createPathRender(compute) {
return (group, options, document) => {
if (!group.handle) {
const path = document.createElement('path');
group.handle = path;
group.appendChild(group.handle);
}
const { handle } = group;
const { width, height, ...rest } = options;
if (width === undefined || height === undefined) return handle;
handle.attr({ ...compute(width, height), ...rest });
return handle;
};
}

// Add event listeners to communicate.
focus.on('brush:filter', (e) => {
const { nativeEvent } = e;
273 changes: 255 additions & 18 deletions src/interaction/brushHighlight.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/interaction/brushXHighlight.ts
Original file line number Diff line number Diff line change
@@ -9,5 +9,6 @@ export function BrushXHighlight(options) {
return BrushHighlight({
...options,
brushRegion: brushXRegion,
selectedHandles: ['handle-e', 'handle-w'],
});
}
1 change: 1 addition & 0 deletions src/interaction/brushYHighlight.ts
Original file line number Diff line number Diff line change
@@ -9,5 +9,6 @@ export function BrushYHighlight(options) {
return BrushHighlight({
...options,
brushRegion: brushYRegion,
selectedHandles: ['handle-n', 'handle-s'],
});
}
2 changes: 1 addition & 1 deletion src/shape/area/curve.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import {
getTransform,
} from '../utils';
import { subObject } from '../../utils/helper';
import { createElement } from '../createElement';
import { createElement } from '../../utils/createElement';

const DoubleArea = createElement((g) => {
const { areaPath, connectPath, areaStyle, connectStyle } = g.attributes;
2 changes: 1 addition & 1 deletion src/shape/connector/connector.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { PathStyleProps } from '@antv/g';
import { Marker } from '@antv/gui';
import { line as d3line } from 'd3-shape';
import { ShapeComponent as SC, Vector2, WithPrefix } from '../../runtime';
import { createElement } from '../../shape/createElement';
import { createElement } from '../../utils/createElement';
import { isTranspose } from '../../utils/coordinate';
import { subObject } from '../../utils/helper';
import { select } from '../../utils/selection';
2 changes: 1 addition & 1 deletion src/shape/line/curve.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import {
getShapeTheme,
getTransform,
} from '../utils';
import { createElement } from '../createElement';
import { createElement } from '../../utils/createElement';
import { subObject } from '../../utils/helper';
import { angleWithQuadrant, dist, sub } from '../../utils/vector';

2 changes: 1 addition & 1 deletion src/shape/text/advance.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import {
import { Marker } from '@antv/gui';
import { line } from 'd3-shape';
import { WithPrefix } from '../../runtime';
import { createElement } from '../createElement';
import { createElement } from '../../utils/createElement';
import { applyStyle } from '../utils';
import { subObject } from '../../utils/helper';
import { select } from '../../utils/selection';
2 changes: 1 addition & 1 deletion src/shape/text/badge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TextStyleProps, DisplayObject } from '@antv/g';
import { Marker } from '@antv/gui';
import { ShapeComponent as SC, WithPrefix } from '../../runtime';
import { createElement } from '../../shape/createElement';
import { createElement } from '../../utils/createElement';
import { subObject } from '../../utils/helper';
import { select } from '../../utils/selection';
import { applyStyle, getShapeTheme } from '../../shape/utils';
2 changes: 1 addition & 1 deletion src/shape/createElement.ts → src/utils/createElement.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ export type ElementDescriptor = {

export function createElement<T = Record<string, any>>(
descriptor: ElementDescriptor | ElementDescriptor['render'],
): new () => DisplayObject {
): new (T?) => DisplayObject {
const render =
typeof descriptor === 'function' ? descriptor : descriptor.render;
return class extends CustomElement<T> {
11 changes: 11 additions & 0 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -116,6 +116,17 @@ export function filterPrefixObject(
);
}

export function omitPrefixObject(
obj: Record<string, any>,
...prefixes: string[]
) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) =>
prefixes.every((prefix) => !key.startsWith(prefix)),
),
);
}

export function maybePercentage(x: number | string, size: number) {
if (x === undefined) return null;
if (typeof x === 'number') return x;

0 comments on commit 857fa7b

Please sign in to comment.