Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(behavior): hide elements when zooming, scrolling and dragging canvas #6054

Merged
merged 4 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/g6/__tests__/demos/behavior-optimize-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Graph } from '@/src';
import data from '@@/dataset/cluster.json';

export const behaviorOptimizeCanvas: TestCase = async (context) => {
const graph = new Graph({
...context,
data,
layout: {
type: 'd3-force',
},
node: {
style: {
iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
labelFontSize: 8,
labelText: (datum) => datum.id,
size: 20,
},
},
edge: {
style: {
labelFontSize: 8,
labelText: (datum) => datum.id,
},
},
behaviors: ['drag-canvas', 'zoom-canvas', 'scroll-canvas', 'optimize-canvas'],
});

await graph.render();

return graph;
};
1 change: 1 addition & 0 deletions packages/g6/__tests__/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { behaviorExpandCollapseNode } from './behavior-expand-collapse-node';
export { behaviorFocusElement } from './behavior-focus-element';
export { behaviorHoverActivate } from './behavior-hover-activate';
export { behaviorLassoSelect } from './behavior-lasso-select';
export { behaviorOptimizeCanvas } from './behavior-optimize-canvas';
export { behaviorScrollCanvas } from './behavior-scroll-canvas';
export { behaviorZoomCanvas } from './behavior-zoom-canvas';
export { caseIndentedTree } from './case-indented-tree';
Expand Down
1,427 changes: 1,427 additions & 0 deletions packages/g6/__tests__/snapshots/behaviors/optimize-canvas/default.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,427 changes: 1,427 additions & 0 deletions packages/g6/__tests__/snapshots/behaviors/optimize-canvas/viewport-change.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions packages/g6/__tests__/unit/behaviors/optimize-canvas.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { behaviorOptimizeCanvas } from '@/__tests__/demos';
import { GraphEvent, type Graph } from '@/src';
import { createDemoGraph } from '@@/utils';

describe('behavior optimize canvas', () => {
let graph: Graph;

beforeAll(async () => {
graph = await createDemoGraph(behaviorOptimizeCanvas, { animation: false });
});

it('viewport', async () => {
await expect(graph).toMatchSnapshot(__filename);

graph.emit(GraphEvent.BEFORE_TRANSFORM, {
type: GraphEvent.BEFORE_TRANSFORM,
data: {
mode: 'relative',
translate: [0, -3],
},
});
await expect(graph).toMatchSnapshot(__filename, 'viewport-change');
graph.emit(GraphEvent.AFTER_TRANSFORM, {
type: GraphEvent.AFTER_TRANSFORM,
data: {
mode: 'relative',
translate: [0, -1],
},
});
});

it('destroy', () => {
graph.destroy();
});
});
2 changes: 2 additions & 0 deletions packages/g6/src/behaviors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { DragElementForce } from './drag-element-force';
export { FocusElement } from './focus-element';
export { HoverActivate } from './hover-activate';
export { LassoSelect } from './lasso-select';
export { OptimizeCanvas } from './optimize-canvas';
export { ScrollCanvas } from './scroll-canvas';
export { ZoomCanvas } from './zoom-canvas';

Expand All @@ -23,5 +24,6 @@ export type { DragElementForceOptions } from './drag-element-force';
export type { FocusElementOptions } from './focus-element';
export type { HoverActivateOptions } from './hover-activate';
export type { LassoSelectOptions } from './lasso-select';
export type { OptimizeCanvasOptions } from './optimize-canvas';
export type { ScrollCanvasOptions } from './scroll-canvas';
export type { ZoomCanvasOptions } from './zoom-canvas';
135 changes: 135 additions & 0 deletions packages/g6/src/behaviors/optimize-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type { BaseStyleProps, DisplayObject } from '@antv/g';
import { debounce, isFunction } from '@antv/util';
import { GraphEvent } from '../constants';
import type { RuntimeContext } from '../runtime/types';
import type { ViewportEvent } from '../utils/event';
import { setVisibility } from '../utils/visibility';
import type { BaseBehaviorOptions } from './base-behavior';
import { BaseBehavior } from './base-behavior';

