From d0c92d26b23d16aee9e0c364a2166fe997043f00 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 12 Mar 2024 10:49:12 +0800 Subject: [PATCH] refactor: adjust element controller (#5513) * refactor(runtime): element split draw to draw and computeDrawData * feat(utils): add parentIdOf util * refactor(runtime): element controller use data flow * refactor(utils): toGraphlibData does a shallow copy * refactor: base-shape set visibility, setVisibility support keep raw attributes * refactor(runtime): refactor element controller, merge runtime style into model * test: update test case and snapshots * test(elements): add test case and snapshots * chore(site): update site demo * refactor(runtime): add syntax sugar for runtime api * test: remove layout case demo * refactor(animation): recover to enter, exit animation stage * refactor(runtime): add frontElement, backElement api * refactor(runtime): add showElement, hideElement API * refactor(behaviors): drag-node adpat new api --- .../__tests__/demo/case/element-position.ts | 55 ++ .../g6/__tests__/demo/case/element-state.ts | 87 +++ .../__tests__/demo/case/element-visibility.ts | 44 ++ packages/g6/__tests__/demo/case/index.ts | 3 + .../static/controller-element-visibility.ts | 4 +- .../demo/static/controller-element-z-index.ts | 8 +- .../demo/static/edge-cubic-horizontal.ts | 10 +- .../demo/static/edge-cubic-vertical.ts | 10 +- .../g6/__tests__/demo/static/edge-cubic.ts | 10 +- .../g6/__tests__/demo/static/edge-line.ts | 10 +- .../__tests__/demo/static/edge-quadratic.ts | 10 +- .../g6/__tests__/demo/static/node-circle.ts | 12 +- .../g6/__tests__/demo/static/node-diamond.ts | 13 +- .../g6/__tests__/demo/static/node-ellipse.ts | 12 +- .../g6/__tests__/demo/static/node-image.ts | 13 +- .../g6/__tests__/demo/static/node-rect.ts | 12 +- .../g6/__tests__/demo/static/node-star.ts | 12 +- .../g6/__tests__/demo/static/node-triangle.ts | 11 +- .../behavior-drag-node__after-drag.svg | 10 +- .../behavior-drag-node__hideEdges-both.svg | 10 +- .../behavior-drag-node__hideEdges-in.svg | 10 +- .../behavior-drag-node__hideEdges-out.svg | 10 +- .../behavior-drag-node__shadow-after-drag.svg | 10 +- .../behavior-drag-node__shadow.svg | 10 +- .../snapshots/elements/position/position.svg | 157 +++++ .../position__translateElementBy-single.svg | 157 +++++ .../position/position__translateElementBy.svg | 157 +++++ .../position__translateElementTo-single.svg | 157 +++++ .../position/position__translateElementTo.svg | 157 +++++ .../snapshots/elements/state/state.svg | 209 ++++++ .../state/state__setState-single-default.svg | 209 ++++++ .../elements/state/state__setState-single.svg | 224 ++++++ .../elements/state/state__setState.svg | 224 ++++++ .../elements/visibility/visibility.svg | 440 ++++++++++++ .../visibility/visibility__hide-single.svg | 467 +++++++++++++ .../elements/visibility/visibility__hide.svg | 467 +++++++++++++ .../visibility/visibility__show-and-hide.svg | 467 +++++++++++++ .../visibility/visibility__show-single.svg | 467 +++++++++++++ .../elements/visibility/visibility__show.svg | 467 +++++++++++++ .../__tests__/unit/elements/position.spec.ts | 43 ++ .../g6/__tests__/unit/elements/state.spec.ts | 34 + .../unit/elements/visibility.spec.ts | 46 ++ .../g6/__tests__/unit/runtime/data.spec.ts | 135 ++-- .../g6/__tests__/unit/runtime/element.spec.ts | 34 +- .../unit/runtime/element/visibility.spec.ts | 4 +- .../unit/runtime/element/z-index.spec.ts | 6 +- .../unit/runtime/graph/graph.spec.ts | 67 +- .../g6/__tests__/unit/utils/cache.spec.ts | 6 +- .../g6/__tests__/unit/utils/graphlib.spec.ts | 27 +- packages/g6/__tests__/unit/utils/id.spec.ts | 7 +- .../g6/__tests__/unit/utils/layout.spec.ts | 47 +- .../__tests__/unit/utils/visibility.spec.ts | 50 ++ packages/g6/src/behaviors/drag-node.ts | 19 +- packages/g6/src/elements/shapes/base-shape.ts | 10 +- packages/g6/src/runtime/data.ts | 33 +- packages/g6/src/runtime/element.ts | 639 ++++++------------ packages/g6/src/runtime/graph.ts | 311 +++++++-- packages/g6/src/runtime/layout.ts | 35 +- packages/g6/src/spec/element/animation.ts | 2 +- packages/g6/src/themes/dark.ts | 9 +- packages/g6/src/themes/light.ts | 9 +- packages/g6/src/types/index.ts | 2 - packages/g6/src/types/layout.ts | 7 - packages/g6/src/types/position.ts | 3 - packages/g6/src/types/state.ts | 4 - packages/g6/src/types/z-index.ts | 1 - packages/g6/src/utils/cache.ts | 27 +- packages/g6/src/utils/event/events.ts | 35 +- packages/g6/src/utils/graphlib.ts | 14 +- packages/g6/src/utils/id.ts | 13 +- packages/g6/src/utils/layout.ts | 38 -- packages/g6/src/utils/visibility.ts | 18 +- .../examples/item/defaultEdges/demo/cubic.ts | 10 +- .../item/defaultEdges/demo/horizontalCubic.ts | 10 +- .../examples/item/defaultEdges/demo/line.ts | 10 +- .../item/defaultEdges/demo/quadratic.ts | 10 +- .../item/defaultEdges/demo/verticalCubic.ts | 10 +- .../examples/item/defaultNodes/demo/circle.ts | 12 +- .../item/defaultNodes/demo/diamond.ts | 12 +- .../item/defaultNodes/demo/ellipse.ts | 12 +- .../examples/item/defaultNodes/demo/image.ts | 12 +- .../item/defaultNodes/demo/radiusRect.ts | 12 +- .../examples/item/defaultNodes/demo/rect.ts | 12 +- .../examples/item/defaultNodes/demo/star.ts | 12 +- .../item/defaultNodes/demo/triangle.ts | 12 +- .../examples/item/label/demo/copyLabel.ts | 8 +- 86 files changed, 5795 insertions(+), 916 deletions(-) create mode 100644 packages/g6/__tests__/demo/case/element-position.ts create mode 100644 packages/g6/__tests__/demo/case/element-state.ts create mode 100644 packages/g6/__tests__/demo/case/element-visibility.ts create mode 100644 packages/g6/__tests__/snapshots/elements/position/position.svg create mode 100644 packages/g6/__tests__/snapshots/elements/position/position__translateElementBy-single.svg create mode 100644 packages/g6/__tests__/snapshots/elements/position/position__translateElementBy.svg create mode 100644 packages/g6/__tests__/snapshots/elements/position/position__translateElementTo-single.svg create mode 100644 packages/g6/__tests__/snapshots/elements/position/position__translateElementTo.svg create mode 100644 packages/g6/__tests__/snapshots/elements/state/state.svg create mode 100644 packages/g6/__tests__/snapshots/elements/state/state__setState-single-default.svg create mode 100644 packages/g6/__tests__/snapshots/elements/state/state__setState-single.svg create mode 100644 packages/g6/__tests__/snapshots/elements/state/state__setState.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility__hide-single.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility__hide.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility__show-and-hide.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility__show-single.svg create mode 100644 packages/g6/__tests__/snapshots/elements/visibility/visibility__show.svg create mode 100644 packages/g6/__tests__/unit/elements/position.spec.ts create mode 100644 packages/g6/__tests__/unit/elements/state.spec.ts create mode 100644 packages/g6/__tests__/unit/elements/visibility.spec.ts create mode 100644 packages/g6/__tests__/unit/utils/visibility.spec.ts delete mode 100644 packages/g6/src/types/layout.ts delete mode 100644 packages/g6/src/types/z-index.ts diff --git a/packages/g6/__tests__/demo/case/element-position.ts b/packages/g6/__tests__/demo/case/element-position.ts new file mode 100644 index 00000000000..9a9e75590cd --- /dev/null +++ b/packages/g6/__tests__/demo/case/element-position.ts @@ -0,0 +1,55 @@ +import { Graph } from '@/src'; +import type { STDTestCase } from '../types'; + +export const elementPosition: STDTestCase = async (context) => { + const graph = new Graph({ + ...context, + data: { + nodes: [ + { id: 'node-1', style: { x: 50, y: 50 } }, + { id: 'node-2', style: { x: 200, y: 50 } }, + { id: 'node-3', style: { x: 125, y: 150 } }, + ], + edges: [ + { id: 'edge-1', source: 'node-1', target: 'node-2' }, + { id: 'edge-2', source: 'node-2', target: 'node-3' }, + { id: 'edge-3', source: 'node-3', target: 'node-1' }, + ], + }, + node: { + style: { + size: 20, + }, + }, + }); + + await graph.render(); + + elementPosition.form = (panel) => { + const config = { + element: 'node-1', + x: 50, + y: 50, + }; + + const translate = () => { + graph.translateElementTo( + { + [config.element]: [config.x, config.y], + }, + false, + ); + }; + + const element = panel.add(config, 'element', ['node-1', 'node-2', 'node-3']).onChange((id: string) => { + const position = graph.getElementPosition(id); + x.setValue(position[0]); + y.setValue(position[1]); + }); + const x = panel.add(config, 'x', 0, 300, 1).onChange(translate); + const y = panel.add(config, 'y', 0, 300, 1).onChange(translate); + return [element, x, y]; + }; + + return graph; +}; diff --git a/packages/g6/__tests__/demo/case/element-state.ts b/packages/g6/__tests__/demo/case/element-state.ts new file mode 100644 index 00000000000..690f7c33f10 --- /dev/null +++ b/packages/g6/__tests__/demo/case/element-state.ts @@ -0,0 +1,87 @@ +import { Graph } from '@/src'; +import type { STDTestCase } from '../types'; + +export const elementState: STDTestCase = async (context) => { + const graph = new Graph({ + ...context, + data: { + nodes: [ + { id: 'node-1', style: { x: 50, y: 50, states: ['active', 'selected'] } }, + { id: 'node-2', style: { x: 200, y: 50 } }, + { id: 'node-3', style: { x: 125, y: 150, states: ['active'] } }, + ], + edges: [ + { id: 'edge-1', source: 'node-1', target: 'node-2', style: { states: ['active'] } }, + { id: 'edge-2', source: 'node-2', target: 'node-3' }, + { id: 'edge-3', source: 'node-3', target: 'node-1' }, + ], + }, + theme: 'light', + node: { + style: { + lineWidth: 1, + size: 20, + }, + state: { + active: { + lineWidth: 2, + }, + selected: { + fill: 'pink', + }, + }, + animation: { + update: [{ fields: ['lineWidth', 'fill'] }], + }, + }, + edge: { + style: { + lineWidth: 1, + }, + state: { + active: { + lineWidth: 2, + stroke: 'pink', + }, + }, + animation: { + update: [ + { + fields: ['lineWidth', 'stroke'], + }, + ], + }, + }, + }); + + await graph.render(); + + elementState.form = (panel) => { + const config = { + element: 'node-1', + active: true, + selected: true, + }; + + const setState = () => { + const state: string[] = []; + if (config.active) state.push('active'); + if (config.selected) state.push('selected'); + graph.setElementState({ [config.element]: state }); + }; + + const element = panel + .add(config, 'element', ['node-1', 'node-2', 'node-3', 'edge-1', 'edge-2', 'edge-3']) + .onChange((id: string) => { + const states = graph.getElementState(id); + selected.setValue(states.includes('selected')); + active.setValue(states.includes('active')); + }); + const active = panel.add(config, 'active').onChange(setState); + const selected = panel.add(config, 'selected').onChange(setState); + + return [element, active, selected]; + }; + + return graph; +}; diff --git a/packages/g6/__tests__/demo/case/element-visibility.ts b/packages/g6/__tests__/demo/case/element-visibility.ts new file mode 100644 index 00000000000..5115938c933 --- /dev/null +++ b/packages/g6/__tests__/demo/case/element-visibility.ts @@ -0,0 +1,44 @@ +import { Graph } from '@/src'; +import type { STDTestCase } from '../types'; + +export const elementVisibility: STDTestCase = async (context) => { + const graph = new Graph({ + ...context, + data: { + nodes: [ + { id: 'node-1', style: { x: 50, y: 50 } }, + { id: 'node-2', style: { x: 200, y: 50 } }, + { id: 'node-3', style: { x: 125, y: 150 } }, + { id: 'node-4', style: { x: 125, y: 200, visibility: 'hidden' } }, + ], + edges: [ + { id: 'edge-1', source: 'node-1', target: 'node-2' }, + { id: 'edge-2', source: 'node-2', target: 'node-3' }, + { id: 'edge-3', source: 'node-3', target: 'node-1' }, + ], + }, + theme: 'light', + node: { style: { size: 20, labelText: (d: any) => d.id.at(-1) } }, + edge: { style: { endArrow: true, labelText: (d: any) => d.id } }, + }); + + await graph.render(); + + elementVisibility.form = (panel) => { + const config = { + element: 'node-1', + visible: true, + }; + const element = panel + .add(config, 'element', ['node-1', 'node-2', 'node-3', 'node-4', 'edge-1', 'edge-2', 'edge-3']) + .onChange((id: string) => { + visible.setValue(graph.getElementVisibility(id) !== 'hidden'); + }); + const visible = panel.add(config, 'visible').onChange((value: boolean) => { + value ? graph.showElement(config.element) : graph.hideElement(config.element); + }); + return [element, visible]; + }; + + return graph; +}; diff --git a/packages/g6/__tests__/demo/case/index.ts b/packages/g6/__tests__/demo/case/index.ts index a4f14e890e5..d26243ecb34 100644 --- a/packages/g6/__tests__/demo/case/index.ts +++ b/packages/g6/__tests__/demo/case/index.ts @@ -4,6 +4,9 @@ export * from './behavior-zoom-canvas'; export * from './combo'; export * from './common-graph'; export * from './element-change-type'; +export * from './element-position'; +export * from './element-state'; +export * from './element-visibility'; export * from './graph-event'; export * from './layout-grid'; export * from './layout-mds'; diff --git a/packages/g6/__tests__/demo/static/controller-element-visibility.ts b/packages/g6/__tests__/demo/static/controller-element-visibility.ts index e72b451a3be..e233b445ca7 100644 --- a/packages/g6/__tests__/demo/static/controller-element-visibility.ts +++ b/packages/g6/__tests__/demo/static/controller-element-visibility.ts @@ -28,8 +28,8 @@ export const controllerElementVisibility: STDTestCase = async (context) => { const graph = new Graph(options); await graph.render(); - const hide = () => graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'hidden'); - const show = () => graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'visible'); + const show = () => graph.showElement(['node-3', 'node-2-node-3', 'node-3-node-1']); + const hide = () => graph.hideElement(['node-3', 'node-2-node-3', 'node-3-node-1']); controllerElementVisibility.form = (panel) => [panel.add({ show }, 'show'), panel.add({ hide }, 'hide')]; diff --git a/packages/g6/__tests__/demo/static/controller-element-z-index.ts b/packages/g6/__tests__/demo/static/controller-element-z-index.ts index ea5acc973f1..ca0a732e4fd 100644 --- a/packages/g6/__tests__/demo/static/controller-element-z-index.ts +++ b/packages/g6/__tests__/demo/static/controller-element-z-index.ts @@ -22,13 +22,13 @@ export const controllerElementZIndex: STDTestCase = async (context) => { const graph = new Graph(options); await graph.render(); - const front = () => graph.setElementZIndex('node-2', 'front'); - const back = () => graph.setElementZIndex('node-2', 'back'); + const front = () => graph.frontElement('node-2'); + const back = () => graph.backElement('node-2'); const to = (zIndex: number) => graph.setElementZIndex('node-2', zIndex); controllerElementZIndex.form = (panel) => [ - panel.add({ front }, 'front'), - panel.add({ back }, 'back'), + panel.add({ front }, 'front').name('Bring Element To Front'), + panel.add({ back }, 'back').name('Send Element To Back'), panel.add({ to: 0 }, 'to', -10, 10, 1).onChange((zIndex: number) => to(zIndex)), ]; diff --git a/packages/g6/__tests__/demo/static/edge-cubic-horizontal.ts b/packages/g6/__tests__/demo/static/edge-cubic-horizontal.ts index 5c2e4b59d75..b86352f49b7 100644 --- a/packages/g6/__tests__/demo/static/edge-cubic-horizontal.ts +++ b/packages/g6/__tests__/demo/static/edge-cubic-horizontal.ts @@ -64,8 +64,10 @@ export const edgeCubicHorizontal: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/demo/static/edge-cubic-vertical.ts b/packages/g6/__tests__/demo/static/edge-cubic-vertical.ts index ad1f3934965..5b881c79313 100644 --- a/packages/g6/__tests__/demo/static/edge-cubic-vertical.ts +++ b/packages/g6/__tests__/demo/static/edge-cubic-vertical.ts @@ -64,8 +64,10 @@ export const edgeCubicVertical: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/demo/static/edge-cubic.ts b/packages/g6/__tests__/demo/static/edge-cubic.ts index 41f7b75c685..c125032d786 100644 --- a/packages/g6/__tests__/demo/static/edge-cubic.ts +++ b/packages/g6/__tests__/demo/static/edge-cubic.ts @@ -56,8 +56,10 @@ export const edgeCubic: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/demo/static/edge-line.ts b/packages/g6/__tests__/demo/static/edge-line.ts index 3af40eab06d..688343b671e 100644 --- a/packages/g6/__tests__/demo/static/edge-line.ts +++ b/packages/g6/__tests__/demo/static/edge-line.ts @@ -56,8 +56,10 @@ export const edgeLine: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/demo/static/edge-quadratic.ts b/packages/g6/__tests__/demo/static/edge-quadratic.ts index b6be722bc68..1b6fb5655ad 100644 --- a/packages/g6/__tests__/demo/static/edge-quadratic.ts +++ b/packages/g6/__tests__/demo/static/edge-quadratic.ts @@ -56,8 +56,10 @@ export const edgeQuadratic: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-circle.ts b/packages/g6/__tests__/demo/static/node-circle.ts index d7ea9328b79..089d19f4471 100644 --- a/packages/g6/__tests__/demo/static/node-circle.ts +++ b/packages/g6/__tests__/demo/static/node-circle.ts @@ -57,9 +57,11 @@ export const nodeCircle: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('circle-active', 'active'); - graph.setElementState('circle-selected', 'selected'); - graph.setElementState('circle-highlight', 'highlight'); - graph.setElementState('circle-inactive', 'inactive'); - graph.setElementState('circle-disabled', 'disabled'); + graph.setElementState({ + 'circle-active': 'active', + 'circle-selected': 'selected', + 'circle-highlight': 'highlight', + 'circle-inactive': 'inactive', + 'circle-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-diamond.ts b/packages/g6/__tests__/demo/static/node-diamond.ts index 76ca48fcef9..adcba8cecc2 100644 --- a/packages/g6/__tests__/demo/static/node-diamond.ts +++ b/packages/g6/__tests__/demo/static/node-diamond.ts @@ -54,9 +54,12 @@ export const nodeDiamond: StaticTestCase = async (context) => { animation, }); await graph.render(); - graph.setElementState('diamond-active', 'active'); - graph.setElementState('diamond-selected', 'selected'); - graph.setElementState('diamond-highlight', 'highlight'); - graph.setElementState('diamond-inactive', 'inactive'); - graph.setElementState('diamond-disabled', 'disabled'); + + graph.setElementState({ + 'diamond-active': 'active', + 'diamond-selected': 'selected', + 'diamond-highlight': 'highlight', + 'diamond-inactive': 'inactive', + 'diamond-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-ellipse.ts b/packages/g6/__tests__/demo/static/node-ellipse.ts index 4d33f085ec0..cf9deb28240 100644 --- a/packages/g6/__tests__/demo/static/node-ellipse.ts +++ b/packages/g6/__tests__/demo/static/node-ellipse.ts @@ -57,9 +57,11 @@ export const nodeEllipse: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('ellipse-active', 'active'); - graph.setElementState('ellipse-selected', 'selected'); - graph.setElementState('ellipse-highlight', 'highlight'); - graph.setElementState('ellipse-inactive', 'inactive'); - graph.setElementState('ellipse-disabled', 'disabled'); + graph.setElementState({ + 'ellipse-active': 'active', + 'ellipse-selected': 'selected', + 'ellipse-highlight': 'highlight', + 'ellipse-inactive': 'inactive', + 'ellipse-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-image.ts b/packages/g6/__tests__/demo/static/node-image.ts index 569e3565b9e..2c33eda89e2 100644 --- a/packages/g6/__tests__/demo/static/node-image.ts +++ b/packages/g6/__tests__/demo/static/node-image.ts @@ -60,9 +60,12 @@ export const nodeImage: StaticTestCase = async (context) => { }); await graph.render(); - graph.setElementState('image-active', 'active'); - graph.setElementState('image-selected', 'selected'); - graph.setElementState('image-highlight', 'highlight'); - graph.setElementState('image-inactive', 'inactive'); - graph.setElementState('image-disabled', 'disabled'); + + graph.setElementState({ + 'image-active': 'active', + 'image-selected': 'selected', + 'image-highlight': 'highlight', + 'image-inactive': 'inactive', + 'image-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-rect.ts b/packages/g6/__tests__/demo/static/node-rect.ts index f5678a757ae..b179074f22a 100644 --- a/packages/g6/__tests__/demo/static/node-rect.ts +++ b/packages/g6/__tests__/demo/static/node-rect.ts @@ -58,9 +58,11 @@ export const nodeRect: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('rect-active', 'active'); - graph.setElementState('rect-selected', 'selected'); - graph.setElementState('rect-highlight', 'highlight'); - graph.setElementState('rect-inactive', 'inactive'); - graph.setElementState('rect-disabled', 'disabled'); + graph.setElementState({ + 'rect-active': 'active', + 'rect-selected': 'selected', + 'rect-highlight': 'highlight', + 'rect-inactive': 'inactive', + 'rect-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-star.ts b/packages/g6/__tests__/demo/static/node-star.ts index 79000fe8763..20ab7bfa879 100644 --- a/packages/g6/__tests__/demo/static/node-star.ts +++ b/packages/g6/__tests__/demo/static/node-star.ts @@ -55,9 +55,11 @@ export const nodeStar: StaticTestCase = async (context) => { await graph.render(); - graph.setElementState('star-active', 'active'); - graph.setElementState('star-selected', 'selected'); - graph.setElementState('star-highlight', 'highlight'); - graph.setElementState('star-inactive', 'inactive'); - graph.setElementState('star-disabled', 'disabled'); + graph.setElementState({ + 'star-active': 'active', + 'star-selected': 'selected', + 'star-highlight': 'highlight', + 'star-inactive': 'inactive', + 'star-disabled': 'disabled', + }); }; diff --git a/packages/g6/__tests__/demo/static/node-triangle.ts b/packages/g6/__tests__/demo/static/node-triangle.ts index fb8f10a7763..0c174aefbd8 100644 --- a/packages/g6/__tests__/demo/static/node-triangle.ts +++ b/packages/g6/__tests__/demo/static/node-triangle.ts @@ -52,8 +52,11 @@ export const nodeTriangle: StaticTestCase = async (context) => { }); await graph.render(); - graph.setElementState('triangle-active', 'active'); - graph.setElementState('triangle-selected', 'selected'); - graph.setElementState('triangle-highlight', 'highlight'); - graph.setElementState('triangle-inactive', 'inactive'); + + graph.setElementState({ + 'triangle-active': 'active', + 'triangle-selected': 'selected', + 'triangle-highlight': 'highlight', + 'triangle-inactive': 'inactive', + }); }; diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__after-drag.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__after-drag.svg index f92a645e649..92d41906146 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__after-drag.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__after-drag.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-both.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-both.svg index 74af9182bf0..fddc9b3fa0d 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-both.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-both.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-in.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-in.svg index 32df156c58f..79a068e398a 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-in.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-in.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-out.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-out.svg index e6e5540904e..baa3bed253d 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-out.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__hideEdges-out.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow-after-drag.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow-after-drag.svg index 8a1787c0534..fcd81e08c7a 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow-after-drag.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow-after-drag.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow.svg b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow.svg index b931b03efd6..60d53642ddc 100644 --- a/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow.svg +++ b/packages/g6/__tests__/snapshots/behaviors/behavior-drag-node/behavior-drag-node__shadow.svg @@ -10,18 +10,18 @@ - + diff --git a/packages/g6/__tests__/snapshots/elements/position/position.svg b/packages/g6/__tests__/snapshots/elements/position/position.svg new file mode 100644 index 00000000000..da08131c1bb --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/position/position.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy-single.svg b/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy-single.svg new file mode 100644 index 00000000000..04ad081bf0d --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy-single.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy.svg b/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy.svg new file mode 100644 index 00000000000..9eb6246546e --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/position/position__translateElementBy.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo-single.svg b/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo-single.svg new file mode 100644 index 00000000000..decccfc086f --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo-single.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo.svg b/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo.svg new file mode 100644 index 00000000000..bbec3fbd7ef --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/position/position__translateElementTo.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/state/state.svg b/packages/g6/__tests__/snapshots/elements/state/state.svg new file mode 100644 index 00000000000..7797c28fff2 --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/state/state.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/state/state__setState-single-default.svg b/packages/g6/__tests__/snapshots/elements/state/state__setState-single-default.svg new file mode 100644 index 00000000000..8cb9939eaef --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/state/state__setState-single-default.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/state/state__setState-single.svg b/packages/g6/__tests__/snapshots/elements/state/state__setState-single.svg new file mode 100644 index 00000000000..5202453275c --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/state/state__setState-single.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/state/state__setState.svg b/packages/g6/__tests__/snapshots/elements/state/state__setState.svg new file mode 100644 index 00000000000..17a2ecddde7 --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/state/state__setState.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility.svg new file mode 100644 index 00000000000..644218fe46e --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility.svg @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide-single.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide-single.svg new file mode 100644 index 00000000000..a9a4050acba --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide-single.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide.svg new file mode 100644 index 00000000000..47075a6a780 --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility__hide.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-and-hide.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-and-hide.svg new file mode 100644 index 00000000000..a9a4050acba --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-and-hide.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-single.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-single.svg new file mode 100644 index 00000000000..d82241a3f7f --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show-single.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/snapshots/elements/visibility/visibility__show.svg b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show.svg new file mode 100644 index 00000000000..d6b2d040c83 --- /dev/null +++ b/packages/g6/__tests__/snapshots/elements/visibility/visibility__show.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + edge-1 + + + + + + + + + + + + + + + + + + + + + edge-2 + + + + + + + + + + + + + + + + + + + + + edge-3 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + 4 + + + + + + + + \ No newline at end of file diff --git a/packages/g6/__tests__/unit/elements/position.spec.ts b/packages/g6/__tests__/unit/elements/position.spec.ts new file mode 100644 index 00000000000..38affc2ba1d --- /dev/null +++ b/packages/g6/__tests__/unit/elements/position.spec.ts @@ -0,0 +1,43 @@ +import type { Graph } from '@/src'; +import { elementPosition } from '@@/demo/case'; +import { createDemoGraph } from '@@/utils'; + +describe('element position', () => { + let graph: Graph; + + beforeAll(async () => { + graph = await createDemoGraph(elementPosition, { animation: false }); + }); + + it('default status', async () => { + await expect(graph.getCanvas()).toMatchSnapshot(__filename); + }); + + it('translateElementTo', async () => { + await graph.translateElementTo({ + 'node-1': [125, 100], + 'node-2': [125, 100], + 'node-3': [125, 100], + }); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementTo'); + }); + + it('translateElementBy', async () => { + await graph.translateElementBy({ + 'node-1': [-50, -50], + 'node-2': [+50, -50], + 'node-3': [0, +50], + }); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementBy'); + }); + + it('translateElementTo single api', async () => { + graph.translateElementTo('node-1', [50, 50]); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementTo-single'); + }); + + it('translateElementBy single api', async () => { + graph.translateElementBy('node-1', [50, 50]); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementBy-single'); + }); +}); diff --git a/packages/g6/__tests__/unit/elements/state.spec.ts b/packages/g6/__tests__/unit/elements/state.spec.ts new file mode 100644 index 00000000000..75487d07e69 --- /dev/null +++ b/packages/g6/__tests__/unit/elements/state.spec.ts @@ -0,0 +1,34 @@ +import type { Graph } from '@/src'; +import { elementState } from '@@/demo/case'; +import { createDemoGraph } from '@@/utils'; + +describe('element state', () => { + let graph: Graph; + + beforeAll(async () => { + graph = await createDemoGraph(elementState, { animation: false }); + }); + + it('default status', async () => { + await expect(graph.getCanvas()).toMatchSnapshot(__filename); + }); + + it('set state', async () => { + graph.setElementState({ + 'node-1': ['active'], + 'node-2': ['selected'], + 'edge-1': [], + 'edge-2': ['active'], + }); + + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState'); + }); + + it('set state single api', async () => { + graph.setElementState('node-1', ['selected']); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState-single'); + + graph.setElementState('node-1', []); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState-single-default'); + }); +}); diff --git a/packages/g6/__tests__/unit/elements/visibility.spec.ts b/packages/g6/__tests__/unit/elements/visibility.spec.ts new file mode 100644 index 00000000000..806c7fcf96e --- /dev/null +++ b/packages/g6/__tests__/unit/elements/visibility.spec.ts @@ -0,0 +1,46 @@ +import type { Graph } from '@/src'; +import { elementVisibility } from '@@/demo/case'; +import { createDemoGraph } from '@@/utils'; + +describe('element visibility', () => { + let graph: Graph; + + beforeAll(async () => { + graph = await createDemoGraph(elementVisibility, { animation: false }); + }); + + it('default status', async () => { + await expect(graph.getCanvas()).toMatchSnapshot(__filename); + }); + + it('hide', async () => { + await graph.hideElement(['node-1', 'node-2', 'node-3', 'edge-1', 'edge-2', 'edge-3']); + + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__hide'); + }); + + it('show', async () => { + await graph.showElement(['node-1', 'node-2', 'edge-1']); + + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show'); + }); + + it('show and hide', async () => { + await graph.setElementVisibility({ + 'node-1': 'hidden', + 'node-3': 'visible', + 'edge-1': 'hidden', + 'edge-2': 'visible', + }); + + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show-and-hide'); + }); + + it('show and hide single api', async () => { + graph.setElementVisibility('node-1', 'visible'); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show-single'); + + graph.setElementVisibility('node-1', 'hidden'); + await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__hide-single'); + }); +}); diff --git a/packages/g6/__tests__/unit/runtime/data.spec.ts b/packages/g6/__tests__/unit/runtime/data.spec.ts index 0ae3d9b8b75..e07cd56a600 100644 --- a/packages/g6/__tests__/unit/runtime/data.spec.ts +++ b/packages/g6/__tests__/unit/runtime/data.spec.ts @@ -11,10 +11,10 @@ const data = { { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, ], edges: [ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, - { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }, ], - combos: [{ id: 'combo-1' }], + combos: [{ id: 'combo-1', data: {}, style: {} }], }; describe('DataController', () => { @@ -32,7 +32,7 @@ describe('DataController', () => { controller.addComboData([{ id: 'combo-1' }]); - expect(controller.getComboData(['combo-1'])).toEqual([{ id: 'combo-1' }]); + expect(controller.getComboData(['combo-1'])).toEqual([{ id: 'combo-1', data: {}, style: {} }]); }); it('setData', () => { @@ -85,11 +85,14 @@ describe('DataController', () => { { id: 'node-5', data: { value: 5 }, style: { fill: 'black', parentId: 'combo-2' } }, ], edges: [ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, - { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, - { id: 'edge-3', source: 'node-1', target: 'node-5', data: { weight: 3 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }, + { id: 'edge-3', source: 'node-1', target: 'node-5', data: { weight: 3 }, style: {} }, + ], + combos: [ + { id: 'combo-1', data: {}, style: {} }, + { id: 'combo-2', data: {}, style: {} }, ], - combos: [{ id: 'combo-1' }, { id: 'combo-2' }], }); }); @@ -134,10 +137,13 @@ describe('DataController', () => { { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, ], edges: [ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 678 } }, - { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 666 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 678 }, style: {} }, + { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 666 }, style: {} }, + ], + combos: [ + { id: 'combo-1', data: { value: 100 }, style: { stroke: 'blue' } }, + { id: 'combo-2', data: {}, style: {} }, ], - combos: [{ id: 'combo-1', data: { value: 100 }, style: { stroke: 'blue' } }, { id: 'combo-2' }], }); }); @@ -160,20 +166,20 @@ describe('DataController', () => { controller.updateEdgeData([{ id: 'edge-1', data: { weight: 1 } }]); expect(controller.getEdgeData()).toEqual([ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, ]); // update source controller.updateEdgeData([{ id: 'edge-1', source: 'node-2' }]); expect(controller.getEdgeData()).toEqual([ - { id: 'edge-1', source: 'node-2', target: 'node-2', data: { weight: 1 } }, + { id: 'edge-1', source: 'node-2', target: 'node-2', data: { weight: 1 }, style: {} }, ]); // update target controller.updateEdgeData([{ id: 'edge-1', target: 'node-1' }]); expect(controller.getEdgeData()).toEqual([ - { id: 'edge-1', source: 'node-2', target: 'node-1', data: { weight: 1 } }, + { id: 'edge-1', source: 'node-2', target: 'node-1', data: { weight: 1 }, style: {} }, ]); }); @@ -203,22 +209,28 @@ describe('DataController', () => { expect(controller.getData()).toEqual({ nodes: [ - { id: 'node-1', style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } }, - { id: 'node-2', style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } }, + { id: 'node-1', data: {}, style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } }, + { id: 'node-2', data: {}, style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } }, ], edges: [], - combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }, { id: 'combo-2' }], + combos: [ + { id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }, + { id: 'combo-2', data: {}, style: {} }, + ], }); controller.updateComboData([{ id: 'combo-1', style: { fill: 'pink' } }]); expect(controller.getData()).toEqual({ nodes: [ - { id: 'node-1', style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } }, - { id: 'node-2', style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } }, + { id: 'node-1', data: {}, style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } }, + { id: 'node-2', data: {}, style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } }, ], edges: [], - combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0, fill: 'pink' } }, { id: 'combo-2' }], + combos: [ + { id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0, fill: 'pink' } }, + { id: 'combo-2', data: {}, style: {} }, + ], }); controller.updateComboData([{ id: 'combo-2' }]); @@ -235,8 +247,8 @@ describe('DataController', () => { controller.translateComboBy(['combo-1'], [100, 100]); expect(controller.getData()).toEqual({ - nodes: [{ id: 'node-1', style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }], - combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }], + nodes: [{ id: 'node-1', data: {}, style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }], + combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }], edges: [], }); }); @@ -256,9 +268,9 @@ describe('DataController', () => { controller.translateComboBy(['combo-1'], [66, 67]); expect(controller.getNodeData()).toEqual([ - { id: 'node-1' }, - { id: 'node-2', style: { x: 66, y: 117, z: 0, fill: 'green', parentId: 'combo-1' } }, - { id: 'node-3', style: { x: 166, y: 167, z: 0, fill: 'blue', parentId: 'combo-1' } }, + { id: 'node-1', data: {}, style: {} }, + { id: 'node-2', data: {}, style: { x: 66, y: 117, z: 0, fill: 'green', parentId: 'combo-1' } }, + { id: 'node-3', data: {}, style: { x: 166, y: 167, z: 0, fill: 'blue', parentId: 'combo-1' } }, ]); }); @@ -274,7 +286,7 @@ describe('DataController', () => { expect(controller.getData()).toEqual({ nodes: [], edges: [], - combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }], + combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }], }); }); @@ -293,10 +305,10 @@ describe('DataController', () => { expect(controller.getData()).toEqual({ nodes: [ - { id: 'node-1', style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }, - { id: 'node-2', style: { parentId: 'combo-1', x: 110, y: 110, z: 0 } }, + { id: 'node-1', data: {}, style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }, + { id: 'node-2', data: {}, style: { parentId: 'combo-1', x: 110, y: 110, z: 0 } }, ], - combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }], + combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }], edges: [], }); }); @@ -317,8 +329,8 @@ describe('DataController', () => { { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } }, { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, ], - edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }], - combos: [{ id: 'combo-1' }], + edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }], + combos: [{ id: 'combo-1', data: {}, style: {} }], }); controller.removeComboData(['combo-1']); @@ -328,7 +340,7 @@ describe('DataController', () => { { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: undefined } }, { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: undefined } }, ], - edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }], + edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }], combos: [], }); @@ -372,8 +384,8 @@ describe('DataController', () => { { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: undefined } }, ], edges: [ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, - { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }, ], combos: [], }); @@ -384,11 +396,14 @@ describe('DataController', () => { it('removeComboData with children', () => { const data = { nodes: [ - { id: 'node-1', style: { parentId: 'combo-1' } }, - { id: 'node-2', style: { parentId: 'combo-1' } }, - { id: 'node-3', style: { parentId: 'combo-1' } }, + { id: 'node-1', data: {}, style: { parentId: 'combo-1' } }, + { id: 'node-2', data: {}, style: { parentId: 'combo-1' } }, + { id: 'node-3', data: {}, style: { parentId: 'combo-1' } }, + ], + combos: [ + { id: 'combo-1', data: {}, style: { parentId: 'combo-2' } }, + { id: 'combo-2', data: {}, style: {} }, ], - combos: [{ id: 'combo-1', style: { parentId: 'combo-2' } }, { id: 'combo-2' }], }; const controller = new DataController(); @@ -404,12 +419,12 @@ describe('DataController', () => { expect(controller.getData()).toEqual({ nodes: [ - { id: 'node-1', style: { parentId: 'combo-2' } }, - { id: 'node-2', style: { parentId: 'combo-2' } }, - { id: 'node-3', style: { parentId: 'combo-2' } }, + { id: 'node-1', data: {}, style: { parentId: 'combo-2' } }, + { id: 'node-2', data: {}, style: { parentId: 'combo-2' } }, + { id: 'node-3', data: {}, style: { parentId: 'combo-2' } }, ], edges: [], - combos: [{ id: 'combo-2' }], + combos: [{ id: 'combo-2', data: {}, style: {} }], }); }); @@ -429,18 +444,18 @@ describe('DataController', () => { const changes = controller.getChanges(); expect(changes).toEqual([ - { value: { id: 'combo-1' }, type: 'ComboAdded' }, + { value: { id: 'combo-1', data: {}, style: {} }, type: 'ComboAdded' }, { value: { id: 'node-1', data: { value: 1 }, style: { fill: 'red' } }, type: 'NodeAdded' }, + { value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } }, type: 'NodeAdded' }, + { value: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, type: 'NodeAdded' }, { - value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } }, - type: 'NodeAdded', + value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + type: 'EdgeAdded', }, { - value: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, - type: 'NodeAdded', + value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }, + type: 'EdgeAdded', }, - { value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, type: 'EdgeAdded' }, - { value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, type: 'EdgeAdded' }, { value: { id: 'combo-2' }, type: 'ComboAdded' }, { value: { id: 'node-4', data: { value: 4 }, style: { fill: 'yellow' } }, type: 'NodeAdded' }, { @@ -448,14 +463,20 @@ describe('DataController', () => { original: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, type: 'NodeUpdated', }, - { value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, type: 'EdgeRemoved' }, - { value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, type: 'EdgeRemoved' }, + { + value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + type: 'EdgeRemoved', + }, + { + value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }, + type: 'EdgeRemoved', + }, { value: { id: 'node-1', data: { value: 1 }, style: { fill: 'red' } }, type: 'NodeRemoved' }, { value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } }, type: 'NodeRemoved', }, - { value: { id: 'combo-1' }, type: 'ComboRemoved' }, + { value: { id: 'combo-1', data: {}, style: {} }, type: 'ComboRemoved' }, ]); expect(reduceDataChanges(changes)).toEqual([ @@ -637,21 +658,21 @@ describe('DataController', () => { controller.addData(clone(data)); expect(controller.getRelatedEdgesData('node-1')).toEqual([ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, ]); controller.addEdgeData([{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } }]); expect(controller.getRelatedEdgesData('node-1')).toEqual([ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, - { id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + { id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 }, style: {} }, ]); expect(controller.getRelatedEdgesData('node-1', 'in')).toEqual([]); expect(controller.getRelatedEdgesData('node-1', 'out')).toEqual([ - { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, - { id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } }, + { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} }, + { id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 }, style: {} }, ]); }); diff --git a/packages/g6/__tests__/unit/runtime/element.spec.ts b/packages/g6/__tests__/unit/runtime/element.spec.ts index a698bf6c5e9..cc374001e71 100644 --- a/packages/g6/__tests__/unit/runtime/element.spec.ts +++ b/packages/g6/__tests__/unit/runtime/element.spec.ts @@ -23,18 +23,18 @@ describe('ElementController', () => { const edge1Id = idOf(options.data!.edges![0]); const edge2Id = idOf(options.data!.edges![1]); - expect(elementController.getDataStyle('node', 'node-1')).toEqual(options.data!.nodes![0].style || {}); + expect(elementController.getDataStyle('node-1')).toEqual(options.data!.nodes![0].style || {}); // 没有属性 / no style - expect(elementController.getDataStyle('node', 'node-2')).toEqual({ x: 150, y: 100 }); + expect(elementController.getDataStyle('node-2')).toEqual({ x: 150, y: 100 }); // 没有样式属性 / No style attribute - expect(elementController.getDataStyle('node', 'node-3')).toEqual({ + expect(elementController.getDataStyle('node-3')).toEqual({ x: 125, y: 150, parentId: 'combo-1', states: ['selected'], }); - expect(elementController.getDataStyle('edge', edge1Id)).toEqual(options.data!.edges![0].style || {}); - expect(elementController.getDataStyle('combo', 'combo-1')).toEqual({}); + expect(elementController.getDataStyle(edge1Id)).toEqual(options.data!.edges![0].style || {}); + expect(elementController.getDataStyle('combo-1')).toEqual({}); // ref light theme expect(elementController.getThemeStyle('node')).toEqual(LIGHT_THEME.node!.style); @@ -72,20 +72,20 @@ describe('ElementController', () => { }); expect(elementController.getStateStyle('combo-1')).toEqual({}); - expect(Object.keys(elementController.getElementsByState('selected'))).toEqual([ - 'node-3', - idOf(options.data!.edges![1]), - ]); + // expect(Object.keys(elementController.getElementsByState('selected'))).toEqual([ + // 'node-3', + // idOf(options.data!.edges![1]), + // ]); - elementController.setElementsState({ 'node-1': ['active'] }); - expect(elementController.getElementStates('node-1')).toEqual(['active']); - elementController.setElementsState({ 'node-1': [] }); - expect(elementController.getElementStates('node-1')).toEqual([]); + // elementController.setElementsState({ 'node-1': ['active'] }); + // expect(elementController.getElementStates('node-1')).toEqual(['active']); + // elementController.setElementsState({ 'node-1': [] }); + // expect(elementController.getElementStates('node-1')).toEqual([]); - expect(elementController.getElementStates('node-2')).toEqual([]); - expect(elementController.getElementStates('node-3')).toEqual(['selected']); - expect(elementController.getElementStates('edge-1')).toEqual([]); - expect(elementController.getElementStates(idOf(options.data!.edges![1]))).toEqual(['active', 'selected']); + // expect(elementController.getElementStates('node-2')).toEqual([]); + // expect(elementController.getElementStates('node-3')).toEqual(['selected']); + // expect(elementController.getElementStates('edge-1')).toEqual([]); + // expect(elementController.getElementStates(idOf(options.data!.edges![1]))).toEqual(['active', 'selected']); expect(elementController.getElementComputedStyle('node', 'node-1')).toEqual({ ...LIGHT_THEME.node?.style, diff --git a/packages/g6/__tests__/unit/runtime/element/visibility.spec.ts b/packages/g6/__tests__/unit/runtime/element/visibility.spec.ts index 33506db3133..4239d379319 100644 --- a/packages/g6/__tests__/unit/runtime/element/visibility.spec.ts +++ b/packages/g6/__tests__/unit/runtime/element/visibility.spec.ts @@ -14,7 +14,7 @@ describe('element visibility', () => { }); it('hide', async () => { - graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'hidden'); + graph.hideElement(['node-3', 'node-2-node-3', 'node-3-node-1']); expect(graph.getElementVisibility('node-3')).toBe('hidden'); expect(graph.getElementVisibility('node-2-node-3')).toBe('hidden'); @@ -24,7 +24,7 @@ describe('element visibility', () => { }); it('show', async () => { - graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'visible'); + graph.showElement(['node-3', 'node-2-node-3', 'node-3-node-1']); expect(graph.getElementVisibility('node-3')).toBe('visible'); expect(graph.getElementVisibility('node-2-node-3')).toBe('visible'); diff --git a/packages/g6/__tests__/unit/runtime/element/z-index.spec.ts b/packages/g6/__tests__/unit/runtime/element/z-index.spec.ts index 5e1de72558d..24ae72bcb78 100644 --- a/packages/g6/__tests__/unit/runtime/element/z-index.spec.ts +++ b/packages/g6/__tests__/unit/runtime/element/z-index.spec.ts @@ -14,19 +14,19 @@ describe('element z-index', () => { }); it('front', async () => { - graph.setElementZIndex('node-2', 'front'); + graph.frontElement('node-2'); await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__front'); }); it('back', async () => { - graph.setElementZIndex('node-2', 'back'); + graph.backElement('node-2'); await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__back'); }); it('to', async () => { - graph.setElementZIndex('node-2', 0); + graph.setElementZIndex({ 'node-2': 0 }); await expect(graph.getCanvas()).toMatchSnapshot(__filename); }); diff --git a/packages/g6/__tests__/unit/runtime/graph/graph.spec.ts b/packages/g6/__tests__/unit/runtime/graph/graph.spec.ts index 398975dcd78..af55feb06f9 100644 --- a/packages/g6/__tests__/unit/runtime/graph/graph.spec.ts +++ b/packages/g6/__tests__/unit/runtime/graph/graph.spec.ts @@ -6,7 +6,7 @@ import { createDemoGraph } from '@@/utils'; describe('Graph', () => { let graph: Graph; beforeAll(async () => { - graph = await createDemoGraph(commonGraph); + graph = await createDemoGraph(commonGraph, { animation: false }); }); const idOf = (d: any) => d.id; @@ -70,22 +70,32 @@ describe('Graph', () => { }); it('updateData/getData/setData', () => { - expect(graph.getData()).toEqual({ combos: [], ...data }); + // 调整之后,getData 获取的为当前 graph 最新的数据,而不是初始化时的数据 + // After adjustment, the data obtained by getData is the latest data of the graph, not the data when it is initialized + const currData = graph.getData(); + expect(currData.nodes?.map(idOf)).toEqual(data.nodes.map(idOf)); + expect(currData.edges?.map(idOf)).toEqual(data.edges.map(idOf)); graph.setData({ nodes: [{ id: 'node-1' }, { id: 'node-2' }], edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' }], }); expect(graph.getData()).toEqual({ - nodes: [{ id: 'node-1' }, { id: 'node-2' }], - edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' }], + nodes: [ + { id: 'node-1', data: {}, style: {} }, + { id: 'node-2', data: {}, style: {} }, + ], + edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: {} }], combos: [], }); graph.updateData({ edges: [{ id: 'edge-1', style: { lineWidth: 5 } }] }); expect(graph.getData()).toEqual({ - nodes: [{ id: 'node-1' }, { id: 'node-2' }], - edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', style: { lineWidth: 5 } }], + nodes: [ + { id: 'node-1', data: {}, style: {} }, + { id: 'node-2', data: {}, style: {} }, + ], + edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: { lineWidth: 5 } }], combos: [], }); }); @@ -115,13 +125,13 @@ describe('Graph', () => { graph.updateEdgeData([{ id: 'edge-2', style: { lineWidth: 10 } }]); graph.updateComboData([{ id: 'combo-1', style: { stroke: 'red' } }]); expect(graph.getNodeData()).toEqual([ - { id: 'node-1' }, - { id: 'node-2' }, - { id: 'node-3', style: { x: 100, y: 100, parentId: 'combo-1' } }, - { id: 'node-4', style: { parentId: 'combo-1' } }, + { id: 'node-1', data: {}, style: {} }, + { id: 'node-2', data: {}, style: {} }, + { id: 'node-3', data: {}, style: { x: 100, y: 100, parentId: 'combo-1' } }, + { id: 'node-4', data: {}, style: { parentId: 'combo-1' } }, ]); expect(graph.getEdgeData().map(idOf)).toEqual(['edge-1', 'edge-2']); - expect(graph.getComboData()).toEqual([{ id: 'combo-1', style: { stroke: 'red' } }]); + expect(graph.getComboData()).toEqual([{ id: 'combo-1', data: {}, style: { stroke: 'red' } }]); graph.removeComboData(['combo-1']); graph.removeNodeData(['node-3', 'node-4']); expect(graph.getNodeData().map(idOf)).toEqual(['node-1', 'node-2']); @@ -150,7 +160,7 @@ describe('Graph', () => { }); it('getNeighborNodesData', () => { - expect(graph.getNeighborNodesData('node-1')).toEqual([{ id: 'node-2' }]); + expect(graph.getNeighborNodesData('node-1')).toEqual([{ id: 'node-2', data: {}, style: {} }]); }); it('getParentData', () => { @@ -164,24 +174,41 @@ describe('Graph', () => { expect(renderBounds.max).toEqual([16, 16, 0]); }); - it('setElementState/getElementState/getElementDataByState', () => { - graph.setElementState('node-1', ['selected']); + it('setElementState/getElementState/getElementDataByState', async () => { + await graph.setElementState('node-2', 'selected'); + expect(graph.getElementState('node-2')).toEqual(['selected']); + await graph.setElementState('node-2', []); + expect(graph.getElementState('node-2')).toEqual([]); + + await graph.setElementState({ 'node-1': 'selected' }); expect(graph.getElementState('node-1')).toEqual(['selected']); expect(graph.getElementState('node-2')).toEqual([]); - expect(graph.getElementDataByState('node', 'selected')).toEqual([{ id: 'node-1' }]); + expect(graph.getElementDataByState('node', 'selected')).toEqual([ + { id: 'node-1', data: {}, style: { states: ['selected'] } }, + ]); }); - it('setElementZIndex/getElementZIndex', () => { - graph.setElementZIndex('node-1', 'front'); + it('setElementZIndex/getElementZIndex', async () => { + await graph.setElementZIndex('node-1', 2); + expect(graph.getElementZIndex('node-1')).toBe(2); + await graph.setElementZIndex({ 'node-1': 0 }); + expect(graph.getElementZIndex('node-1')).toBe(0); + + await graph.frontElement('node-1'); expect(graph.getElementZIndex('node-1')).toBe(1); expect(graph.getElementZIndex('node-2')).toBe(0); }); - it('setElementVisibility/getElementVisibility', () => { - graph.setElementVisibility('node-1', 'hidden'); + it('setElementVisibility/getElementVisibility', async () => { + await graph.hideElement('node-1'); + expect(graph.getElementVisibility('node-1')).toBe('hidden'); + await graph.showElement('node-1'); + expect(graph.getElementVisibility('node-1')).toBe('visible'); + + await graph.setElementVisibility({ 'node-1': 'hidden' }); expect(graph.getElementVisibility('node-1')).toBe('hidden'); expect(graph.getElementVisibility('node-2')).toBe('visible'); - graph.setElementVisibility('node-1', 'visible'); + await graph.setElementVisibility({ 'node-1': 'visible' }); expect(graph.getElementVisibility('node-1')).toBe('visible'); }); diff --git a/packages/g6/__tests__/unit/utils/cache.spec.ts b/packages/g6/__tests__/unit/utils/cache.spec.ts index e9cc1ed5dee..7aaeb4cf24a 100644 --- a/packages/g6/__tests__/unit/utils/cache.spec.ts +++ b/packages/g6/__tests__/unit/utils/cache.spec.ts @@ -1,4 +1,4 @@ -import { cacheStyle, getCachedStyle, setCacheStyle } from '@/src/utils/cache'; +import { cacheStyle, getCachedStyle, hasCachedStyle, setCacheStyle } from '@/src/utils/cache'; import { Circle } from '@antv/g'; describe('cache', () => { @@ -11,10 +11,14 @@ describe('cache', () => { }, }); + expect(hasCachedStyle(circle, 'fill')).toBe(false); + cacheStyle(circle, ['fill', 'stroke']); circle.style.fill = 'green'; + expect(hasCachedStyle(circle, 'fill')).toBe(true); + expect(getCachedStyle(circle, 'fill')).toBe('red'); setCacheStyle(circle, 'fill', 'yellow'); diff --git a/packages/g6/__tests__/unit/utils/graphlib.spec.ts b/packages/g6/__tests__/unit/utils/graphlib.spec.ts index e67687e78f8..20689f6b3c8 100644 --- a/packages/g6/__tests__/unit/utils/graphlib.spec.ts +++ b/packages/g6/__tests__/unit/utils/graphlib.spec.ts @@ -9,8 +9,8 @@ describe('graphlib', () => { { id: 'node-3', data: { value: 2 }, style: { opacity: 0.5 } }, ].map(toGraphlibData), ).toEqual([ - { id: 'node-1', data: { id: 'node-1' } }, - { id: 'node-2', data: { id: 'node-2', data: { value: 1 } } }, + { id: 'node-1', data: { id: 'node-1', data: {}, style: {} } }, + { id: 'node-2', data: { id: 'node-2', data: { value: 1 }, style: {} } }, { id: 'node-3', data: { id: 'node-3', data: { value: 2 }, style: { opacity: 0.5 } } }, ]); @@ -24,7 +24,7 @@ describe('graphlib', () => { id: 'edge-1', source: 'node-1', target: 'node-2', - data: { id: 'edge-1', source: 'node-1', target: 'node-2' }, + data: { id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: {} }, }, { id: 'edge-2', @@ -35,6 +35,27 @@ describe('graphlib', () => { ]); }); + it('data isolation', () => { + const raw = { + id: 'node-3', + data: { basic: 2, array: [1, 2, 3], object: { a: 1 } }, + style: { x: 100, y: 100, opacity: 0.5, size: [100, 100] }, + }; + const graphlibData = toGraphlibData(raw); + + expect(graphlibData.data).toEqual(raw); + + Object.assign(graphlibData.data.data!, { basic: 3, array: [4, 5, 6], object: { b: 2 } }); + + expect(raw.data).toEqual({ basic: 2, array: [1, 2, 3], object: { a: 1 } }); + expect(graphlibData.data.data).toEqual({ basic: 3, array: [4, 5, 6], object: { b: 2 } }); + + graphlibData.data.style!.x = 200; + graphlibData.data.style!.size = [200, 200]; + + expect(raw.style).toEqual({ x: 100, y: 100, opacity: 0.5, size: [100, 100] }); + }); + it('toG6Data', () => { expect( [ diff --git a/packages/g6/__tests__/unit/utils/id.spec.ts b/packages/g6/__tests__/unit/utils/id.spec.ts index 63652ba4820..b5c494445d9 100644 --- a/packages/g6/__tests__/unit/utils/id.spec.ts +++ b/packages/g6/__tests__/unit/utils/id.spec.ts @@ -1,4 +1,4 @@ -import { idOf } from '@/src/utils/id'; +import { idOf, parentIdOf } from '@/src/utils/id'; describe('id', () => { it('idOf', () => { @@ -7,4 +7,9 @@ describe('id', () => { expect(idOf({ source: 'node-1', target: 'edge-1' })).toBe(`node-1-edge-1`); expect(() => idOf({})).toThrow(); }); + + it('parentIdOf', () => { + expect(parentIdOf({ style: { parentId: '1' } })).toBe('1'); + expect(parentIdOf({})).toBeUndefined(); + }); }); diff --git a/packages/g6/__tests__/unit/utils/layout.spec.ts b/packages/g6/__tests__/unit/utils/layout.spec.ts index 29e0852f4df..6f661f892f5 100644 --- a/packages/g6/__tests__/unit/utils/layout.spec.ts +++ b/packages/g6/__tests__/unit/utils/layout.spec.ts @@ -1,4 +1,4 @@ -import { isComboLayout, isPositionSpecified, isTreeLayout, pickLayoutResult } from '@/src/utils/layout'; +import { isComboLayout, isPositionSpecified, isTreeLayout } from '@/src/utils/layout'; describe('layout', () => { it('isComboLayout', () => { @@ -14,51 +14,6 @@ describe('layout', () => { expect(isTreeLayout({ type: 'mindmap' })).toBe(true); }); - it('pickLayoutResult', () => { - expect(pickLayoutResult({ nodes: [], edges: [] })).toEqual({ nodes: {}, edges: {} }); - expect( - pickLayoutResult({ - nodes: [{ id: 'node-1', data: { x: 100, y: 100 } }], - edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }], - }), - ).toEqual({ - nodes: { - 'node-1': [100, 100], - }, - edges: { - 'edge-1': {}, - }, - }); - expect( - pickLayoutResult({ - nodes: [{ id: 'node-1', data: { x: 100, y: 100, z: 100 } }], - edges: [], - }), - ).toEqual({ - nodes: { - 'node-1': [100, 100, 100], - }, - edges: {}, - }); - expect( - pickLayoutResult({ - nodes: [ - { id: 'node-1', data: { x: 100, y: 100 } }, - { id: 'node-2', data: { x: 150, y: 100 } }, - { id: 'node-3', data: { x: 100, y: 150 } }, - ], - edges: [], - }), - ).toEqual({ - nodes: { - 'node-1': [100, 100], - 'node-2': [150, 100], - 'node-3': [100, 150], - }, - edges: {}, - }); - }); - it('isPositionSpecified', () => { expect(isPositionSpecified({})).toBe(false); expect(isPositionSpecified({ x: 100 })).toBe(false); diff --git a/packages/g6/__tests__/unit/utils/visibility.spec.ts b/packages/g6/__tests__/unit/utils/visibility.spec.ts new file mode 100644 index 00000000000..69d9845c27d --- /dev/null +++ b/packages/g6/__tests__/unit/utils/visibility.spec.ts @@ -0,0 +1,50 @@ +import { BaseShape } from '@/src/elements/shapes/base-shape'; +import { Circle } from '@antv/g'; + +class Shape extends BaseShape<{ visibility: 'visible' | 'hidden' }> { + render() { + this.upsert('visibleShape', Circle, { r: 10 }, this); + this.upsert('hiddenShape', Circle, { r: 10, visibility: 'hidden' }, this); + } +} + +describe('visibility', () => { + it('setVisibility', () => { + const shape = new Shape({}); + // @ts-expect-error shapeMap is private + const vShape = shape.shapeMap.visibleShape; + // @ts-expect-error shapeMap is private + const hShape = shape.shapeMap.hiddenShape; + + expect(shape.style.visibility).toBe(undefined); + expect(vShape.style.visibility).toBe(undefined); + expect(hShape.style.visibility).toBe('hidden'); + + shape.update({ visibility: 'hidden' }); + expect(shape.style.visibility).toBe('hidden'); + expect(vShape.style.visibility).toBe('hidden'); + expect(hShape.style.visibility).toBe('hidden'); + + shape.update({ visibility: 'visible' }); + expect(shape.style.visibility).toBe('visible'); + expect(vShape.style.visibility).toBe('visible'); + expect(hShape.style.visibility).toBe('hidden'); + }); + + it('setVisibility default is hidden', () => { + const shape = new Shape({ style: { visibility: 'hidden' } }); + // @ts-expect-error shapeMap is private + const vShape = shape.shapeMap.visibleShape; + // @ts-expect-error shapeMap is private + const hShape = shape.shapeMap.hiddenShape; + + expect(shape.style.visibility).toBe('hidden'); + expect(vShape.style.visibility).toBe('hidden'); + expect(hShape.style.visibility).toBe('hidden'); + + shape.update({ visibility: 'visible' }); + expect(shape.style.visibility).toBe('visible'); + expect(vShape.style.visibility).toBe('visible'); + expect(hShape.style.visibility).toBe('hidden'); + }); +}); diff --git a/packages/g6/src/behaviors/drag-node.ts b/packages/g6/src/behaviors/drag-node.ts index d9455ecf6d8..871a646b982 100644 --- a/packages/g6/src/behaviors/drag-node.ts +++ b/packages/g6/src/behaviors/drag-node.ts @@ -196,7 +196,7 @@ export class DragNode extends BaseBehavior { private showEdges() { if (this.options.shadow || this.hiddenEdges.length === 0) return; - this.context.graph.setElementVisibility(this.hiddenEdges, 'visible'); + this.context.graph.showElement(this.hiddenEdges); this.hiddenEdges = []; } @@ -204,18 +204,13 @@ export class DragNode extends BaseBehavior { const { hideEdges, shadow } = this.options; if (hideEdges === 'none' || shadow) return; const { graph } = this.context; - let hiddenEdges: ID[] = []; - if (hideEdges === 'all') { - hiddenEdges = graph.getEdgeData().map((edge) => idOf(edge)); - } else { - const edgeSet = new Set(); - this.target.forEach((id) => { - graph.getRelatedEdgesData(id, hideEdges).forEach((edge) => edgeSet.add(idOf(edge))); - }); - hiddenEdges = Array.from(edgeSet); + if (hideEdges === 'all') this.hiddenEdges = graph.getEdgeData().map(idOf); + else { + this.hiddenEdges = Array.from( + new Set(this.target.map((id) => graph.getRelatedEdgesData(id, hideEdges).map(idOf)).flat()), + ); } - this.hiddenEdges = hiddenEdges; - graph.setElementVisibility(hiddenEdges, 'hidden'); + graph.hideElement(this.hiddenEdges); } public destroy() { diff --git a/packages/g6/src/elements/shapes/base-shape.ts b/packages/g6/src/elements/shapes/base-shape.ts index 4ef7b48800f..9303311e506 100644 --- a/packages/g6/src/elements/shapes/base-shape.ts +++ b/packages/g6/src/elements/shapes/base-shape.ts @@ -4,6 +4,7 @@ import { deepMix } from '@antv/util'; import type { Keyframe } from '../../types'; import { createAnimationsProxy, preprocessKeyframes } from '../../utils/animation'; import { updateStyle } from '../../utils/element'; +import { setVisibility } from '../../utils/visibility'; export interface BaseShapeStyleProps extends BaseStyleProps { x?: number | string; @@ -15,6 +16,7 @@ export abstract class BaseShape extends super(options); this.render(this.attributes as Required, this); + this.setVisibility(); this.bindEvents(); } @@ -80,7 +82,8 @@ export abstract class BaseShape extends public update(attr: Partial = {}): void { this.attr(deepMix({}, this.attributes, attr)); - return this.render(this.attributes as Required, this); + this.render(this.attributes as Required, this); + this.setVisibility(); } /** @@ -140,4 +143,9 @@ export abstract class BaseShape extends return createAnimationsProxy(animationMap); } + + private setVisibility() { + const { visibility } = this.attributes; + setVisibility(this, visibility); + } } diff --git a/packages/g6/src/runtime/data.ts b/packages/g6/src/runtime/data.ts index 00e92d54976..fde91973020 100644 --- a/packages/g6/src/runtime/data.ts +++ b/packages/g6/src/runtime/data.ts @@ -14,6 +14,7 @@ import type { PartialEdgeData, PartialGraphData, PartialNodeLikeData, + State, } from '../types'; import type { EdgeDirection } from '../types/edge'; import type { ElementType } from '../types/element'; @@ -21,7 +22,7 @@ import type { Point } from '../types/point'; import { cloneElementData, mergeElementsData } from '../utils/data'; import { arrayDiff } from '../utils/diff'; import { toG6Data, toGraphlibData } from '../utils/graphlib'; -import { idOf } from '../utils/id'; +import { idOf, parentIdOf } from '../utils/id'; import { dfs } from '../utils/traverse'; export class DataController { @@ -179,6 +180,20 @@ export class DataController { return this.model.getChildren(id, TREE_KEY).map((node) => node.data); } + /** + * 获取指定类型元素的数据 + * + * Get the data of the specified type of element + * @param elementType - 元素类型 | element type + * @returns 元素数据 | element data + */ + public getElementData(elementType: ElementType) { + if (elementType === 'node') return this.getNodeData(); + if (elementType === 'edge') return this.getEdgeData(); + if (elementType === 'combo') return this.getComboData(); + return []; + } + /** * 根据 ID 获取元素的数据,不用关心元素的类型 * @@ -189,7 +204,6 @@ export class DataController { public getElementsData(ids: ID[]): ElementDatum[] { return ids.map((id) => { const type = this.getElementType(id); - if (type === 'node') return this.getNodeData([id])[0]; else if (type === 'edge') return this.getEdgeData([id])[0]; return this.getComboData([id])[0]; @@ -211,6 +225,15 @@ export class DataController { }, [] as NodeLikeData[]); } + public getElementDataByState(elementType: ElementType, state: string) { + const elementData = this.getElementData(elementType); + return elementData.filter((datum) => datum.style?.states?.includes(state)); + } + + public getElementState(id: ID): State[] { + return this.getElementsData([id])?.[0]?.style?.states || []; + } + public hasNode(id: ID) { return this.model.hasNode(id) && !this.isCombo(id); } @@ -327,7 +350,7 @@ export class DataController { data.forEach((datum) => { const id = idOf(datum); - const parentId = datum?.style?.parentId; + const parentId = parentIdOf(datum); if (parentId !== undefined) { model.attachTreeStructure(COMBO_KEY); model.setParent(id, parentId, COMBO_KEY); @@ -541,10 +564,10 @@ export class DataController { this.model.getChildren(id, COMBO_KEY).forEach((child) => { const childData = child.data; const childId = idOf(childData); - this.model.setParent(idOf(childData), data?.style?.parentId, COMBO_KEY); + this.model.setParent(idOf(childData), parentIdOf(data), COMBO_KEY); const value = mergeElementsData(childData, { id: idOf(childData), - style: { parentId: data?.style?.parentId }, + style: { parentId: parentIdOf(data) }, }); this.pushChange({ value, diff --git a/packages/g6/src/runtime/element.ts b/packages/g6/src/runtime/element.ts index bb7b7547827..101891d36f8 100644 --- a/packages/g6/src/runtime/element.ts +++ b/packages/g6/src/runtime/element.ts @@ -3,14 +3,14 @@ import type { BaseStyleProps, DisplayObject, IAnimation } from '@antv/g'; import { Group } from '@antv/g'; import type { ID } from '@antv/graphlib'; -import { groupBy, isUndefined, pick } from '@antv/util'; +import { groupBy } from '@antv/util'; import { executor as animationExecutor } from '../animations'; import type { AnimationContext } from '../animations/types'; import { AnimationType, ChangeTypeEnum, GraphEvent } from '../constants'; import type { BaseNode } from '../elements/nodes'; import type { BaseShape } from '../elements/shapes'; import { getExtension } from '../registry'; -import type { ComboData, EdgeData, GraphData, NodeData } from '../spec'; +import type { ComboData, EdgeData, NodeData } from '../spec'; import type { AnimationStage } from '../spec/element/animation'; import type { EdgeStyle } from '../spec/element/edge'; import type { NodeLikeStyle } from '../spec/element/node'; @@ -22,48 +22,21 @@ import type { ElementData, ElementDatum, ElementType, - LayoutResult, Node, - Positions, State, - States, StyleIterationContext, - ZIndex, } from '../types'; import { executeAnimatableTasks, inferDefaultValue, withAnimationCallbacks } from '../utils/animation'; -import { deduplicate } from '../utils/array'; -import { cacheStyle, getCachedStyle, setCacheStyle } from '../utils/cache'; +import { cacheStyle, getCachedStyle, hasCachedStyle } from '../utils/cache'; import { reduceDataChanges } from '../utils/change'; -import { isEmptyData } from '../utils/data'; import { updateStyle } from '../utils/element'; import type { BaseEvent } from '../utils/event'; -import { - AnimateEvent, - ElementStateChangeEvent, - ElementTranslateEvent, - ElementVisibilityChangeEvent, - ElementZIndexChangeEvent, - GraphLifeCycleEvent, -} from '../utils/event'; -import { idOf } from '../utils/id'; +import { AnimateEvent, GraphLifeCycleEvent } from '../utils/event'; +import { idOf, parentIdOf } from '../utils/id'; import { assignColorByPalette, parsePalette } from '../utils/palette'; import { computeElementCallbackStyle } from '../utils/style'; -import { setVisibility } from '../utils/visibility'; import type { RuntimeContext } from './types'; -type AnimationExecutor = ( - id: ID, - shape: DisplayObject, - originalStyle: Record, - modifiedStyle?: Record, -) => IAnimation | null; - -type RenderContext = { - animator?: AnimationExecutor; - /** 是否使用动画,默认为 true | Whether to use animation, default is true */ - animation: boolean; -}; - export class ElementController { private context: RuntimeContext; @@ -79,7 +52,6 @@ export class ElementController { constructor(context: RuntimeContext) { this.context = context; - this.initElementState(context.options.data || {}); } public init() { @@ -98,40 +70,14 @@ export class ElementController { graph.emit(event.type, event); } - private getElementData(elementType: ElementType, ids?: ID[]) { - const { model } = this.context; - - switch (elementType) { - case 'node': - return model.getNodeData(ids); - case 'edge': - return model.getEdgeData(ids); - case 'combo': - return model.getComboData(ids); - default: - return []; - } - } - private forEachElementData(callback: (elementType: ElementType, elementData: ElementData) => void) { const elementTypes: ElementType[] = ['node', 'edge', 'combo']; elementTypes.forEach((elementType) => { - const elementData = this.getElementData(elementType); + const elementData = this.context.model.getElementData(elementType); callback(elementType, elementData); }); } - private runtimeStyle: Record> = {}; - - private getRuntimeStyle(id: ID) { - return this.runtimeStyle[id] || {}; - } - - private setRuntimeStyle(id: ID, style: Record) { - if (!this.runtimeStyle[id]) this.runtimeStyle[id] = { ...style }; - else Object.assign(this.runtimeStyle[id], style); - } - private getTheme(elementType: ElementType) { const { theme } = this.context.options; if (!theme) return {}; @@ -170,8 +116,8 @@ export class ElementController { }; } - public getDataStyle(elementType: ElementType, id: ID): NodeLikeStyle | EdgeStyle { - const datum = this.getElementData(elementType, [id])?.[0]; + public getDataStyle(id: ID): NodeLikeStyle | EdgeStyle { + const datum = this.context.model.getElementsData([id])?.[0]; return datum?.style || {}; } @@ -202,55 +148,13 @@ export class ElementController { return this.defaultStyle[id] || {}; } - public elementState: Record = {}; - - /** - * 从数据中初始化元素状态 - * - * Initialize element state from data - */ - private initElementState(data: GraphData) { - const { nodes = [], edges = [], combos = [] } = data; - [...nodes, ...edges, ...combos].forEach((elementData) => { - const states = elementData.style?.states || []; - const id = idOf(elementData); - this.elementState[id] = states; - }); - } - - public setElementsState(states: States) { - const graphData: Required = { nodes: [], edges: [], combos: [] }; - - Object.entries(states).forEach(([id, state]) => { - this.elementState[id] = state; - const elementType = this.context.model.getElementType(id); - const datum = this.context.model.getElementsData([id])[0]; - this.computeElementStatesStyle(elementType, state, { datum, index: 0, elementData: [datum] as ElementData }); - - graphData[`${elementType}s`].push(datum as any); - }); - - const tasks = this.getUpdateTasks(graphData, { animation: false }); - - executeAnimatableTasks(tasks, { - before: () => this.emit(new ElementStateChangeEvent(GraphEvent.BEFORE_ELEMENT_STATE_CHANGE, states)), - beforeAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_STATE_CHANGE, animation, states)), - afterAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_STATE_CHANGE, animation, states)), - after: () => this.emit(new ElementStateChangeEvent(GraphEvent.AFTER_ELEMENT_STATE_CHANGE, states)), - }); - } - - /** - * 获取指定元素的状态 - * - * Get the state of the specified element - * @param id - 元素 id | element id - * @returns 元素状态数组 | element state array - */ - public getElementStates(id: ID): State[] { - return this.elementState[id] || []; + private getElementState(id: ID) { + try { + const { model } = this.context; + return model.getElementState(id); + } catch { + return []; + } } private stateStyle: Record> = {}; @@ -289,7 +193,7 @@ export class ElementController { elementData .filter((datum) => ids === undefined || ids.includes(idOf(datum))) .forEach((datum, index) => { - const states = this.getElementStates(idOf(datum)); + const states = this.getElementState(idOf(datum)); this.computeElementStatesStyle(elementType, states, { datum, index, elementData }); }); }); @@ -359,30 +263,13 @@ export class ElementController { { originalStyle, modifiedStyle, - states: this.getElementStates(id), + states: this.getElementState(id), ...context, }, ); }; } - /** - * 获取状态为指定状态的元素 - * - * Get elements with the specified state - * @param state - 状态或状态数组 | state or state array - * @returns 元素 id 与元素实例的键值对 | key-value pairs of element id and element instance - */ - public getElementsByState(state: State | State[]): Record { - return Object.fromEntries( - Object.entries(this.elementState) - .filter(([, states]) => { - return (Array.isArray(state) ? state : [state]).every((s) => states.includes(s)); - }) - .map(([id]) => [id, this.elementMap[id]]), - ); - } - /** * 获取边端点连接上下文 * @@ -420,22 +307,12 @@ export class ElementController { // 优先级(从低到高) Priority (from low to high): const themeStyle = this.getThemeStyle(elementType); const paletteStyle = this.getPaletteStyle(id); - const dataStyle = this.getDataStyle(elementType, id); + const dataStyle = this.getDataStyle(id); const defaultStyle = this.getDefaultStyle(id); - const themeStateStyle = this.getThemeStateStyle(elementType, this.getElementStates(id)); + const themeStateStyle = this.getThemeStateStyle(elementType, this.getElementState(id)); const stateStyle = this.getStateStyle(id); - const runtimeStyle = this.getRuntimeStyle(id); - const style = Object.assign( - {}, - themeStyle, - paletteStyle, - dataStyle, - defaultStyle, - themeStateStyle, - stateStyle, - runtimeStyle, - ); + const style = Object.assign({}, themeStyle, paletteStyle, dataStyle, defaultStyle, themeStateStyle, stateStyle); if (elementType === 'edge') { Object.assign(style, this.getEdgeEndsContext(id)); @@ -452,20 +329,45 @@ export class ElementController { return style; } - // ---------- Render API ---------- - /** * 开始绘制流程 * * start render process */ - public async draw() { + public async draw(drawContext: DrawContext = { animation: true }) { + const drawData = this.computeDrawData(); + if (!drawData) return; + this.init(); + + // 计算样式 / Calculate style + this.computeStyle(); + + // 创建渲染任务 / Create render task + const { add, update, remove } = drawData; + const destroyTasks = this.getDestroyTasks(remove, drawContext); + const createTasks = this.getCreateTasks(add, drawContext); + const updateTasks = this.getUpdateTasks(update, drawContext); + + await executeAnimatableTasks( + [...destroyTasks, ...createTasks, ...updateTasks], + drawContext.silence + ? {} + : { + before: () => this.emit(new GraphLifeCycleEvent(GraphEvent.BEFORE_DRAW)), + beforeAnimate: (animation) => + this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.DRAW, animation, drawData)), + afterAnimate: (animation) => + this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.DRAW, animation, drawData)), + after: () => this.emit(new GraphLifeCycleEvent(GraphEvent.AFTER_DRAW)), + }, + )?.finished; + } + + private computeDrawData() { const { model } = this.context; const tasks = reduceDataChanges(model.getChanges()); - if (tasks.length === 0) return; - - this.init(); + if (tasks.length === 0) return null; const { NodeAdded = [], @@ -479,83 +381,80 @@ export class ElementController { ComboRemoved = [], } = groupBy(tasks, (change) => change.type) as unknown as Record<`${ChangeTypeEnum}`, DataChange[]>; - const dataOf = (data: DataChange[]) => data.map((datum) => datum.value) as T[]; - - // 计算要新增的元素 / compute elements to add - const nodesToAdd = dataOf(NodeAdded); - const edgesToAdd = dataOf(EdgeAdded); - const combosToAdd = dataOf(ComboAdded); - - // 计算要更新的元素 / compute elements to update - const nodesToUpdate = dataOf(NodeUpdated); - const edgesToUpdate = dataOf(EdgeUpdated); - const combosToUpdate = dataOf(ComboUpdated); - - this.initElementState({ nodes: nodesToUpdate, edges: edgesToUpdate, combos: combosToUpdate }); - - // 计算要删除的元素 / compute elements to remove - const nodesToRemove = dataOf(NodeRemoved); - const edgesToRemove = dataOf(EdgeRemoved); - const combosToRemove = dataOf(ComboRemoved); - - // 如果更新了节点,需要更新连接的边 - // If the node is updated, the connected edge and the combo it is in need to be updated - // TODO 待优化,仅考虑影响边更新的属性,如 x, y, size 等 - nodesToUpdate - .map((node) => model.getRelatedEdgesData(idOf(node))) - .flat() - .forEach((edge) => edgesToUpdate.push(edge)); - - // 如果操作(新增/更新/移除)了节点或 combo,需要更新相对应的 combo - // If nodes or combos are operated (added/updated/removed), the related combo needs to be updated - model - .getComboData( - [ - ...nodesToAdd, - ...nodesToUpdate, - ...nodesToRemove, - ...combosToAdd, - ...combosToUpdate, - ...combosToRemove, - ].reduce((acc, curr) => { - const parentId = curr?.style?.parentId; - if (parentId) acc.push(parentId); - return acc; - }, [] as ID[]), - ) - .forEach((combo) => combosToUpdate.push(combo)); + const dataOf = (data: DataChange[]) => + new Map( + data.map((datum) => { + const data = datum.value; + return [idOf(data), data] as [ID, T]; + }), + ); - // 计算样式 / Calculate style - this.computeStyle(); + const input: FlowData = { + add: { + nodes: dataOf(NodeAdded), + edges: dataOf(EdgeAdded), + combos: dataOf(ComboAdded), + }, + update: { + nodes: dataOf(NodeUpdated), + edges: dataOf(EdgeUpdated), + combos: dataOf(ComboUpdated), + }, + remove: { + nodes: dataOf(NodeRemoved), + edges: dataOf(EdgeRemoved), + combos: dataOf(ComboRemoved), + }, + }; - // 创建渲染任务 / Create render task - const renderContext = { animation: true }; + const output = [this.updateRelatedEdgeFlow, this.updateRelatedComboFlow].reduce((data, flow) => flow(data), input); - const dataToDestroy = { nodes: nodesToRemove, edges: edgesToRemove, combos: combosToRemove }; - const destroyTasks = this.getDestroyTasks(dataToDestroy, renderContext); + return output; + } - const dataToCreate = { nodes: nodesToAdd, edges: edgesToAdd, combos: combosToAdd }; - const createTasks = this.getCreateTasks(dataToCreate, renderContext); + /** + * 如果更新了节点,需要更新连接的边 + * If the node is updated, the connected edge and the combo it is in need to be updated + */ + private updateRelatedEdgeFlow: Flow = (input) => { + const { model } = this.context; + const { + update: { nodes, edges }, + } = input; - const dataToUpdate = { - nodes: nodesToUpdate, - edges: deduplicate(edgesToUpdate, idOf), - combos: deduplicate(combosToUpdate, idOf), - }; - const updateTasks = this.getUpdateTasks(dataToUpdate, renderContext); + nodes.forEach((_, id) => { + const relatedEdgesData = model.getRelatedEdgesData(id); + relatedEdgesData.forEach((edge) => edges.set(idOf(edge), edge)); + }); - const diffData = { create: dataToCreate, update: dataToUpdate, destroy: dataToDestroy }; - return executeAnimatableTasks([...destroyTasks, ...createTasks, ...updateTasks], { - before: () => this.emit(new GraphLifeCycleEvent(GraphEvent.BEFORE_DRAW)), - beforeAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.DRAW, animation, diffData)), - afterAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.DRAW, animation, diffData)), - after: () => this.emit(new GraphLifeCycleEvent(GraphEvent.AFTER_DRAW)), - })?.finished.then(() => {}); - } + return input; + }; + + /** + * 如果操作(新增/更新/移除)了节点或 combo,需要更新相对应的 combo + * If nodes or combos are operated (added/updated/removed), the related combo needs to be updated + */ + private updateRelatedComboFlow: Flow = (input) => { + const { add, update, remove } = input; + const { model } = this.context; + + const comboIds = [add.nodes, update.nodes, remove.nodes, add.combos, update.combos, remove.combos].reduce( + (acc, data) => { + data.forEach((datum) => { + const parentId = parentIdOf(datum); + if (parentId !== undefined) acc.push(parentId); + }); + return acc; + }, + [] as ID[], + ); - private createElement(elementType: ElementType, datum: ElementDatum, context: RenderContext) { + model.getComboData(comboIds).forEach((combo, index) => update.combos.set(comboIds[index], combo)); + + return input; + }; + + private createElement(elementType: ElementType, datum: ElementDatum, context: DrawContext) { const { animator } = context; const id = idOf(datum); const currentShape = this.getElement(id); @@ -581,11 +480,9 @@ export class ElementController { return () => animator?.(id, shape, { ...shape.attributes, opacity: 0 }) || null; } - private getCreateTasks(data: GraphData, context: Omit): AnimatableTask[] { - if (isEmptyData(data)) return []; - - const { nodes = [], edges = [], combos = [] } = data; - const iteration: [ElementType, ElementData][] = [ + private getCreateTasks(data: ProcedureData, context: Omit): AnimatableTask[] { + const { nodes, edges, combos } = data; + const iteration: [ElementType, Map][] = [ ['node', nodes], ['edge', edges], ['combo', combos], @@ -593,7 +490,7 @@ export class ElementController { const tasks: AnimatableTask[] = []; iteration.forEach(([elementType, elementData]) => { - if (elementData.length === 0) return; + if (elementData.size === 0) return; const animator = this.getAnimationExecutor(elementType, 'enter'); elementData.forEach((datum) => tasks.push(() => this.createElement(elementType, datum, { ...context, animator })), @@ -603,7 +500,26 @@ export class ElementController { return tasks; } - private updateElement(elementType: ElementType, datum: ElementDatum, context: RenderContext) { + /** + * 由于 show 和 hide 的时序存在差异 + * - show: 立即将元素显示出来,然后执行动画 + * - hide: 先执行动画,然后隐藏元素 + * + * 如果在调用 hide 后立即调用 show,hide 动画完成后会将元素隐藏 + * + * 因此需要缓存元素最新的可见性 + * + * Due to the difference in the timing of show and hide + * - show: immediately shows the element and then executes the animation + * - hide: executes the animation first, and then hides the element + * + * If show is called immediately after hide,it hide the element after hide animation completed + * + * Therefore, the latest visibility of the element needs to be cached + */ + private latestElementVisibilityMap: WeakMap = new WeakMap(); + + private updateElement(elementType: ElementType, datum: ElementDatum, context: DrawContext) { const { animator } = context; const id = idOf(datum); @@ -621,88 +537,39 @@ export class ElementController { }; } - const originalStyle = { ...shape.attributes }; - - updateStyle(shape, style); - - return () => animator?.(id, shape, originalStyle) || null; - } + // 如果是可见性更新 / If it is a visibility update + if (context.stage === 'visibility' && 'visibility' in style) { + // 缓存原始透明度 / Cache original opacity + if (!hasCachedStyle(shape, 'opacity')) cacheStyle(shape, 'opacity'); - public updateNodeLikePosition(positions: Positions, animation: boolean = true, edgeIds: ID[] = []) { - if (Object.keys(positions).length === 0) return null; - const { model } = this.context; - - const animationsFilter: AnimationContext['animationsFilter'] = (animation) => !animation.shape; - const nodeAnimator = this.getAnimationExecutor('node', 'update', animation, { animationsFilter }); - const comboAnimator = this.getAnimationExecutor('combo', 'update', animation, { animationsFilter }); - - const nodeTasks: AnimatableTask[] = []; - Object.entries(positions).forEach(([id, [x, y, z]]) => { - const element = this.getElement(id); - const elementType = this.context.model.isCombo(id) ? 'combo' : 'node'; - if (!element) return; - // 更新原生位置属性,避免执行 render 以及 animation 调用 getXxxStyle 流程 / Update the native position attribute to avoid executing the render and animation calls to getXxxStyle - const animator = elementType === 'combo' ? comboAnimator : nodeAnimator; - const originalPosition = pick(element.attributes, ['x', 'y', 'z']); - const modifiedPosition = { x, y, z }; - - nodeTasks.push(() => { - this.setRuntimeStyle(id, modifiedPosition); - element.attr({ x, y, z }); - return () => animator(id, element, originalPosition); - }); - }); + const originalOpacity = getCachedStyle(shape, 'opacity') ?? inferDefaultValue('opacity'); + this.latestElementVisibilityMap.set(shape, style.visibility); - const edgeTasks = this.getUpdateTasks( - { - nodes: [], - edges: model.getEdgeData( - Object.keys(positions).reduce( - (acc, id) => { - if (!model.isCombo(id)) { - model.getRelatedEdgesData(id).forEach((edge) => acc.push(idOf(edge))); - } - return acc; + // show + if (style.visibility !== 'hidden') { + updateStyle(shape, { visibility: 'visible' }); + return () => animator?.(id, shape, { ...shape.attributes, opacity: 0 }, { opacity: originalOpacity }) || null; + } + // hide + else if (style.visibility === 'hidden') { + return () => + withAnimationCallbacks( + animator?.(id, shape, { ...shape.attributes, opacity: originalOpacity }, { opacity: 0 }) || null, + { + after: () => updateStyle(shape, { visibility: this.latestElementVisibilityMap.get(shape) }), }, - [...edgeIds], - ), - ), - combos: [], - }, - { animation }, - ); - - return executeAnimatableTasks([...nodeTasks, ...edgeTasks], { - before: () => this.emit(new ElementTranslateEvent(GraphEvent.BEFORE_ELEMENT_TRANSLATE, positions)), - beforeAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_TRANSLATE, animation, positions)), - afterAnimate: (animation) => - this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_TRANSLATE, animation, positions)), - after: () => this.emit(new ElementTranslateEvent(GraphEvent.AFTER_ELEMENT_TRANSLATE, positions)), - }); - } - - /** - * 基于布局结果进行更新 - * - * Update based on layout results - */ - public updateByLayoutResult(layoutResult: LayoutResult, animation: boolean = true) { - const { nodes: nodeLikeResults, edges: edgeResults } = layoutResult; - if (Object.keys(nodeLikeResults).length === 0 && Object.keys(edgeResults).length === 0) return null; - - Object.entries(edgeResults).forEach(([id, style]) => { - this.setRuntimeStyle(id, style); - }); + ); + } + } - this.updateNodeLikePosition(nodeLikeResults, animation, Object.keys(edgeResults)); + const originalStyle = { ...shape.attributes }; + updateStyle(shape, style); + return () => animator?.(id, shape, originalStyle) || null; } - private getUpdateTasks(data: GraphData, context: Omit): AnimatableTask[] { - if (isEmptyData(data)) return []; - - const { nodes = [], edges = [], combos = [] } = data; - const iteration: [ElementType, ElementData][] = [ + private getUpdateTasks(data: ProcedureData, context: Omit): AnimatableTask[] { + const { nodes, edges, combos } = data; + const iteration: [ElementType, Map][] = [ ['node', nodes], ['edge', edges], ['combo', combos], @@ -711,8 +578,8 @@ export class ElementController { const { animation } = context; const tasks: AnimatableTask[] = []; iteration.forEach(([elementType, elementData]) => { - if (elementData.length === 0) return []; - const animator = this.getAnimationExecutor(elementType, 'update', animation); + if (elementData.size === 0) return []; + const animator = this.getAnimationExecutor(elementType, context.stage || 'update', animation); elementData.forEach((datum) => tasks.push(() => this.updateElement(elementType, datum, { ...context, animator })), ); @@ -721,7 +588,7 @@ export class ElementController { return tasks; } - private destroyElement(datum: ElementDatum, context: RenderContext) { + private destroyElement(datum: ElementDatum, context: DrawContext) { const { animator } = context; const id = idOf(datum); const element = this.elementMap[id]; @@ -739,11 +606,9 @@ export class ElementController { }; } - private getDestroyTasks(data: GraphData, context: Omit): AnimatableTask[] { - if (isEmptyData(data)) return []; - - const { nodes = [], edges = [], combos = [] } = data; - const iteration: [ElementType, ElementData][] = [ + private getDestroyTasks(data: ProcedureData, context: Omit): AnimatableTask[] { + const { nodes, edges, combos } = data; + const iteration: [ElementType, Map][] = [ ['combo', combos], ['edge', edges], ['node', nodes], @@ -751,7 +616,7 @@ export class ElementController { const tasks: AnimatableTask[] = []; iteration.forEach(([elementType, elementData]) => { - if (elementData.length === 0) return []; + if (elementData.size === 0) return []; const animator = this.getAnimationExecutor(elementType, 'exit'); elementData.forEach((datum) => tasks.push(() => this.destroyElement(datum, { ...context, animator }))); }); @@ -765,132 +630,60 @@ export class ElementController { delete this.paletteStyle[id]; delete this.defaultStyle[id]; delete this.stateStyle[id]; - delete this.elementState[id]; delete this.elementMap[id]; delete this.shapeTypeMap[id]; - delete this.runtimeStyle[id]; } - public async setElementsVisibility(ids: ID[], visibility: BaseStyleProps['visibility'], animation?: boolean) { - if (ids.length === 0) return null; - - const isHide = visibility === 'hidden'; - const nodes: ID[] = []; - const edges: ID[] = []; - const combos: ID[] = []; - - ids.forEach((id) => { - const elementType = this.context.model.getElementType(id); - if (elementType === 'node') nodes.push(id); - if (elementType === 'edge') edges.push(id); - if (elementType === 'combo') combos.push(id); - }); - - const iteration: [ElementType, ID[]][] = [ - ['node', nodes], - ['edge', edges], - ['combo', combos], - ]; - - const cacheOpacity = (element: DisplayObject) => { - if (isUndefined(getCachedStyle(element, 'opacity'))) cacheStyle(element, 'opacity'); - }; - - const show = (id: ID, element: DisplayObject, animator: AnimationExecutor) => { - const originalOpacity = - getCachedStyle(element, 'opacity') ?? element.style.opacity ?? inferDefaultValue('opacity'); - cacheOpacity(element); - // 由于 show 和 hide 的时序存在差异 - // - show: 立即将元素显示出来,然后执行动画 - // - hide: 先执行动画,然后隐藏元素 - // 如果在调用 hide 后立即调用 show,hide 动画完成后会将元素隐藏 - // 因此需要缓存元素最新的可见性 - // - // Due to the difference in the timing of show and hide - // - show: immediately shows the element and then executes the animation - // - hide: executes the animation first, and then hides the element - // If show is called immediately after hide,it hide the element after hide animation completed - // Therefore, the latest visibility of the element needs to be cached - setCacheStyle(element, 'visibility', visibility); - setVisibility(element, visibility); - return animator(id, element, { ...element.attributes, opacity: 0 }, { opacity: originalOpacity }); - }; - - const hide = (id: ID, element: DisplayObject, animator: AnimationExecutor) => { - const originalOpacity = - getCachedStyle(element, 'opacity') ?? element.style.opacity ?? inferDefaultValue('opacity'); - - cacheOpacity(element); - setCacheStyle(element, 'visibility', visibility); - const animation = animator(id, element, { ...element.attributes, opacity: originalOpacity }, { opacity: 0 }); - return withAnimationCallbacks(animation, { - after: () => setVisibility(element, getCachedStyle(element, 'visibility')), - }); - }; - - const tasks: AnimatableTask[] = []; - iteration.forEach(([elementType, elementIds]) => { - if (elementIds.length === 0) return; - const animator = this.getAnimationExecutor(elementType, isHide ? 'hide' : 'show', animation); - elementIds.forEach((id) => { - const element = this.getElement(id); - if (element) { - tasks.push(() => () => (isHide ? hide : show)(id, element, animator)); - } - }); - }); - - return executeAnimatableTasks(tasks, { - before: () => - this.emit(new ElementVisibilityChangeEvent(GraphEvent.BEFORE_ELEMENT_VISIBILITY_CHANGE, ids, visibility)), - beforeAnimate: (animation) => - this.emit( - new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_VISIBILITY_CHANGE, animation, { - ids, - visibility, - }), - ), - afterAnimate: (animation) => - this.emit( - new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_VISIBILITY_CHANGE, animation, { - ids, - visibility, - }), - ), - after: () => - this.emit(new ElementVisibilityChangeEvent(GraphEvent.AFTER_ELEMENT_VISIBILITY_CHANGE, ids, visibility)), - }); - } - - public setElementZIndex(id: ID, zIndex: ZIndex) { - const element = this.getElement(id); - if (!element) return; - - this.emit(new ElementZIndexChangeEvent(GraphEvent.BEFORE_ELEMENT_Z_INDEX_CHANGE, id, zIndex)); - if (typeof zIndex === 'number') element.attr({ zIndex }); - else { - const elementType = this.context.model.getElementType(id); - const childNodes = this.container[elementType].childNodes as DisplayObject[]; - const delta = zIndex === 'front' ? 1 : -1; - const parsedZIndex = - Math[zIndex === 'front' ? 'max' : 'min']( - ...childNodes.map((node) => node.attributes.zIndex ?? inferDefaultValue('zIndex')), - ) + delta; - element.attr({ zIndex: parsedZIndex }); - } - this.emit(new ElementZIndexChangeEvent(GraphEvent.AFTER_ELEMENT_Z_INDEX_CHANGE, id, zIndex)); + public getElementZIndexRange(elementType: ElementType) { + const childNodes = this.container[elementType].childNodes as DisplayObject[]; + const zIndexes = childNodes.map((node) => node.attributes.zIndex ?? inferDefaultValue('zIndex')).sort(); + return [zIndexes.at(0), zIndexes.at(-1)] as [number, number]; } public destroy() { Object.values(this.container).forEach((container) => container.destroy()); this.elementMap = {}; this.shapeTypeMap = {}; - this.runtimeStyle = {}; this.defaultStyle = {}; - this.elementState = {}; this.stateStyle = {}; this.paletteStyle = {}; // @ts-expect-error force delete delete this.context; } } + +type AnimationExecutor = ( + id: ID, + shape: DisplayObject, + originalStyle: Record, + modifiedStyle?: Record, +) => IAnimation | null; + +type DrawContext = { + animator?: AnimationExecutor; + /** 是否使用动画,默认为 true | Whether to use animation, default is true */ + animation: boolean; + /** 当前绘制阶段 | Current draw stage */ + stage?: AnimationStage; + /** 是否不抛出事件 | Whether not to throw events */ + silence?: boolean; +}; + +/** + * 在 Element Controller 中,为了提高查询性能,统一使用 Map 存储数据 + * + * In Element Controller, in order to improve query performance, use Map to store data uniformly + */ +type ProcedureData = { + nodes: Map; + edges: Map; + combos: Map; +}; + +type FlowData = { + add: ProcedureData; + update: ProcedureData; + remove: ProcedureData; +}; + +type Flow = (input: FlowData) => FlowData; diff --git a/packages/g6/src/runtime/graph.ts b/packages/g6/src/runtime/graph.ts index 8ba39c86e37..000f53ab941 100644 --- a/packages/g6/src/runtime/graph.ts +++ b/packages/g6/src/runtime/graph.ts @@ -1,7 +1,7 @@ import EventEmitter from '@antv/event-emitter'; import type { AABB, BaseStyleProps, DataURLOptions } from '@antv/g'; import type { ID } from '@antv/graphlib'; -import { debounce, isEqual, isFunction, isNumber, isString, omit } from '@antv/util'; +import { debounce, isEqual, isFunction, isNumber, isObject, isString, omit } from '@antv/util'; import { GraphEvent } from '../constants'; import type { BehaviorOptions, @@ -29,14 +29,13 @@ import type { PartialGraphData, PartialNodeLikeData, Point, - Positions, + Position, State, Vector2, ViewportAnimationEffectTiming, - ZIndex, } from '../types'; import { sizeOf } from '../utils/dom'; -import { GraphLifeCycleEvent, emit } from '../utils/event'; +import { ElementStateChangeEvent, GraphLifeCycleEvent, emit } from '../utils/event'; import { parsePoint, toPointObject } from '../utils/point'; import { add, subtract } from '../utils/vector'; import { BehaviorController } from './behavior'; @@ -303,14 +302,9 @@ export class Graph extends EventEmitter { public getElementDataByState(elementType: 'edge', state: State): EdgeData[]; public getElementDataByState(elementType: 'combo', state: State): ComboData[]; public getElementDataByState(elementType: ElementType, state: State): ElementDatum[] { - const ids = Object.entries(this.context.element!.elementState) - .filter(([id, states]) => this.context.model.getElementType(id) === elementType && states.includes(state)) - .map(([id]) => id); - return this.context.model.getElementsData(ids); + return this.context.model.getElementDataByState(elementType, state); } - // ---------- end core API ---------- - private createCanvas() { if (this.context.canvas) return this.context.canvas; @@ -407,7 +401,6 @@ export class Graph extends EventEmitter { this.destroyed = true; } - // ---------- Runtime API ---------- public getCanvas(): Canvas { return this.context.canvas; } @@ -582,22 +575,83 @@ export class Graph extends EventEmitter { return subtract([0, 0], this.getCanvasByViewport([0, 0])); } - public translateElementBy(offsets: Positions, animation?: boolean): void { - const positions = Object.entries(offsets).reduce((acc, [id, offset]) => { - const curr = this.getElementPosition(id); - const next = add(curr, [...offset, 0].slice(0, 3) as Point); - acc[id] = next; - return acc; - }, {} as Positions); + /** + * 将元素平移指定距离 + * + * Translate the element by the specified distance + * @param id - 元素 ID | element ID + * @param offset - 偏移量 | offset + * @param animation - 动画配置 | animation configuration + */ + public async translateElementBy(id: ID, offset: Position, animation?: boolean): Promise; + /** + * 批量将元素平移指定距离 + * + * Batch translate elements by the specified distance + * @param offsets - 偏移量配置 | offset configuration + * @param animation - 动画配置 | animation configuration + */ + public async translateElementBy(offsets: Record, animation?: boolean): Promise; + public async translateElementBy( + args1: ID | Record, + args2?: Position | boolean, + args3: boolean = true, + ): Promise { + const [config, animation] = isObject(args1) + ? [args1, (args2 as boolean) ?? true] + : [{ [args1 as ID]: args2 as Position }, args3]; + + const positions = Object.entries(config).reduce( + (acc, [id, offset]) => { + const curr = this.getElementPosition(id); + const next = add(curr, [...offset, 0].slice(0, 3) as Point); + acc[id] = next; + return acc; + }, + {} as Record, + ); - this.translateElementTo(positions, animation); + await this.translateElementTo(positions, animation); } - public translateElementTo(positions: Positions, animation?: boolean): void { - this.context.element!.updateNodeLikePosition(positions, animation); + /** + * 将元素平移至指定位置 + * + * Translate the element to the specified position + * @param id - 元素 ID | element ID + * @param position - 指定位置 | specified position + * @param animation - 动画配置 | animation configuration + */ + public async translateElementTo(id: ID, position: Position, animation?: boolean): Promise; + /** + * 批量将元素平移至指定位置 + * + * Batch translate elements to the specified position + * @param positions - 位置配置 | position configuration + * @param animation - 动画配置 | animation configuration + */ + public async translateElementTo(positions: Record, animation?: boolean): Promise; + public async translateElementTo( + args1: ID | Record, + args2?: boolean | Position, + args3: boolean = true, + ): Promise { + const dataToUpdate: Required = { nodes: [], edges: [], combos: [] }; + const [config, animation] = isObject(args1) + ? [args1, (args2 as boolean) ?? true] + : [{ [args1 as ID]: args2 as Position }, args3]; + + Object.entries(config).forEach(([id, [x, y, z = 0]]) => { + const elementType = this.getElementType(id); + dataToUpdate[`${elementType}s`].push({ id, style: { x, y, z } }); + }); + + this.updateData(dataToUpdate); + + await this.context.element!.draw({ animation }); } - public getElementPosition(id: ID): Point { + public getElementPosition(id: ID): Position { const element = this.context.element!.getElement(id)!; const { x = 0, y = 0, z = 0 } = element.style; return [x, y, z]; @@ -607,45 +661,222 @@ export class Graph extends EventEmitter { return omit(this.context.element!.getElement(id)!.attributes, ['context']); } + /** + * 设置元素可见性 + * + * Set element visibility + * @param id - 元素 ID | element ID + * @param visibility - 可见性 | visibility + * @param animation - 动画配置 | animation configuration + */ public async setElementVisibility( - id: ID | ID[], + id: ID, visibility: BaseStyleProps['visibility'], animation?: boolean, + ): Promise; + /** + * 批量设置元素可见性 + * + * Batch set element visibility + * @param visibility - 可见性配置 | visibility configuration + * @param animation - 动画配置 | animation configuration + */ + public async setElementVisibility( + visibility: Record, + animation?: boolean, + ): Promise; + public async setElementVisibility( + args1: ID | Record, + args2?: boolean | BaseStyleProps['visibility'], + args3: boolean = true, ): Promise { - await this.context.element!.setElementsVisibility(Array.isArray(id) ? id : [id], visibility, animation); + const [config, animation] = isObject(args1) + ? [args1, (args2 as boolean) ?? true] + : [{ [args1]: args2 as BaseStyleProps['visibility'] }, args3]; + + const dataToUpdate: Required = { nodes: [], edges: [], combos: [] }; + Object.entries(config).forEach(([id, value]) => { + const elementType = this.getElementType(id); + dataToUpdate[`${elementType}s`].push({ id, style: { visibility: value } }); + }); + this.updateData(dataToUpdate); + + await this.context.element!.draw({ animation, stage: 'visibility' }); + } + + /** + * 显示元素 + * + * Show element + * @param id - 元素 ID | element ID + * @param animation - 动画配置 | animation configuration + */ + public async showElement(id: ID | ID[], animation?: boolean): Promise { + const ids = Array.isArray(id) ? id : [id]; + await this.setElementVisibility( + Object.fromEntries(ids.map((_id) => [_id, 'visible'] as [ID, BaseStyleProps['visibility']])), + animation, + ); + } + + /** + * 隐藏元素 + * + * Hide element + * @param id - 元素 ID | element ID + * @param animation - 动画配置 | animation configuration + */ + public async hideElement(id: ID | ID[], animation?: boolean): Promise { + const ids = Array.isArray(id) ? id : [id]; + await this.setElementVisibility( + Object.fromEntries(ids.map((_id) => [_id, 'hidden'] as [ID, BaseStyleProps['visibility']])), + animation, + ); } + /** + * 获取元素可见性 + * + * Get element visibility + * @param id - 元素 ID | element ID + * @returns 元素可见性 | element visibility + */ public getElementVisibility(id: ID): BaseStyleProps['visibility'] { const element = this.context.element!.getElement(id)!; - return element.style.visibility ?? 'visible'; + return element?.style?.visibility ?? 'visible'; } - public setElementZIndex(id: ID | ID[], zIndex: ZIndex): void { - const ids = Array.isArray(id) ? id : [id]; - ids.forEach((id) => { - this.context.element!.setElementZIndex(id, zIndex); + /** + * 设置元素层级 + * + * Set element z-index + * @param id - 元素 ID | element ID + * @param zIndex - 层级 | z-index + */ + public async setElementZIndex(id: ID, zIndex: number): Promise; + /** + * 批量设置元素层级 + * + * Batch set element z-index + * @param zIndex - 层级配置 | z-index configuration + */ + public async setElementZIndex(zIndex: Record): Promise; + public async setElementZIndex(args1: ID | Record, args2?: number): Promise { + const dataToUpdate: Required = { nodes: [], edges: [], combos: [] }; + const config = isObject(args1) ? args1 : { [args1 as ID]: args2 as number }; + + Object.entries(config).forEach(([id, value]) => { + const elementType = this.getElementType(id); + dataToUpdate[`${elementType}s`].push({ id, style: { zIndex: value } }); }); + + this.updateData(dataToUpdate); + + await this.context.element!.draw(); } + /** + * 将元素置于最顶层 + * + * Bring the element to the front + * @param id - 元素 ID | element ID + */ + public async frontElement(id: ID | ID[]): Promise { + const ids = Array.isArray(id) ? id : [id]; + + await this.setElementZIndex( + Object.fromEntries( + ids.map((_id) => { + const elementType = this.getElementType(_id); + const [, max] = this.context.element!.getElementZIndexRange(elementType); + const parsedZIndex = max + 1; + return [_id, parsedZIndex]; + }), + ), + ); + } + + /** + * 将元素置于最底层 + * + * Send the element to the back + * @param id - 元素 ID | element ID + */ + public async backElement(id: ID | ID[]): Promise { + const ids = Array.isArray(id) ? id : [id]; + + await this.setElementZIndex( + Object.fromEntries( + ids.map((_id) => { + const elementType = this.getElementType(_id); + const [min] = this.context.element!.getElementZIndexRange(elementType); + const parsedZIndex = min - 1; + return [_id, parsedZIndex]; + }), + ), + ); + } + + /** + * 获取元素层级 + * + * Get element z-index + * @param id - 元素 ID | element ID + * @returns 元素层级 | element z-index + */ public getElementZIndex(id: ID): BaseStyleProps['zIndex'] { const element = this.context.element!.getElement(id)!; return element.style.zIndex ?? 0; } - public setElementState(id: ID | ID[], state: CallableValue): void { - const states = (Array.isArray(id) ? id : [id]).reduce( - (acc, i) => { - const staticState = isFunction(state) ? state(this.getElementState(i)) : state; - acc[i] = Array.isArray(staticState) ? staticState : [staticState]; - return acc; - }, - {} as Record, - ); - this.context.element!.setElementsState(states); + /** + * 设置元素状态 + * + * Set element state + * @param id - 元素 ID | element ID + * @param state - 状态 | state + * @param animation - 动画配置 | animation configuration + */ + public async setElementState(id: ID, state: State | State[], animation?: boolean): Promise; + /** + * 批量设置元素状态 + * + * Batch set element state + * @param state - 状态配置 | state configuration + * @param animation - 动画配置 | animation configuration + */ + public async setElementState(state: Record, animation?: boolean): Promise; + public async setElementState( + args1: ID | Record, + args2?: boolean | State | State[], + args3: boolean = true, + ): Promise { + const [config, animation] = isObject(args1) + ? [args1, (args2 as boolean) ?? true] + : [{ [args1]: args2 as State | State[] }, args3]; + + emit(this, new ElementStateChangeEvent(GraphEvent.BEFORE_ELEMENT_STATE_CHANGE, config)); + + const dataToUpdate: Required = { nodes: [], edges: [], combos: [] }; + Object.entries(config).forEach(([id, value]) => { + const elementType = this.getElementType(id); + dataToUpdate[`${elementType}s`].push({ id, style: { states: Array.isArray(value) ? value : [value] } }); + }); + this.updateData(dataToUpdate); + + await this.context.element!.draw({ animation }); + emit(this, new ElementStateChangeEvent(GraphEvent.AFTER_ELEMENT_STATE_CHANGE, config)); } + /** + * 获取元素状态 + * + * Get element state + * @param id - 元素 ID | element ID + * @returns 元素状态 | element state + */ public getElementState(id: ID): State[] { - return this.context.element!.getElementStates(id); + return this.context.model.getElementState(id); } public getElementRenderBounds(id: ID): AABB { diff --git a/packages/g6/src/runtime/layout.ts b/packages/g6/src/runtime/layout.ts index 0f0404c0b23..5699bdde46a 100644 --- a/packages/g6/src/runtime/layout.ts +++ b/packages/g6/src/runtime/layout.ts @@ -9,12 +9,13 @@ import type { BaseLayoutOptions } from '../layouts/types'; import { getExtension } from '../registry'; import type { EdgeData, NodeData } from '../spec'; import type { STDLayoutOptions } from '../spec/layout'; -import type { NodeLikeData, Point, TreeData } from '../types'; +import type { NodeLikeData, PartialGraphData, Point, TreeData } from '../types'; import { getAnimation } from '../utils/animation'; import { isVisible } from '../utils/element'; import { GraphLifeCycleEvent, emit } from '../utils/event'; import { createTreeStructure } from '../utils/graphlib'; -import { isComboLayout, isPositionSpecified, isTreeLayout, pickLayoutResult } from '../utils/layout'; +import { isComboLayout, isPositionSpecified, isTreeLayout } from '../utils/layout'; +import { parsePoint } from '../utils/point'; import { parseSize } from '../utils/size'; import { dfs } from '../utils/traverse'; import { add } from '../utils/vector'; @@ -88,7 +89,7 @@ export class LayoutController { [] as LayoutMapping['nodes'], ); - this.updateElement({ nodes: positions, edges: [] }, false); + this.updateElementPosition({ nodes: positions, edges: [] }, false); } public async layout() { @@ -103,7 +104,7 @@ export class LayoutController { const result = await this.stepLayout(model, { ...this.presetOptions, ...options }); if (!options.animation) { - this.updateElement(result, false); + this.updateElementPosition(result, false); } } emit(graph, new GraphLifeCycleEvent(GraphEvent.AFTER_LAYOUT)); @@ -132,7 +133,7 @@ export class LayoutController { if (animation) { return await layout.execute(model, { onTick: (tickData: LayoutMapping) => { - this.updateElement(tickData, false); + this.updateElementPosition(tickData, false); }, }); } @@ -146,7 +147,7 @@ export class LayoutController { // 无迭代的布局,直接返回终态位置 / Layout without iteration, return final position directly const layoutResult = await layout.execute(model); if (animation) { - this.updateElement(layoutResult, animation); + this.updateElementPosition(layoutResult, animation); } return layoutResult; } @@ -186,7 +187,7 @@ export class LayoutController { }); if (animation) { - this.updateElement(layoutResult, animation); + this.updateElementPosition(layoutResult, animation); } return layoutResult; @@ -347,10 +348,24 @@ export class LayoutController { return new Ctor(config); } - private updateElement(layoutData: LayoutMapping, animation: boolean) { - const { element } = this.context; + private updateElementPosition(layoutData: LayoutMapping, animation: boolean) { + const { model, element } = this.context; if (!element) return null; - element.updateByLayoutResult(pickLayoutResult(layoutData), animation); + + const { nodes, edges } = layoutData; + const dataToUpdate: Required = { nodes: [], edges: [], combos: [] }; + nodes.forEach(({ id, data: { x, y, z = 0 } }) => { + dataToUpdate.nodes.push({ id, style: { x, y, z } }); + }); + + edges.forEach(({ id, data: { controlPoints } }) => { + if (controlPoints?.length) { + dataToUpdate.edges.push({ id, style: { controlPoints: controlPoints.map(parsePoint) } }); + } + }); + + model.updateData(dataToUpdate); + return element.draw({ animation, silence: true }); } public destroy() { diff --git a/packages/g6/src/spec/element/animation.ts b/packages/g6/src/spec/element/animation.ts index f0bbe8f1b5a..79ae3ffbf8d 100644 --- a/packages/g6/src/spec/element/animation.ts +++ b/packages/g6/src/spec/element/animation.ts @@ -13,4 +13,4 @@ export type AnimationOptions = * * Animation stage */ -export type AnimationStage = 'enter' | 'exit' | 'update' | 'show' | 'hide' | 'transform'; +export type AnimationStage = 'enter' | 'update' | 'exit' | 'visibility'; diff --git a/packages/g6/src/themes/dark.ts b/packages/g6/src/themes/dark.ts index c7887a839d0..1f1934d9142 100644 --- a/packages/g6/src/themes/dark.ts +++ b/packages/g6/src/themes/dark.ts @@ -76,8 +76,7 @@ export const dark: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [{ fields: ['x', 'y', 'fill', 'stroke'] }], }, }, @@ -127,8 +126,7 @@ export const dark: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [{ fields: ['stroke'] }, { fields: ['path'], shape: 'key' }], }, }, @@ -184,8 +182,7 @@ export const dark: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [ { fields: ['cx', 'cy', 'r', 'x', 'y', 'width', 'height'], shape: 'key' }, { fields: ['x', 'y'], shape: 'label' }, diff --git a/packages/g6/src/themes/light.ts b/packages/g6/src/themes/light.ts index c6af4e365ca..b9f082db625 100644 --- a/packages/g6/src/themes/light.ts +++ b/packages/g6/src/themes/light.ts @@ -76,8 +76,7 @@ export const light: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [{ fields: ['x', 'y', 'fill', 'stroke'] }], }, }, @@ -126,8 +125,7 @@ export const light: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [{ fields: ['stroke'] }, { fields: ['path'], shape: 'key' }], }, }, @@ -180,8 +178,7 @@ export const light: Theme = { animation: { enter: 'fade', exit: 'fade', - hide: 'fade', - show: 'fade', + visibility: 'fade', update: [{ fields: ['x', 'y'] }, { fields: ['r', 'width', 'height'], shape: 'key' }], }, }, diff --git a/packages/g6/src/types/index.ts b/packages/g6/src/types/index.ts index 9a5b4b415c4..ec7a998c053 100644 --- a/packages/g6/src/types/index.ts +++ b/packages/g6/src/types/index.ts @@ -10,7 +10,6 @@ export type * from './element'; export type * from './enum'; export type * from './event'; export type * from './graphlib'; -export type * from './layout'; export type * from './node'; export type * from './padding'; export type * from './placement'; @@ -23,4 +22,3 @@ export type * from './style'; export type * from './tree'; export type * from './vector'; export type * from './viewport'; -export type * from './z-index'; diff --git a/packages/g6/src/types/layout.ts b/packages/g6/src/types/layout.ts deleted file mode 100644 index 69b73b7b78d..00000000000 --- a/packages/g6/src/types/layout.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ID } from '@antv/graphlib'; -import type { Positions } from './position'; - -export type LayoutResult = { - nodes: Positions; - edges: Record>; -}; diff --git a/packages/g6/src/types/position.ts b/packages/g6/src/types/position.ts index e919971d649..56ae61f860a 100644 --- a/packages/g6/src/types/position.ts +++ b/packages/g6/src/types/position.ts @@ -1,6 +1,3 @@ -import type { ID } from '@antv/graphlib'; import type { Point } from './point'; export type Position = Point; - -export type Positions = Record; diff --git a/packages/g6/src/types/state.ts b/packages/g6/src/types/state.ts index 0b631097866..9876387e32a 100644 --- a/packages/g6/src/types/state.ts +++ b/packages/g6/src/types/state.ts @@ -1,5 +1 @@ -import type { ID } from '@antv/graphlib'; - export type State = string; - -export type States = Record; diff --git a/packages/g6/src/types/z-index.ts b/packages/g6/src/types/z-index.ts deleted file mode 100644 index 9abee4ab3ed..00000000000 --- a/packages/g6/src/types/z-index.ts +++ /dev/null @@ -1 +0,0 @@ -export type ZIndex = number | 'front' | 'back'; diff --git a/packages/g6/src/utils/cache.ts b/packages/g6/src/utils/cache.ts index 3af4c07ae81..9437b99a281 100644 --- a/packages/g6/src/utils/cache.ts +++ b/packages/g6/src/utils/cache.ts @@ -1,4 +1,7 @@ import type { DisplayObject } from '@antv/g'; +import { get, set } from '@antv/util'; + +const CacheTargetKey = 'cachedStyle'; const getStyleCacheKey = (name: string) => `__${name}__`; @@ -11,12 +14,9 @@ const getStyleCacheKey = (name: string) => `__${name}__`; */ export function cacheStyle(element: DisplayObject, name: string | string[]) { const names = Array.isArray(name) ? name : [name]; + if (!get(element, CacheTargetKey)) set(element, CacheTargetKey, {}); names.forEach((n) => { - if (n in element.attributes) { - Object.assign(element.attributes, { - [getStyleCacheKey(n)]: element.attributes[n], - }); - } + set(get(element, CacheTargetKey), getStyleCacheKey(n), element.attributes[n]); }); } @@ -29,7 +29,19 @@ export function cacheStyle(element: DisplayObject, name: string | string[]) { * @returns 样式值 | style value */ export function getCachedStyle(element: DisplayObject, name: string) { - return element.attributes[getStyleCacheKey(name)]; + return get(element, [CacheTargetKey, getStyleCacheKey(name)]); +} + +/** + * 是否有缓存的样式 + * + * Whether there is a cached style + * @param element - 图形元素 | graphic element + * @param name - 样式名 | style name + * @returns 是否有缓存的样式 | Whether there is a cached style + */ +export function hasCachedStyle(element: DisplayObject, name: string) { + return getStyleCacheKey(name) in (get(element, CacheTargetKey) || {}); } /** @@ -41,5 +53,6 @@ export function getCachedStyle(element: DisplayObject, name: string) { * @param value - 样式值 | style value */ export function setCacheStyle(element: DisplayObject, name: string, value: any) { - element.attributes[getStyleCacheKey(name)] = value; + // element.attributes[getStyleCacheKey(name)] = value; + set(element, [CacheTargetKey, getStyleCacheKey(name)], value); } diff --git a/packages/g6/src/utils/event/events.ts b/packages/g6/src/utils/event/events.ts index bddfb06dfcd..5de3710aadf 100644 --- a/packages/g6/src/utils/event/events.ts +++ b/packages/g6/src/utils/event/events.ts @@ -1,8 +1,8 @@ -import type { BaseStyleProps, IAnimation } from '@antv/g'; +import type { IAnimation } from '@antv/g'; import type { ID } from '@antv/graphlib'; import type { AnimationType, GraphEvent } from '../../constants'; import type { GraphData } from '../../spec'; -import type { Positions, States, TransformOptions, ZIndex } from '../../types'; +import type { State, TransformOptions } from '../../types'; export class BaseEvent { constructor(public type: string) {} @@ -63,36 +63,7 @@ export class ViewportEvent extends BaseEvent { export class ElementStateChangeEvent extends BaseEvent { constructor( type: GraphEvent.BEFORE_ELEMENT_STATE_CHANGE | GraphEvent.AFTER_ELEMENT_STATE_CHANGE, - public states: States, - ) { - super(type); - } -} - -export class ElementTranslateEvent extends BaseEvent { - constructor( - type: GraphEvent.BEFORE_ELEMENT_TRANSLATE | GraphEvent.AFTER_ELEMENT_TRANSLATE, - public positions: Positions, - ) { - super(type); - } -} - -export class ElementVisibilityChangeEvent extends BaseEvent { - constructor( - type: GraphEvent.BEFORE_ELEMENT_VISIBILITY_CHANGE | GraphEvent.AFTER_ELEMENT_VISIBILITY_CHANGE, - public ids: ID[], - public visibility: BaseStyleProps['visibility'], - ) { - super(type); - } -} - -export class ElementZIndexChangeEvent extends BaseEvent { - constructor( - type: GraphEvent.BEFORE_ELEMENT_Z_INDEX_CHANGE | GraphEvent.AFTER_ELEMENT_Z_INDEX_CHANGE, - public id: ID, - public zIndex: ZIndex, + public states: Record, ) { super(type); } diff --git a/packages/g6/src/utils/graphlib.ts b/packages/g6/src/utils/graphlib.ts index e38753ba04e..8d5033f6cd6 100644 --- a/packages/g6/src/utils/graphlib.ts +++ b/packages/g6/src/utils/graphlib.ts @@ -15,15 +15,11 @@ export function toGraphlibData(datums: NodeLikeData): Node; * @returns graphlib 数据 | graphlib data */ export function toGraphlibData(data: NodeData | EdgeData | ComboData): Node | Edge { - if (isEdgeData(data)) { - const { style, data: customData, ...rest } = data; - return { - ...rest, - data, - id: idOf(data), - } as Edge; - } - return { id: idOf(data), data } as Node; + const { id = idOf(data), style, data: customData, ...rest } = data; + const _data = { ...data, style: { ...style }, data: { ...customData } }; + + if (isEdgeData(data)) return { id, data: _data, ...rest } as Edge; + return { id, data: _data } as Node; } export function toG6Data(data: Edge): T; diff --git a/packages/g6/src/utils/id.ts b/packages/g6/src/utils/id.ts index 8368ac1e9bf..3e0d2f9f2f8 100644 --- a/packages/g6/src/utils/id.ts +++ b/packages/g6/src/utils/id.ts @@ -7,7 +7,7 @@ import { isEdgeData } from './is'; * * get the id of node/edge/combo * @param data - 节点/边/Combo 的数据 | data of node/edge/combo - * @returns - 节点/边/Combo 的 ID | ID of node/edge/combo + * @returns 节点/边/Combo 的 ID | ID of node/edge/combo */ export function idOf(data: Partial) { if (isString(data.id) || isNumber(data.id)) return data.id; @@ -15,3 +15,14 @@ export function idOf(data: Partial) { throw new Error('The data does not have available id.'); } + +/** + * 获取节点/Combo 的父节点 ID + * + * get the parent id of node/combo + * @param data - 节点/Combo 的数据 | data of node/combo + * @returns 节点/Combo 的父节点 ID | parent id of node/combo + */ +export function parentIdOf(data: Partial) { + return data.style?.parentId; +} diff --git a/packages/g6/src/utils/layout.ts b/packages/g6/src/utils/layout.ts index ef5eae9ec28..49bb8b6bb29 100644 --- a/packages/g6/src/utils/layout.ts +++ b/packages/g6/src/utils/layout.ts @@ -1,9 +1,5 @@ -import type { LayoutMapping } from '@antv/layout'; import { isNumber } from '@antv/util'; import type { STDLayoutOptions } from '../spec/layout'; -import type { LayoutResult } from '../types'; -import { idOf } from './id'; -import { parsePoint } from './point'; /** * 判断是否是 combo 布局 @@ -31,40 +27,6 @@ export function isTreeLayout(options: STDLayoutOptions) { return ['compact-box', 'mindmap', 'dendrogram', 'indented'].includes(type); } -/** - * 从布局算法的结果抽取应用到元素中的样式 - * - * Extract the style applied to the element from the result of the layout algorithm - * @param result - 布局算法的结果 | The result of the layout algorithm - * @returns 应用到元素中的样式 | Style applied to the element - */ -export function pickLayoutResult(result: LayoutMapping): LayoutResult { - const { nodes = [], edges = [] } = result; - - return { - nodes: Object.fromEntries( - nodes.map((node) => { - const { - id, - data: { x, y, z }, - } = node; - if (isNumber(z)) return [id, [x, y, z]]; - return [id, [x, y]]; - }), - ), - edges: Object.fromEntries( - edges.map((edge) => { - const id = idOf(edge); - const { data } = edge; - const result: Record = {}; - if ('controlPoints' in data) result.controlPoints = data.controlPoints!.map(parsePoint); - // if ('points' in data) result.points = data.points!.map(parsePoint); - return [id, result]; - }), - ), - }; -} - /** * 数据中是否指定了位置 * diff --git a/packages/g6/src/utils/visibility.ts b/packages/g6/src/utils/visibility.ts index 8eee41f995a..5570c404ebf 100644 --- a/packages/g6/src/utils/visibility.ts +++ b/packages/g6/src/utils/visibility.ts @@ -1,6 +1,9 @@ import type { BaseStyleProps, DisplayObject } from '@antv/g'; +import { cacheStyle, getCachedStyle, hasCachedStyle } from './cache'; import { getDescendantShapes } from './shape'; +const PropertyKey = 'visibility'; + /** * 设置图形实例的可见性 * @@ -13,9 +16,16 @@ import { getDescendantShapes } from './shape'; * 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']) { - shape.style.visibility = visibility; - const descendants = getDescendantShapes(shape); - descendants.forEach((descendant) => { - descendant.style.visibility = visibility; + const shapes = [shape, ...getDescendantShapes(shape)]; + + shapes.forEach((sp) => { + if (!hasCachedStyle(sp, PropertyKey)) cacheStyle(sp, PropertyKey); + const cachedVisibility = getCachedStyle(sp, PropertyKey); + + // 如果子图形为隐藏状态,始终保持隐藏状态 + // If the child shape is hidden, keep it hidden + if (shape !== sp && cachedVisibility === 'hidden') return; + + sp.style.visibility = visibility; }); } diff --git a/packages/site/examples/item/defaultEdges/demo/cubic.ts b/packages/site/examples/item/defaultEdges/demo/cubic.ts index 50532185fab..ed5b488d3ff 100644 --- a/packages/site/examples/item/defaultEdges/demo/cubic.ts +++ b/packages/site/examples/item/defaultEdges/demo/cubic.ts @@ -51,8 +51,10 @@ const graph = new Graph({ graph.render(); graph.on('afterrender', () => { - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }); diff --git a/packages/site/examples/item/defaultEdges/demo/horizontalCubic.ts b/packages/site/examples/item/defaultEdges/demo/horizontalCubic.ts index 26fc52b0e8c..a4f02648d75 100644 --- a/packages/site/examples/item/defaultEdges/demo/horizontalCubic.ts +++ b/packages/site/examples/item/defaultEdges/demo/horizontalCubic.ts @@ -58,8 +58,10 @@ const graph = new Graph({ graph.render(); graph.on('afterrender', () => { - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }); diff --git a/packages/site/examples/item/defaultEdges/demo/line.ts b/packages/site/examples/item/defaultEdges/demo/line.ts index a499756756f..0fd0827810e 100644 --- a/packages/site/examples/item/defaultEdges/demo/line.ts +++ b/packages/site/examples/item/defaultEdges/demo/line.ts @@ -51,8 +51,10 @@ const graph = new Graph({ graph.render(); graph.on('afterrender', () => { - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }); diff --git a/packages/site/examples/item/defaultEdges/demo/quadratic.ts b/packages/site/examples/item/defaultEdges/demo/quadratic.ts index ed39fde86fc..68e3ce33f83 100644 --- a/packages/site/examples/item/defaultEdges/demo/quadratic.ts +++ b/packages/site/examples/item/defaultEdges/demo/quadratic.ts @@ -51,8 +51,10 @@ const graph = new Graph({ graph.render(); graph.on('afterrender', () => { - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }); diff --git a/packages/site/examples/item/defaultEdges/demo/verticalCubic.ts b/packages/site/examples/item/defaultEdges/demo/verticalCubic.ts index 1761000c94e..87855128f2d 100644 --- a/packages/site/examples/item/defaultEdges/demo/verticalCubic.ts +++ b/packages/site/examples/item/defaultEdges/demo/verticalCubic.ts @@ -59,8 +59,10 @@ const graph = new Graph({ graph.render(); graph.on('afterrender', () => { - graph.setElementState('line-active', 'active'); - graph.setElementState('line-selected', 'selected'); - graph.setElementState('line-highlight', 'highlight'); - graph.setElementState('line-inactive', 'inactive'); + graph.setElementState({ + 'line-active': 'active', + 'line-selected': 'selected', + 'line-highlight': 'highlight', + 'line-inactive': 'inactive', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/circle.ts b/packages/site/examples/item/defaultNodes/demo/circle.ts index 758a6cc8651..15a63960583 100644 --- a/packages/site/examples/item/defaultNodes/demo/circle.ts +++ b/packages/site/examples/item/defaultNodes/demo/circle.ts @@ -50,9 +50,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('circle-active', 'active'); - graph.setElementState('circle-selected', 'selected'); - graph.setElementState('circle-highlight', 'highlight'); - graph.setElementState('circle-inactive', 'inactive'); - graph.setElementState('circle-disabled', 'disabled'); + graph.setElementState({ + 'circle-active': 'active', + 'circle-selected': 'selected', + 'circle-highlight': 'highlight', + 'circle-inactive': 'inactive', + 'circle-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/diamond.ts b/packages/site/examples/item/defaultNodes/demo/diamond.ts index 26ad4a9e62d..789940f4b4c 100644 --- a/packages/site/examples/item/defaultNodes/demo/diamond.ts +++ b/packages/site/examples/item/defaultNodes/demo/diamond.ts @@ -50,9 +50,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('diamond-active', 'active'); - graph.setElementState('diamond-selected', 'selected'); - graph.setElementState('diamond-highlight', 'highlight'); - graph.setElementState('diamond-inactive', 'inactive'); - graph.setElementState('diamond-disabled', 'disabled'); + graph.setElementState({ + 'diamond-active': 'active', + 'diamond-selected': 'selected', + 'diamond-highlight': 'highlight', + 'diamond-inactive': 'inactive', + 'diamond-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/ellipse.ts b/packages/site/examples/item/defaultNodes/demo/ellipse.ts index 03b5420bc73..6f4a588ff9f 100644 --- a/packages/site/examples/item/defaultNodes/demo/ellipse.ts +++ b/packages/site/examples/item/defaultNodes/demo/ellipse.ts @@ -50,9 +50,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('ellipse-active', 'active'); - graph.setElementState('ellipse-selected', 'selected'); - graph.setElementState('ellipse-highlight', 'highlight'); - graph.setElementState('ellipse-inactive', 'inactive'); - graph.setElementState('ellipse-disabled', 'disabled'); + graph.setElementState({ + 'ellipse-active': 'active', + 'ellipse-selected': 'selected', + 'ellipse-highlight': 'highlight', + 'ellipse-inactive': 'inactive', + 'ellipse-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/image.ts b/packages/site/examples/item/defaultNodes/demo/image.ts index 1dfd574d2f4..2508e8034ff 100644 --- a/packages/site/examples/item/defaultNodes/demo/image.ts +++ b/packages/site/examples/item/defaultNodes/demo/image.ts @@ -53,9 +53,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('image-active', 'active'); - graph.setElementState('image-selected', 'selected'); - graph.setElementState('image-highlight', 'highlight'); - graph.setElementState('image-inactive', 'inactive'); - graph.setElementState('image-disabled', 'disabled'); + graph.setElementState({ + 'image-active': 'active', + 'image-selected': 'selected', + 'image-highlight': 'highlight', + 'image-inactive': 'inactive', + 'image-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/radiusRect.ts b/packages/site/examples/item/defaultNodes/demo/radiusRect.ts index 18ca987e491..ac0ad83ca01 100644 --- a/packages/site/examples/item/defaultNodes/demo/radiusRect.ts +++ b/packages/site/examples/item/defaultNodes/demo/radiusRect.ts @@ -50,9 +50,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('rect-active', 'active'); - graph.setElementState('rect-selected', 'selected'); - graph.setElementState('rect-highlight', 'highlight'); - graph.setElementState('rect-inactive', 'inactive'); - graph.setElementState('rect-disabled', 'disabled'); + graph.setElementState({ + 'rect-active': 'active', + 'rect-selected': 'selected', + 'rect-highlight': 'highlight', + 'rect-inactive': 'inactive', + 'rect-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/rect.ts b/packages/site/examples/item/defaultNodes/demo/rect.ts index 568237730d9..0fb56d57688 100644 --- a/packages/site/examples/item/defaultNodes/demo/rect.ts +++ b/packages/site/examples/item/defaultNodes/demo/rect.ts @@ -49,9 +49,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('rect-active', 'active'); - graph.setElementState('rect-selected', 'selected'); - graph.setElementState('rect-highlight', 'highlight'); - graph.setElementState('rect-inactive', 'inactive'); - graph.setElementState('rect-disabled', 'disabled'); + graph.setElementState({ + 'rect-active': 'active', + 'rect-selected': 'selected', + 'rect-highlight': 'highlight', + 'rect-inactive': 'inactive', + 'rect-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/star.ts b/packages/site/examples/item/defaultNodes/demo/star.ts index 1bccbb1e8e9..43dd390fbc1 100644 --- a/packages/site/examples/item/defaultNodes/demo/star.ts +++ b/packages/site/examples/item/defaultNodes/demo/star.ts @@ -47,9 +47,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('star-active', 'active'); - graph.setElementState('star-selected', 'selected'); - graph.setElementState('star-highlight', 'highlight'); - graph.setElementState('star-inactive', 'inactive'); - graph.setElementState('star-disabled', 'disabled'); + graph.setElementState({ + 'star-active': 'active', + 'star-selected': 'selected', + 'star-highlight': 'highlight', + 'star-inactive': 'inactive', + 'star-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/defaultNodes/demo/triangle.ts b/packages/site/examples/item/defaultNodes/demo/triangle.ts index 01080451e32..7331451052a 100644 --- a/packages/site/examples/item/defaultNodes/demo/triangle.ts +++ b/packages/site/examples/item/defaultNodes/demo/triangle.ts @@ -45,9 +45,11 @@ const graph = new Graph({ graph.render(); graph.on(GraphEvent.AFTER_RENDER, () => { - graph.setElementState('triangle-active', 'active'); - graph.setElementState('triangle-selected', 'selected'); - graph.setElementState('triangle-highlight', 'highlight'); - graph.setElementState('triangle-inactive', 'inactive'); - graph.setElementState('triangle-disabled', 'disabled'); + graph.setElementState({ + 'triangle-active': 'active', + 'triangle-selected': 'selected', + 'triangle-highlight': 'highlight', + 'triangle-inactive': 'inactive', + 'triangle-disabled': 'disabled', + }); }); diff --git a/packages/site/examples/item/label/demo/copyLabel.ts b/packages/site/examples/item/label/demo/copyLabel.ts index dc74db6effe..8f21bd375a6 100644 --- a/packages/site/examples/item/label/demo/copyLabel.ts +++ b/packages/site/examples/item/label/demo/copyLabel.ts @@ -38,16 +38,16 @@ graph.render(); graph.on('node:click', (e) => { const id = e.target.id; const node = graph.getNodeData(id); - const label = node?.data?.label; + const label = node?.data?.label as string; - navigator.clipboard.writeText(label); + navigator.clipboard.writeText(label as string); alert('copied to clipboard!'); }); graph.on('node:pointerenter', (e) => { - graph.setElementState(e.target.id, 'active'); + graph.setElementState({ [e.target.id]: 'active' }); }); graph.on('node:pointerout', (e) => { - graph.setElementState(e.target.id, ''); + graph.setElementState({ [e.target.id]: [] }); });