/**
* <zh/> 画布优化交互配置项
*
* <en/> Canvas optimization behavior options
*/
export interface OptimizeCanvasOptions extends BaseBehaviorOptions {
/**
* <zh/> 是否启用画布优化功能
*
* <en/> Whether to enable canvas optimization function
* @defaultValue true
*/
enable?: boolean | ((event: ViewportEvent) => boolean);
/**
* <zh/> 始终保留的图形类名。操作画布过程中会隐藏元素reservedShapes(除了指定类名的图形),以提高性能
*
* <en/> Persistently reserved shape classnames. Elements are hidden during canvas manipulation (except for shapes with specified classnames) to enhance performance.
* @defaultValue `{ node: ['key'] }`
*/
shapes?: {
node?: string[];
edge?: string[];
combo?: string[];
};
/**
* <zh/> 设置防抖时间
*
* <en/> Set debounce time
* @defaultValue 200
*/
debounce?: number;
}

/**
* <zh/> 操作画布过程中隐藏元素
*
* <en/> Hide elements during canvas operations (dragging, zooming, scrolling)
*/
export class OptimizeCanvas extends BaseBehavior<OptimizeCanvasOptions> {
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved
static defaultOptions: Partial<OptimizeCanvasOptions> = {
enable: true,
debounce: 200,
shapes: { node: ['key'] },
};

private isVisible: boolean = true;

constructor(context: RuntimeContext, options: OptimizeCanvasOptions) {
super(context, Object.assign({}, OptimizeCanvas.defaultOptions, options));
this.bindEvents();
}

public update(options: Partial<OptimizeCanvasOptions>) {
super.update(options);
this.bindEvents();
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved
}

private filterShapes = (shapes: DisplayObject[], classnames?: string[]) => {
return shapes.filter((shape) => shape.className && !classnames?.includes(shape.className));
};

private setElementsVisibility = (
elements: DisplayObject[],
visibility: BaseStyleProps['visibility'],
excludedClassnames?: string[],
) => {
elements.forEach((element) => {
setVisibility(
element,
visibility,
excludedClassnames && ((shapes) => this.filterShapes(shapes, excludedClassnames)),
);
});
};

private hideShapes = (event: ViewportEvent) => {
if (!this.validate(event) || !this.isVisible) return;
console.log('before event', event);
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved

const { element } = this.context;
const { shapes = {} } = this.options;
this.setElementsVisibility(element!.getNodes(), 'hidden', shapes.node);
this.setElementsVisibility(element!.getEdges(), 'hidden', shapes.edge);
this.setElementsVisibility(element!.getCombos(), 'hidden', shapes.combo);
this.isVisible = false;
};

private showShapes = debounce((event: ViewportEvent) => {
if (!this.validate(event) || this.isVisible) return;
console.log('after event', event);
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved

const { element } = this.context;
this.setElementsVisibility(element!.getNodes(), 'visible');
this.setElementsVisibility(element!.getEdges(), 'visible');
this.setElementsVisibility(element!.getCombos(), 'visible');
this.isVisible = true;
}, this.options.debounce);

private bindEvents() {
const { graph } = this.context;
this.unbindEvents();
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved

graph.on(GraphEvent.BEFORE_TRANSFORM, this.hideShapes);
graph.on(GraphEvent.AFTER_TRANSFORM, this.showShapes);
}

private unbindEvents() {
const { graph } = this.context;

graph.off(GraphEvent.BEFORE_TRANSFORM, this.hideShapes);
graph.off(GraphEvent.AFTER_TRANSFORM, this.showShapes);
}

private validate(event: ViewportEvent) {
if (this.destroyed) return false;

const { enable } = this.options;
if (isFunction(enable)) return enable(event);
return !!enable;
}

public destroy() {
this.unbindEvents();
super.destroy();
}
}
18 changes: 10 additions & 8 deletions packages/g6/src/registry/build-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
FocusElement,
HoverActivate,
LassoSelect,
OptimizeCanvas,
ScrollCanvas,
ZoomCanvas,
} from '../behaviors';
Expand Down Expand Up @@ -94,18 +95,19 @@ export const BUILT_IN_EXTENSIONS: ExtensionRegistry = {
translate: Translate,
},
behavior: {
'zoom-canvas': ZoomCanvas,
'brush-select': BrushSelect,
'click-select': ClickSelect,
'collapse-expand': CollapseExpand,
'create-edge': CreateEdge,
'drag-canvas': DragCanvas,
'drag-element': DragElement,
'drag-element-force': DragElementForce,
'scroll-canvas': ScrollCanvas,
'collapse-expand': CollapseExpand,
'click-select': ClickSelect,
'hover-activate': HoverActivate,
'drag-element': DragElement,
'focus-element': FocusElement,
'create-edge': CreateEdge,
'brush-select': BrushSelect,
'hover-activate': HoverActivate,
'lasso-select': LassoSelect,
'optimize-canvas': OptimizeCanvas,
'scroll-canvas': ScrollCanvas,
'zoom-canvas': ZoomCanvas,
},
combo: {
circle: CircleCombo,
Expand Down
11 changes: 9 additions & 2 deletions packages/g6/src/utils/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ const PropertyKey = 'visibility';
* <en/> Set the visibility of the shape instance
* @param shape - <zh/> 图形实例 | <en/> shape instance
* @param visibility - <zh/> 可见性 | <en/> visibility
* @param filter - <zh/> 筛选出需要设置可见性的图形 | <en/> Filter out the shapes that need to set visibility
* @remarks
* <zh/> 在设置 enableCSSParsing 为 false 的情况下,复合图形无法继承父属性,因此需要对所有子图形应用相同的可见性
*
* <en/> After setting enableCSSParsing to false, the compound shape cannot inherit the parent attribute, so the same visibility needs to be applied to all child shapes
*/
export function setVisibility(shape: DisplayObject, visibility: BaseStyleProps['visibility']) {
const shapes = [shape, ...getDescendantShapes(shape)];
export function setVisibility(
shape: DisplayObject,
visibility: BaseStyleProps['visibility'],
filter?: (shapes: DisplayObject[]) => DisplayObject[],
) {
let shapes = [shape, ...getDescendantShapes(shape)];

if (filter) shapes = filter?.(shapes);

shapes.forEach((sp) => {
if (!hasCachedStyle(sp, PropertyKey)) cacheStyle(sp, PropertyKey);
Expand Down
2 changes: 1 addition & 1 deletion packages/site/.dumirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineConfig({
content: 'A Graph Visualization Framework in JavaScript',
},
],
mako: {},
// mako: {},
yvonneyx marked this conversation as resolved.
Show resolved Hide resolved
themeConfig: {
title: 'G6',
description: 'A Graph Visualization Framework in JavaScript',
Expand Down
16 changes: 16 additions & 0 deletions packages/site/examples/behavior/canvas/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@
"en": "Scroll Y"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*c5DCSoAYsZQAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "zoom.js",
"title": {
"zh": "缩放画布",
"en": "Zoom Canvas"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1AXoRY9Bw-0AAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "optimize.js",
"title": {
"zh": "拖拽缩放画布时隐藏元素",
"en": "Hide Elements When Dragging and Zooming"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*AZ4IRJkIZr8AAAAAAAAAAAAADmJ7AQ/original"
}
]
}
27 changes: 27 additions & 0 deletions packages/site/examples/behavior/canvas/demo/optimize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Graph } from '@antv/g6';

const graph = new Graph({
container: 'container',
layout: {
type: 'grid',
},
data: {
nodes: [{ id: 'node1' }, { id: 'node2' }, { id: 'node3' }, { id: 'node4' }, { id: 'node5' }],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node1', target: 'node4' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4' },
{ source: 'node4', target: 'node5' },
],
},
node: {
style: {
labelText: (datum) => datum.id,
},
},
behaviors: ['zoom-canvas', 'drag-canvas', 'scroll-canvas', 'optimize-canvas'],
});

graph.render();
22 changes: 22 additions & 0 deletions packages/site/examples/behavior/canvas/demo/zoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Graph } from '@antv/g6';

const graph = new Graph({
container: 'container',
layout: {
type: 'grid',
},
data: {
nodes: [{ id: 'node1' }, { id: 'node2' }, { id: 'node3' }, { id: 'node4' }, { id: 'node5' }],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node1', target: 'node4' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4' },
{ source: 'node4', target: 'node5' },
],
},
behaviors: ['zoom-canvas'],
});

graph.render();
Loading