From 5ebc32f86c683335e3d3888a34fa53cbf423ffff Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 8 Aug 2023 20:13:11 +0800 Subject: [PATCH] feat: tree formatted data rendering; feat: collapse-expand-tree behavior; --- packages/g6/package.json | 6 +- packages/g6/src/item/combo.ts | 3 +- packages/g6/src/item/edge.ts | 10 +- packages/g6/src/item/item.ts | 161 +++++++---- packages/g6/src/item/node.ts | 38 ++- packages/g6/src/runtime/controller/data.ts | 260 +++++++++++++---- packages/g6/src/runtime/controller/item.ts | 217 ++++++++++++-- packages/g6/src/runtime/controller/layout.ts | 88 +++++- packages/g6/src/runtime/graph.ts | 106 ++++++- .../stdlib/behavior/collapse-expand-tree.ts | 106 +++++++ packages/g6/src/stdlib/index.ts | 8 +- packages/g6/src/stdlib/plugin/grid/index.ts | 2 - packages/g6/src/types/data.ts | 17 +- packages/g6/src/types/graph.ts | 33 ++- packages/g6/src/types/hook.ts | 24 +- packages/g6/src/types/item.ts | 13 +- packages/g6/src/types/node.ts | 4 + packages/g6/src/types/spec.ts | 10 +- packages/g6/src/util/animate.ts | 45 ++- packages/g6/src/util/data.ts | 131 ++++++++- packages/g6/src/util/event.ts | 7 +- packages/g6/src/util/layout.ts | 65 +++++ .../g6/tests/demo/behaviors/brush-select.ts | 1 - .../g6/tests/demo/behaviors/click-select.ts | 1 - packages/g6/tests/demo/combo/combo-basic.ts | 1 - packages/g6/tests/demo/demo/bugReproduce.ts | 1 - packages/g6/tests/demo/demo/demo.ts | 2 - packages/g6/tests/demo/demo/menu.ts | 1 - packages/g6/tests/demo/demo/quadratic.ts | 1 - packages/g6/tests/demo/demo/rect.ts | 1 - packages/g6/tests/demo/demo/tooltip.ts | 1 - packages/g6/tests/demo/index.ts | 2 + .../g6/tests/demo/item/edge/cubic-edge.ts | 1 - .../demo/item/edge/cubic-horizon-edge.ts | 1 - .../demo/item/edge/cubic-vertical-edge.ts | 1 - packages/g6/tests/demo/item/edge/line-edge.ts | 1 - packages/g6/tests/demo/layouts/d3force.ts | 1 - .../g6/tests/demo/layouts/force-wasm-3d.ts | 1 - packages/g6/tests/demo/layouts/force-wasm.ts | 1 - .../g6/tests/demo/layouts/forceatlas2-wasm.ts | 1 - .../g6/tests/demo/layouts/fruchterman-gpu.ts | 1 - .../g6/tests/demo/layouts/fruchterman-wasm.ts | 1 - .../g6/tests/demo/performance/layout-3d.ts | 2 - packages/g6/tests/demo/performance/layout.ts | 2 - .../g6/tests/demo/performance/performance.ts | 1 - packages/g6/tests/demo/plugins/fisheye.ts | 1 - packages/g6/tests/demo/tree/tree-graph.ts | 266 ++++++++++++++++++ packages/g6/tests/demo/visual/visual.ts | 1 - .../g6/tests/intergration/layouts/circular.ts | 16 ++ .../g6/tests/intergration/layouts/force-3d.ts | 56 ++++ packages/g6/tests/unit/behavior-spec.ts | 2 - .../tests/unit/behaviors/brush-select-spec.ts | 11 - .../tests/unit/behaviors/drag-canvas-spec.ts | 7 - .../tests/unit/behaviors/lasso-select-spec.ts | 11 - .../tests/unit/behaviors/zoom-canvas-spec.ts | 4 - packages/g6/tests/unit/click-select-spec.ts | 1 - packages/g6/tests/unit/data-spec.ts | 1 - packages/g6/tests/unit/drag-node-spec.ts | 1 - packages/g6/tests/unit/edge-spec.ts | 14 - packages/g6/tests/unit/item-3d-spec.ts | 1 - packages/g6/tests/unit/item-animate-spec.ts | 1 - packages/g6/tests/unit/layout-spec.ts | 12 - packages/g6/tests/unit/node-spec.ts | 7 - packages/g6/tests/unit/plugins/grid-spec.ts | 9 +- packages/g6/tests/unit/plugins/legend-spec.ts | 1 - packages/g6/tests/unit/plugins/menu-spec.ts | 1 - .../g6/tests/unit/plugins/minimap-spec.ts | 1 - .../g6/tests/unit/plugins/tooltip-spec.ts | 1 - packages/g6/tests/unit/quadratic-spec.ts | 6 - packages/g6/tests/unit/rect-spec.ts | 1 - packages/g6/tests/unit/show-animate-spec.ts | 1 - packages/g6/tests/unit/theme-spec.ts | 4 - packages/g6/tests/unit/theme-subject-spec.ts | 3 - packages/g6/tests/unit/view-spec.ts | 10 - 74 files changed, 1493 insertions(+), 339 deletions(-) create mode 100644 packages/g6/src/stdlib/behavior/collapse-expand-tree.ts create mode 100644 packages/g6/src/util/layout.ts create mode 100644 packages/g6/tests/demo/tree/tree-graph.ts create mode 100644 packages/g6/tests/intergration/layouts/circular.ts create mode 100644 packages/g6/tests/intergration/layouts/force-3d.ts diff --git a/packages/g6/package.json b/packages/g6/package.json index f67257b14a..3ff29f2099 100644 --- a/packages/g6/package.json +++ b/packages/g6/package.json @@ -46,7 +46,7 @@ "test": "jest", "test:integration": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/ --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles", "size": "limit-size", - "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/edge-spec.ts", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/item-animate-spec.ts", "test-behavior": "DEBUG_MODE=1 jest --watch ./tests/unit/item-3d-spec.ts" }, "lint-staged": { @@ -58,7 +58,7 @@ }, "dependencies": { "@ant-design/colors": "^7.0.0", - "@antv/algorithm": "^0.1.25", + "@antv/algorithm": "^0.1.26", "@antv/dom-util": "^2.0.4", "@antv/g": "^5.15.7", "@antv/g-canvas": "^1.9.28", @@ -75,6 +75,7 @@ "@antv/matrix-util": "^3.0.4", "@antv/util": "~2.0.5", "color": "^4.2.3", + "stats-js": "^1.0.1", "tslib": "^2.5.0", "typedoc-plugin-markdown": "^3.14.0", "typescript": "^5.1.6" @@ -115,6 +116,7 @@ "rollup-plugin-polyfill-node": "^0.8.0", "rollup-plugin-typescript": "^1.0.1", "rollup-plugin-visualizer": "^5.6.0", + "stats-js": "1.0.1", "stats.js": "^0.17.0", "ts-jest": "^28.0.8", "typedoc": "^0.24.0", diff --git a/packages/g6/src/item/combo.ts b/packages/g6/src/item/combo.ts index 0b10d09381..f34a1787a9 100644 --- a/packages/g6/src/item/combo.ts +++ b/packages/g6/src/item/combo.ts @@ -74,6 +74,7 @@ export default class Combo extends Node { displayModel: ComboDisplayModel, diffData?: { previous: ComboUserModelData; current: ComboUserModelData }, diffState?: { previous: State[]; current: State[] }, + animate: boolean = true, onfinish: Function = () => {}, ) { if (displayModel.data.collapsed) { @@ -90,7 +91,7 @@ export default class Combo extends Node { } }); } - super.draw(displayModel, diffData, diffState, onfinish); + super.draw(displayModel, diffData, diffState, animate, onfinish); } /** diff --git a/packages/g6/src/item/edge.ts b/packages/g6/src/item/edge.ts index dfbb1f07b9..0609d2d873 100644 --- a/packages/g6/src/item/edge.ts +++ b/packages/g6/src/item/edge.ts @@ -8,6 +8,7 @@ import { animateShapes } from '../util/animate'; import { EdgeStyleSet } from '../types/theme'; import Item from './item'; import Node from './node'; +import Combo from './combo'; interface IProps { model: EdgeModel; @@ -50,6 +51,7 @@ export default class Edge extends Item { displayModel: EdgeDisplayModel, diffData?: { previous: EdgeModelData; current: EdgeModelData }, diffState?: { previous: State[]; current: State[] }, + animate: boolean = true, onfinish: Function = () => {}, ) { // get the end points @@ -94,7 +96,7 @@ export default class Edge extends Item { const timing = firstRendering ? 'buildIn' : 'update'; // handle shape's animate - if (!disableAnimate && usingAnimates[timing]?.length) { + if (animate && !disableAnimate && usingAnimates[timing]?.length) { this.animations = animateShapes( usingAnimates, targetStyles, // targetStylesMap @@ -103,7 +105,7 @@ export default class Edge extends Item { firstRendering ? 'buildIn' : 'update', this.changedStates, this.animateFrameListener, - () => onfinish(displayModel.id), + (canceled) => onfinish(displayModel.id, canceled), ); } } @@ -140,8 +142,8 @@ export default class Edge extends Item { public clone( containerGroup: Group, - sourceItem: Node, - targetItem: Node, + sourceItem: Node | Combo, + targetItem: Node | Combo, onlyKeyShape?: boolean, disableAnimate?: boolean, ) { diff --git a/packages/g6/src/item/item.ts b/packages/g6/src/item/item.ts index 314626c04d..17917a716e 100644 --- a/packages/g6/src/item/item.ts +++ b/packages/g6/src/item/item.ts @@ -68,8 +68,10 @@ export default abstract class Item implements IItem { /** The flag of transient item. */ public transient = false; public renderExtensions: any; // TODO - /** Cache the animation instances to stop at next lifecycle. */ + /** Cache the shape animation instances to stop at next lifecycle. */ public animations: IAnimation[]; + /** Cache the group animation instances to stop at next lifecycle. */ + public groupAnimations: IAnimation[]; public themeStyles: { default: ItemShapeStyles; @@ -159,6 +161,7 @@ export default abstract class Item implements IItem { displayModel: ItemDisplayModel, diffData?: { previous: ItemModelData; current: ItemModelData }, diffState?: { previous: State[]; current: State[] }, + animate: boolean = true, onfinish: Function = () => {}, ) { // call this.renderExt.draw in extend implementations @@ -198,6 +201,7 @@ export default abstract class Item implements IItem { lodStrategy: LodStrategyObj; }, onlyMove?: boolean, + animate: boolean = true, onfinish?: Function, ) { // 1. merge model into this model @@ -218,7 +222,7 @@ export default abstract class Item implements IItem { : itemTheme?.lodStrategy || this.lodStrategy; if (onlyMove) { - this.updatePosition(displayModel, diffData, onfinish); + this.updatePosition(displayModel, diffData, animate, onfinish); return; } @@ -242,9 +246,9 @@ export default abstract class Item implements IItem { } // 3. call element update fn from useLib if (this.states?.length) { - this.drawWithStates(this.states, onfinish); + this.drawWithStates(this.states, animate, onfinish); } else { - this.draw(this.displayModel, diffData, undefined, onfinish); + this.draw(this.displayModel, diffData, undefined, animate, onfinish); } // 4. tag all the states with 'dirty', for state style regenerating when state changed this.stateDirtyMap = {}; @@ -261,6 +265,7 @@ export default abstract class Item implements IItem { public updatePosition( displayModel: ItemDisplayModel, diffData?: { previous: ItemModelData; current: ItemModelData }, + animate?: boolean, onfinish?: Function, ) {} @@ -395,60 +400,63 @@ export default abstract class Item implements IItem { /** Show the item. */ public show(animate = true) { - // TODO: utilize graphcore's view - this.stopAnimations(); - - const { animates = {} } = this.displayModel.data; - if (animate && animates.show?.length) { - const showAnimateFieldsMap: any = {}; - Object.values(animates.show).forEach((animate) => { - const { shapeId = 'group' } = animate; - showAnimateFieldsMap[shapeId] = ( - showAnimateFieldsMap[shapeId] || [] - ).concat(animate.fields); - }); - const targetStyleMap = {}; - Object.keys(this.shapeMap).forEach((id) => { - const shape = this.shapeMap[id]; - if (!this.cacheHiddenShape[id]) { - // set the animate fields to initial value - if (showAnimateFieldsMap[id]) { - targetStyleMap[id] = targetStyleMap[id] || {}; - const beginStyle = getShapeAnimateBeginStyles(shape); - showAnimateFieldsMap[id].forEach((field) => { - if (beginStyle.hasOwnProperty(field)) { - targetStyleMap[id][field] = shape.style[field]; - shape.style[field] = beginStyle[field]; - } - }); + Promise.all(this.stopAnimations()).finally(() => { + if (this.destroyed || this.visible) return; + const { animates = {} } = this.displayModel.data; + if (animate && animates.show?.length) { + const showAnimateFieldsMap: any = {}; + Object.values(animates.show).forEach((animate) => { + const { shapeId = 'group' } = animate; + showAnimateFieldsMap[shapeId] = ( + showAnimateFieldsMap[shapeId] || [] + ).concat(animate.fields); + }); + const targetStyleMap = {}; + Object.keys(this.shapeMap).forEach((id) => { + const shape = this.shapeMap[id]; + if (!this.cacheHiddenShape[id]) { + // set the animate fields to initial value + if (showAnimateFieldsMap[id]) { + targetStyleMap[id] = targetStyleMap[id] || {}; + const beginStyle = getShapeAnimateBeginStyles(shape); + showAnimateFieldsMap[id].forEach((field) => { + if (beginStyle.hasOwnProperty(field)) { + targetStyleMap[id][field] = shape.style[field]; + shape.style[field] = beginStyle[field]; + } + }); + } + shape.show(); } - shape.show(); + }); + if (showAnimateFieldsMap.group) { + showAnimateFieldsMap.group.forEach((field) => { + const usingField = field === 'size' ? 'transform' : field; + if (GROUP_ANIMATE_STYLES[0].hasOwnProperty(usingField)) { + this.group.style[usingField] = + GROUP_ANIMATE_STYLES[0][usingField]; + } + }); } - }); - if (showAnimateFieldsMap.group) { - showAnimateFieldsMap.group.forEach((field) => { - const usingField = field === 'size' ? 'transform' : field; - if (GROUP_ANIMATE_STYLES[0].hasOwnProperty(usingField)) { - this.group.style[usingField] = GROUP_ANIMATE_STYLES[0][usingField]; - } + + this.animations = this.runWithAnimates( + animates, + 'show', + targetStyleMap, + ); + } else { + Object.keys(this.shapeMap).forEach((id) => { + const shape = this.shapeMap[id]; + if (!this.cacheHiddenShape[id]) shape.show(); }); } - this.animations = this.runWithAnimates(animates, 'show', targetStyleMap); - } else { - Object.keys(this.shapeMap).forEach((id) => { - const shape = this.shapeMap[id]; - if (!this.cacheHiddenShape[id]) shape.show(); - }); - } - - this.visible = true; + this.visible = true; + }); } /** Hides the item. */ public hide(animate = true) { - // TODO: utilize graphcore's view - this.stopAnimations(); const func = () => { Object.keys(this.shapeMap).forEach((id) => { const shape = this.shapeMap[id]; @@ -457,15 +465,22 @@ export default abstract class Item implements IItem { shape.hide(); }); }; - const { animates = {} } = this.displayModel.data; - if (animate && animates.hide?.length) { - this.animations = this.runWithAnimates(animates, 'hide', undefined, func); - } else { - // 2. clear group and remove group - func(); - } - - this.visible = false; + Promise.all(this.stopAnimations()).then(() => { + if (this.destroyed || !this.visible) return; + const { animates = {} } = this.displayModel.data; + if (animate && animates.hide?.length) { + this.animations = this.runWithAnimates( + animates, + 'hide', + undefined, + func, + ); + } else { + // 2. clear group and remove group + func(); + } + this.visible = false; + }); } /** Returns the visibility of the item. */ @@ -606,7 +621,11 @@ export default abstract class Item implements IItem { * @param previousStates previous states * @returns */ - private drawWithStates(previousStates: State[], onfinish?: Function) { + private drawWithStates( + previousStates: State[], + animate: boolean = true, + onfinish?: Function, + ) { const { default: _, ...themeStateStyles } = this.themeStyles; const { data: displayModelData } = this.displayModel; let styles = {}; // merged styles @@ -675,6 +694,7 @@ export default abstract class Item implements IItem { previous: previousStates, current: this.states, }, + animate, onfinish, ); } @@ -729,8 +749,27 @@ export default abstract class Item implements IItem { * Stop all the animations on the item. */ public stopAnimations() { - this.animations?.forEach(stopAnimate); - this.animations = []; + let promises = []; + if (this.animations?.length) { + while (this.animations.length) { + const animate = this.animations.pop(); + if (animate.playState !== 'running') break; + promises.push(stopAnimate(animate)); + // @ts-ignore + animate.onManualCancel?.(); + } + } + if (this.groupAnimations?.length) { + while (this.groupAnimations.length) { + const groupAnimate = this.groupAnimations.pop(); + if (groupAnimate.playState !== 'running') break; + promises.push(stopAnimate(groupAnimate)); + // @ts-ignore + groupAnimate.onManualCancel?.(); + } + this.groupAnimations = []; + } + return promises; } /** diff --git a/packages/g6/src/item/node.ts b/packages/g6/src/item/node.ts index e3b3ddfc3d..5da2fe4d9b 100644 --- a/packages/g6/src/item/node.ts +++ b/packages/g6/src/item/node.ts @@ -45,6 +45,7 @@ export default class Node extends Item { this.displayModel as NodeDisplayModel | ComboDisplayModel, undefined, undefined, + !this.displayModel.data.disableAnimate, props.onfinish, ); } @@ -55,6 +56,7 @@ export default class Node extends Item { current: NodeModelData | ComboModelData; }, diffState?: { previous: State[]; current: State[] }, + animate: boolean = true, onfinish: Function = () => {}, ) { const { group, renderExt, shapeMap: prevShapeMap, model } = this; @@ -78,13 +80,13 @@ export default class Node extends Item { } else { // terminate previous animations this.stopAnimations(); - this.updatePosition(displayModel, diffData, onfinish); + this.updatePosition(displayModel, diffData, animate, onfinish); } const { haloShape } = this.shapeMap; haloShape?.toBack(); - super.draw(displayModel, diffData, diffState, onfinish); + super.draw(displayModel, diffData, diffState, animate, onfinish); this.anchorPointsCache = undefined; renderExt.updateCache(this.shapeMap); @@ -94,7 +96,7 @@ export default class Node extends Item { } // handle shape's and group's animate - if (!disableAnimate && animates) { + if (animate && !disableAnimate && animates) { const animatesExcludePosition = getAnimatesExcludePosition(animates); this.animations = animateShapes( animatesExcludePosition, // animates @@ -104,7 +106,7 @@ export default class Node extends Item { firstRendering ? 'buildIn' : 'update', this.changedStates, this.animateFrameListener, - () => onfinish(model.id), + (canceled) => onfinish(model.id, canceled), ); } } @@ -121,9 +123,18 @@ export default class Node extends Item { lodStrategy: LodStrategyObj; }, onlyMove?: boolean, + animate?: boolean, onfinish?: Function, ) { - super.update(model, diffData, isReplace, theme, onlyMove, onfinish); + super.update( + model, + diffData, + isReplace, + theme, + onlyMove, + animate, + onfinish, + ); } /** @@ -136,19 +147,20 @@ export default class Node extends Item { previous: NodeModelData | ComboModelData; current: NodeModelData | ComboModelData; }, + animate?: boolean, onfinish: Function = () => {}, ) { const { group } = this; const { x, y, z = 0, animates, disableAnimate } = displayModel.data; - if (isNaN(x as number) || isNaN(y as number) || isNaN(z)) return; - if (!disableAnimate && animates?.update) { + if (isNaN(x as number) || isNaN(y as number) || isNaN(z as number)) return; + if (animate && !disableAnimate && animates?.update) { const groupAnimates = animates.update.filter( ({ shapeId, fields = [] }) => (!shapeId || shapeId === 'group') && (fields.includes('x') || fields.includes('y')), ); if (groupAnimates.length) { - animateShapes( + const animations = animateShapes( { update: groupAnimates }, { group: { x, y, z } } as any, // targetStylesMap this.shapeMap, // shapeMap @@ -156,12 +168,14 @@ export default class Node extends Item { 'update', [], this.animateFrameListener, - () => onfinish(displayModel.id), + (canceled) => onfinish(displayModel.id, canceled), ); + this.groupAnimations = animations; return; } } group.setLocalPosition(x, y, z); + onfinish(displayModel.id, !animate); } public clone( @@ -294,6 +308,12 @@ export default class Node extends Item { } public getPosition(): Point { + const initiated = + this.shapeMap.keyShape && this.group.attributes.x !== undefined; + if (initiated) { + const { center } = this.shapeMap.keyShape.getRenderBounds(); + return { x: center[0], y: center[1], z: center[2] }; + } const { x = 0, y = 0, z = 0 } = this.model.data; return { x: x as number, y: y as number, z: z as number }; } diff --git a/packages/g6/src/runtime/controller/data.ts b/packages/g6/src/runtime/controller/data.ts index 2a928d4d47..72807528e1 100644 --- a/packages/g6/src/runtime/controller/data.ts +++ b/packages/g6/src/runtime/controller/data.ts @@ -1,16 +1,16 @@ import { Graph as GraphLib, GraphView, ID } from '@antv/graphlib'; -import { - clone, - isArray, - isFunction, - isNumber, - isObject, - isString, -} from '@antv/util'; +import { clone, isArray, isObject } from '@antv/util'; import { registery as registry } from '../../stdlib'; import { ComboModel, ComboUserModel, GraphData, IGraph } from '../../types'; import { ComboUserModelData } from '../../types/combo'; -import { DataChangeType, GraphCore } from '../../types/data'; +import { + DataChangeType, + DataConfig, + FetchDataConfig, + GraphCore, + InlineGraphDataConfig, + InlineTreeDataConfig, +} from '../../types/data'; import { EdgeModel, EdgeModelData, @@ -28,9 +28,10 @@ import { getExtension } from '../../util/extension'; import { deconstructData, graphComboTreeDfs, - graphCoreTreeDfs, - isSucceed, + graphData2TreeData, + treeData2GraphData, validateComboStrucutre, + traverse, } from '../../util/data'; /** @@ -49,8 +50,6 @@ export class DataController { */ public graphCore: GraphCore; - private comboTreeView: GraphView; - constructor(graph: IGraph) { this.graph = graph; this.tap(); @@ -142,6 +141,9 @@ export class DataController { private tap() { this.extensions = this.getExtensions(); this.graph.hooks.datachange.tap(this.onDataChange.bind(this)); + this.graph.hooks.treecollapseexpand.tap( + this.onTreeCollapseExpand.bind(this), + ); } /** @@ -161,24 +163,24 @@ export class DataController { * Listener of graph's datachange hook. * @param param contains new graph data and type of data change */ - private onDataChange(param: { data: GraphData; type: DataChangeType }) { + private onDataChange(param: { data: DataConfig; type: DataChangeType }) { const { data, type: changeType } = param; const change = () => { switch (changeType) { case 'remove': - this.removeData(data); + this.removeData(data as GraphData); break; case 'update': - this.updateData(data); + this.updateData(data as GraphData); break; case 'moveCombo': - this.moveCombo(data); + this.moveCombo(data as GraphData); break; case 'addCombo': - this.addCombo(data); + this.addCombo(data as GraphData); break; default: - // 'replace' | 'mergeReplace' | 'union' + // changeType is 'replace' | 'mergeReplace' | 'union' this.changeData(data, changeType); break; } @@ -191,16 +193,30 @@ export class DataController { } } + private onTreeCollapseExpand(params: { + ids: ID[]; + action: 'collapse' | 'expand'; + }) { + const { ids, action } = params; + ids.forEach((id) => { + this.userGraphCore.mergeNodeData(id, { + collapsed: action === 'collapse', + }); + }); + } + /** * Change data by replace, merge repalce, or union. * @param data new data * @param changeType type of data change, 'replace' means discard the old data. 'mergeReplace' means merge the common part. 'union' means merge whole sets of old and new one */ private changeData( - data: GraphData, + dataConfig: DataConfig, changeType: 'replace' | 'mergeReplace' | 'union', ) { - const { userGraphCore } = this; + const { type: dataType, data } = this.formatData(dataConfig) || {}; + if (!dataType) return; + if (changeType === 'replace') { this.userGraphCore = new GraphLib({ nodes: data.nodes.concat( @@ -224,10 +240,6 @@ export class DataController { edges, }); if (combos?.length) { - this.comboTreeView = new GraphView({ - graph: this.graphCore, - cache: 'manual', - }); this.graphCore.attachTreeStructure('combo'); nodes.forEach((node) => { if (node.data.parentId) { @@ -257,9 +269,10 @@ export class DataController { }); } } else { + const { userGraphCore } = this; const prevData = deconstructData({ nodes: userGraphCore.getAllNodes(), - edges: userGraphCore.getAllEdges(), + edges: [], }); const { nodes = [], edges = [], combos = [] } = data; const nodesAndCombos = nodes.concat( @@ -294,6 +307,7 @@ export class DataController { } // =========== edge ============ + prevData.edges = userGraphCore.getAllEdges(); if (!prevData.edges.length) { userGraphCore.addEdges(edges); } else { @@ -317,6 +331,15 @@ export class DataController { }); } } + + if (data.edges?.length) { + const { userGraphCore } = this; + // convert and store tree structure to graphCore + this.updateTreeGraph(dataType, { + nodes: userGraphCore.getAllNodes(), + edges: userGraphCore.getAllEdges(), + }); + } } /** @@ -331,12 +354,14 @@ export class DataController { const prevEdges = userGraphCore.getAllEdges(); if (prevNodesAndCombos.length && nodesAndCombos.length) { // update the parentId - nodesAndCombos.forEach((item) => { - const { parentId } = item.data; - this.graphCore.getChildren(item.id, 'combo').forEach((child) => { - userGraphCore.mergeNodeData(child.id, { parentId }); + if (this.graphCore.hasTreeStructure('combo')) { + nodesAndCombos.forEach((item) => { + const { parentId } = item.data; + this.graphCore.getChildren(item.id, 'combo').forEach((child) => { + userGraphCore.mergeNodeData(child.id, { parentId }); + }); }); - }); + } // remove the node userGraphCore.removeNodes(nodesAndCombos.map((node) => node.id)); } @@ -353,9 +378,11 @@ export class DataController { * Update part of old data. * @param data data to be updated which is part of old one */ - private updateData(data: GraphData) { + private updateData(dataConfig: DataConfig) { const { userGraphCore } = this; - const { nodes = [], edges = [], combos = [] } = data; + const { type: dataType, data } = this.formatData(dataConfig); + if (!dataType) return; + const { nodes = [], edges = [], combos = [] } = data as GraphData; const { nodes: prevNodes, edges: prevEdges, @@ -453,6 +480,38 @@ export class DataController { userGraphCore.mergeNodeData(id, data); }); } + + if (edges.length) { + // convert and store tree structure to graphCore + this.updateTreeGraph(dataType, { + nodes: this.userGraphCore.getAllNodes(), + edges: this.userGraphCore.getAllEdges(), + }); + } + } + + private formatData(dataConfig: DataConfig): { + data: GraphData; + type: 'graphData' | 'treeData' | 'fetch'; + } { + const { type, value } = dataConfig as + | InlineGraphDataConfig + | InlineTreeDataConfig + | FetchDataConfig; + let data = value; + if (!type) { + data = dataConfig as GraphData; + } else if (type === 'treeData') { + data = treeData2GraphData(value); + } else if (type === 'fetch') { + // TODO: fetch + } else { + console.warn( + 'Input data type is invalid, the type shuold be "graphData", "treeData", or "fetch".', + ); + return; + } + return { type: type || 'graphData', data: data as GraphData }; } /** @@ -592,9 +651,22 @@ export class DataController { const changeMap: { [id: string]: boolean; } = {}; + const treeChanges = []; event.changes.forEach((change) => { - const { value, id } = change; - changeMap[id || value.id] = true; + const id = change.id || change.value?.id; + if (id !== undefined) { + changeMap[id] = true; + return; + } + if ( + [ + 'TreeStructureAttached', + 'TreeStructureChanged', + 'TreeStructureChanged', + ].includes(change.type) + ) { + treeChanges.push(change); + } }); nodes.forEach((model) => { newModelMap[model.id] = { type: 'node', model }; @@ -633,6 +705,7 @@ export class DataController { const { model: newModel } = newModelMap[id] || {}; // remove if (!newModel) { + // remove a combo, put the children to upper parent if (prevModel.data._isCombo) { graphCore.getChildren(id, 'combo').forEach((child) => { parentMap[child.id] = { @@ -641,9 +714,43 @@ export class DataController { }; }); } + // if it has combo parent, remove it from the parent's children list if (prevModel.data.parentId) { graphCore.setParent(id, undefined, 'combo'); } + + // for tree graph view, show the succeed nodes and edges + const succeedIds = []; + graphCore.dfsTree( + id, + (child) => { + succeedIds.push(child.id); + }, + 'tree', + ); + const succeedEdgeIds = graphCore + .getAllEdges() + .filter( + ({ source, target }) => + succeedIds.includes(source) && succeedIds.includes(target), + ) + .map((edge) => edge.id); + this.graph.showItem( + succeedIds + .filter((succeedId) => succeedId !== id) + .concat(succeedEdgeIds), + ); + + // for tree graph view, remove the node from the parent's children list + graphCore.setParent(id, undefined, 'tree'); + // for tree graph view, make the its children to be roots + graphCore + .getChildren(id, 'tree') + .forEach((child) => + graphCore.setParent(child.id, undefined, 'tree'), + ); + + // remove the node data graphCore.removeNode(id); delete parentMap[prevModel.id]; } @@ -682,6 +789,21 @@ export class DataController { graphCore.mergeNodeData(id, { parentId: parentMap[id].new }); graphCore.setParent(id, parentMap[id].new, 'combo'); }); + + // update tree structure + treeChanges.forEach((change) => { + const { type, treeKey, nodeId, newParentId } = change; + if (type === 'TreeStructureAttached') { + graphCore.attachTreeStructure(treeKey); + return; + } else if (type === 'TreeStructureChanged') { + graphCore.setParent(nodeId, newParentId, treeKey); + return; + } else if (type === 'TreeStructureDetached') { + graphCore.detachTreeStructure(treeKey); + return; + } + }); } else { // situation 2: idMaps is complete // calculate the final idMap which maps the ids from final transformed data to their comes from ids in userData @@ -704,15 +826,17 @@ export class DataController { const { changes } = event; changes.forEach((change) => { const { value, id, type } = change; - // TODO: temporary skip. how to handle tree change events? - if ( - [ - 'TreeStructureAttached', - 'TreeStructureDetached', - 'TreeStructureChanged', - ].includes(type) - ) + if (type === 'TreeStructureAttached') { + graphCore.attachTreeStructure(change.treeKey); return; + } else if (type === 'TreeStructureChanged') { + const { newParentId, nodeId, treeKey } = change; + graphCore.setParent(nodeId, newParentId, treeKey); + return; + } else if (type === 'TreeStructureDetached') { + graphCore.detachTreeStructure(change.treeKey); + return; + } const dataId = id || value.id; changeMap[dataId] = changeMap[dataId] || []; changeMap[dataId].push(type.toLawerCase()); @@ -727,15 +851,14 @@ export class DataController { const oldValue = prevModelMap[newId]; const isNodeOrCombo = graphCore.hasNode(newId); if (newValue && !oldValue) { - const addFunc = isNodeOrCombo - ? graphCore.addNode - : graphCore.addEdge; - addFunc(newValue); + isNodeOrCombo + ? graphCore.addNode(newValue) + : graphCore.addEdge(newValue); } else if (!newValue && oldValue) { - const removeFunc = isNodeOrCombo - ? graphCore.removeNode - : graphCore.removeEdge; - removeFunc(newId); + isNodeOrCombo + ? graphCore.removeNode(newId) + : graphCore.removeEdge(newId); + // TODO: update combo tree and tree graph } else { if (!comesFromIds?.length) { // no comesForm, find same id in userGraphCore to follow the change, if it not found, diff new and old data value of graphCore (inner data) @@ -748,11 +871,9 @@ export class DataController { isNodeOrCombo, diff, ); - } else { + } else if (changeMap[comesFromIds[0]]?.length) { // follow the corresponding data event in userGraphCore - const comesFromChanges = changeMap[comesFromIds[0]]; - if (comesFromChanges?.length) - syncUpdateToGraphCore(newId, newValue, oldValue, isNodeOrCombo); + syncUpdateToGraphCore(newId, newValue, oldValue, isNodeOrCombo); } } }); @@ -771,7 +892,6 @@ export class DataController { graphCore.setParent(node.id, node.data.parentId as ID, 'combo'); }); } - this.comboTreeView?.refreshCache(); }); } @@ -798,6 +918,34 @@ export class DataController { }); return { data: dataCloned, idMaps }; } + + /** + * convert and store tree structure to graphCore + * @param dataType + * @param data + */ + private updateTreeGraph(dataType, data) { + this.userGraphCore.attachTreeStructure('tree'); + if (dataType === 'treeData') { + // tree structure storing + data.edges.forEach((edge) => { + const { source, target } = edge; + this.userGraphCore.setParent(target, source, 'tree'); + }); + } else { + // graph data to tree structure and storing + const rootIds = data.nodes + .filter((node) => node.data.isRoot) + .map((node) => node.id); + graphData2TreeData({}, data, rootIds).forEach((tree) => { + traverse(tree, (node) => { + node.children?.forEach((child) => { + this.userGraphCore.setParent(child.id, node.id, 'tree'); + }); + }); + }); + } + } } /** diff --git a/packages/g6/src/runtime/controller/item.ts b/packages/g6/src/runtime/controller/item.ts index 96d5144399..b425ac4a97 100644 --- a/packages/g6/src/runtime/controller/item.ts +++ b/packages/g6/src/runtime/controller/item.ts @@ -126,8 +126,6 @@ export class ItemController { [id: string]: Node | Edge | Combo | Group; } = {}; - // private comboTreeView: GraphView; - constructor(graph: IGraph) { this.graph = graph; // get mapper for node / edge / combo @@ -168,6 +166,9 @@ export class ItemController { this.graph.hooks.transientupdate.tap(this.onTransientUpdate.bind(this)); this.graph.hooks.viewportchange.tap(this.onViewportChange.bind(this)); this.graph.hooks.themechange.tap(this.onThemeChange.bind(this)); + this.graph.hooks.treecollapseexpand.tap( + this.onTreeCollapseExpand.bind(this), + ); this.graph.hooks.destroy.tap(this.onDestroy.bind(this)); } @@ -258,6 +259,7 @@ export class ItemController { this.renderCombos(combos, theme.combo, graphCore); this.renderEdges(edges, theme.edge); this.sortByComboTree(graphCore); + // collapse the combos which has 'collapsed' in initial data if (graphCore.hasTreeStructure('combo')) { graphCoreTreeDfs( graphCore, @@ -266,8 +268,21 @@ export class ItemController { if (child.data.collapsed) this.collapseCombo(graphCore, child); }, 'BT', + 'combo', ); } + // collapse the sub tree which has 'collapsed' in initial data + const collapseNodes = []; + graphCoreTreeDfs( + graphCore, + graphCore.getRoots('tree'), + (child) => { + if (child.data.collapsed) collapseNodes.push(child); + }, + 'BT', + 'tree', + ); + this.collapseSubTree(collapseNodes, graphCore, false); } /** @@ -280,14 +295,21 @@ export class ItemController { graphCore: GraphCore; theme: ThemeSpecification; upsertAncestors?: boolean; + animate?: boolean; action?: 'updatePosition'; + callback?: ( + model: NodeModel | EdgeModel | ComboModel, + canceled?: boolean, + ) => void; }) { const { changes, graphCore, action, + animate = true, upsertAncestors = true, theme = {}, + callback = () => {}, } = param; const groupedChanges = getGroupedChanges(graphCore, changes); const { itemMap } = this; @@ -298,10 +320,9 @@ export class ItemController { ({ value }) => { const { id } = value; const item = itemMap[id]; - if (item) { - item.destroy(); - delete itemMap[id]; - } + if (!item) return; + item.destroy(); + delete itemMap[id]; }, ); @@ -381,8 +402,16 @@ export class ItemController { const onlyMove = action === 'updatePosition'; const item = itemMap[id] as Node | Combo; const type = item.getType(); - if (onlyMove && type === 'node' && isNaN(current.x) && isNaN(current.y)) + const innerModel = graphCore.getNode(id); + if ( + onlyMove && + type === 'node' && + isNaN(current.x) && + isNaN(current.y) + ) { + callback(innerModel, true); return; + } // update the theme if the dataType value is changed let itemTheme; if ( @@ -396,7 +425,6 @@ export class ItemController { nodeTheme, ); } - const innerModel = graphCore.getNode(id); const relatedEdgeInnerModels = graphCore.getRelatedEdges(id); const nodeRelatedIdsToUpdate: ID[] = []; relatedEdgeInnerModels.forEach((edge) => { @@ -415,11 +443,13 @@ export class ItemController { isReplace, itemTheme, onlyMove, + animate, // call after updating finished - () => { - item.onframe(); + (_, canceled) => { + item.onframe?.(); // @ts-ignore item.onframe = undefined; + callback(innerModel, canceled); }, ); @@ -476,7 +506,7 @@ export class ItemController { this.graph.hideItem(innerModel.id); } }); - updateRelates(); + updateRelatesThrottle(); } // === 6. update edges' data === @@ -513,7 +543,15 @@ export class ItemController { } const item = itemMap[id]; const innerModel = graphCore.getEdge(id); - item.update(innerModel, { current, previous }, isReplace, itemTheme); + item.update( + innerModel, + { current, previous }, + isReplace, + itemTheme, + undefined, + animate, + (_, canceled) => callback(innerModel, canceled), + ); }); } @@ -538,9 +576,25 @@ export class ItemController { } // === 8. combo tree structure change, resort the shapes === - if (groupedChanges.TreeStructureChanged.length) { + if (groupedChanges.ComboStructureChanged.length) { this.sortByComboTree(graphCore); } + + // === 9. tree data structure change, hide the new node and edge while one of the ancestor is collapsed === + if (groupedChanges.TreeStructureChanged.length) { + groupedChanges.TreeStructureChanged.forEach((change) => { + const { nodeId } = change; + // hide it when an ancestor is collapsed + let parent = graphCore.getParent(nodeId, 'tree'); + while (parent) { + if (parent.data.collapsed) { + this.graph.hideItem(nodeId, true); + break; + } + parent = graphCore.getParent(parent.id, 'tree'); + } + }); + } } /** @@ -593,21 +647,20 @@ export class ItemController { if (type === 'edge') { item.show(animate); } else { - let anccestorCollapsed = false; if (graphCore.hasTreeStructure('combo')) { + let anccestorCollapsed = false; traverseAncestors(graphCore, [item.model], (model) => { if (model.data.collapsed) anccestorCollapsed = true; return anccestorCollapsed; }); if (anccestorCollapsed) return; } - const relatedEdges = graphCore.getRelatedEdges(id); item.show(animate); relatedEdges.forEach(({ id: edgeId, source, target }) => { if (this.getItemVisible(source) && this.getItemVisible(target)) - this.itemMap[edgeId]?.show(); + this.itemMap[edgeId]?.show(animate); }); } } else { @@ -615,7 +668,7 @@ export class ItemController { if (type !== 'edge') { const relatedEdges = graphCore.getRelatedEdges(id); relatedEdges.forEach(({ id: edgeId }) => { - this.itemMap[edgeId]?.hide(); + this.itemMap[edgeId]?.hide(animate); }); } } @@ -979,11 +1032,13 @@ export class ItemController { console.warn( `The source node ${source} is not exist in the graph for edge ${id}, please add the node first`, ); + return; } if (!targetItem) { console.warn( `The source node ${source} is not exist in the graph for edge ${id}, please add the node first`, ); + return; } // get the base styles from theme let dataType; @@ -1164,6 +1219,134 @@ export class ItemController { // remove related virtual edges this.graph.removeData('edge', uniq(relatedVirtualEdgeIds)); } + + /** + * Collapse or expand a sub tree according to action + * @param params + */ + private onTreeCollapseExpand(params: { + ids: ID[]; + animate: boolean; + action: 'collapse' | 'expand'; + graphCore: GraphCore; + }) { + const { ids, animate, action, graphCore } = params; + const rootModels = ids.map((id) => graphCore.getNode(id)); + switch (action) { + case 'collapse': + this.collapseSubTree(rootModels, graphCore, animate); + break; + case 'expand': + default: + this.expandSubTree(rootModels, graphCore, animate); + break; + } + } + + /** + * Collapse sub tree(s). + * @param rootModels The root node models of sub trees + * @param graphCore + * @param animate Whether enable animations for expanding, true by default + * @returns + */ + private collapseSubTree( + rootModels: NodeModel[], + graphCore: GraphCore, + animate: boolean = true, + ) { + let positions = []; + rootModels.forEach((root) => { + let shouldCollapse = true; + const nodes = []; + graphCoreTreeDfs( + graphCore, + [root], + (node) => { + if (node.id === root.id) return; + const neighbors = graphCore.getNeighbors(node.id); + if ( + neighbors.length > 2 || + (!graphCore.getChildren(node.id, 'tree')?.length && + neighbors.length > 1) + ) { + shouldCollapse = false; + } + nodes.push(node); + }, + 'TB', + 'tree', + { + stopAllFn: () => !shouldCollapse, + }, + ); + if (shouldCollapse) { + positions = positions.concat( + nodes.map((node) => ({ + id: node.id, + data: { x: root.data.x, y: root.data.y }, + })), + ); + } + }); + if (!positions.length) return; + this.graph.updateNodePosition( + positions, + undefined, + !animate, + (model, canceled) => { + this.graph.hideItem(model.id, canceled); + }, + undefined, + ); + } + + /** + * Expand sub tree(s). + * @param rootModels The root node models of sub trees. + * @param graphCore + * @param animate Whether enable animations for expanding, true by default. + * @returns + */ + private expandSubTree( + rootModels: NodeModel[], + graphCore: GraphCore, + animate: boolean = true, + ) { + let allNodeIds = []; + let allEdgeIds = []; + rootModels.forEach((root) => { + const nodeIds = []; + graphCoreTreeDfs( + graphCore, + [root], + (node) => nodeIds.push(node.id), + 'TB', + 'tree', + { + stopBranchFn: (node) => { + const shouldStop = + node.id !== root.id && (node.data.collapsed as boolean); + if (shouldStop) nodeIds.push(node.id); + return shouldStop; + }, + }, + ); + allEdgeIds = allEdgeIds.concat( + graphCore + .getAllEdges() + .filter( + (edge) => + nodeIds.includes(edge.source) && nodeIds.includes(edge.target), + ) + .map((edge) => edge.id), + ); + allNodeIds = allNodeIds.concat(nodeIds.filter((id) => id !== root.id)); + }); + const ids = uniq(allNodeIds.concat(allEdgeIds)); + this.graph.showItem(ids, !animate); + this.graph.layout(undefined, !animate); + } } const getItemTheme = ( diff --git a/packages/g6/src/runtime/controller/layout.ts b/packages/g6/src/runtime/controller/layout.ts index 7a47d815cb..486ac771ed 100644 --- a/packages/g6/src/runtime/controller/layout.ts +++ b/packages/g6/src/runtime/controller/layout.ts @@ -1,4 +1,5 @@ import { Animation, DisplayObject, IAnimationEffectTiming } from '@antv/g'; +import Hierarchy from '@antv/hierarchy'; import { Graph as GraphLib } from '@antv/graphlib'; import { isLayoutWithIterations, @@ -17,6 +18,7 @@ import { } from '../../types'; import { GraphCore } from '../../types/data'; import { EdgeModelData } from '../../types/edge'; +import { layoutOneTree } from '../../util/layout'; /** * Manages layout extensions and graph layout. @@ -48,6 +50,7 @@ export class LayoutController { private async onLayout(params: { graphCore: GraphCore; options: LayoutOptions; + animate?: boolean; }) { /** * The final calculated result. @@ -57,13 +60,15 @@ export class LayoutController { // Stop currentLayout if any. this.stopLayout(); - const { graphCore, options } = params; + const { graphCore, options, animate = true } = params; const layoutNodes = graphCore .getAllNodes() - .filter((node) => node.data.visible !== false && !node.data._isCombo); + .filter( + (node) => this.graph.getItemVisible(node.id) && !node.data._isCombo, + ); const layoutNodesIdMap = {}; layoutNodes.forEach((node) => (layoutNodesIdMap[node.id] = true)); - const layoutGraphCore = new GraphLib({ + const layoutData = { nodes: layoutNodes, edges: graphCore .getAllEdges() @@ -71,7 +76,10 @@ export class LayoutController { (edge) => layoutNodesIdMap[edge.source] && layoutNodesIdMap[edge.target], ), - }); + }; + const layoutGraphCore = new GraphLib( + layoutData, + ); this.graph.emit('startlayout'); @@ -112,6 +120,19 @@ export class LayoutController { throw new Error(`Unknown layout algorithm: ${type}`); } + if (Hierarchy[type]) { + // tree layout type + await this.handleTreeLayout( + type, + options, + animationEffectTiming, + graphCore, + layoutData, + animate, + ); + return; + } + // Initialize layout. const layout = new layoutCtor(rest); this.currentLayout = layout; @@ -170,7 +191,57 @@ export class LayoutController { this.graph.emit('endlayout'); // Update nodes' positions. - this.updateNodesPosition(positions); + this.updateNodesPosition(positions, animate); + } + + async handleTreeLayout( + type, + options, + animationEffectTiming, + graphCore, + layoutData, + animate, + ) { + const { animated = false, rootIds = [], begin = [0, 0] } = options; + const nodePositions = []; + const nodeMap = {}; + // tree layout with tree data + const trees = graphCore + .getRoots('tree') + .filter( + (node) => !node.data._isCombo, // this.graph.getItemVisible(node.id) && + ) + .map((node) => ({ id: node.id, children: [] })); + + trees.forEach((tree) => { + nodeMap[tree.id] = tree; + graphCore.dfsTree( + tree.id, + (node) => { + nodeMap[node.id].children = graphCore + .getChildren(node.id, 'tree') + .filter((node) => !node.data._isCombo) + .map((child) => { + nodeMap[child.id] = { id: child.id, children: [] }; + return nodeMap[child.id]; + }); + }, + 'tree', + ); + layoutOneTree(tree, type, options, nodeMap, nodePositions, begin); + }); + if (animated) { + await this.animateLayoutWithoutIterations( + { nodes: nodePositions, edges: [] }, + animationEffectTiming, + ); + } + this.graph.emit('endlayout'); + this.updateNodesPosition( + { nodes: nodePositions, edges: [] }, + animated || animate, + ); + return; } stopLayout() { @@ -202,8 +273,11 @@ export class LayoutController { } } - private updateNodesPosition(positions: LayoutMapping) { - this.graph.updateNodePosition(positions.nodes); + private updateNodesPosition( + positions: LayoutMapping, + animate: boolean = true, + ) { + this.graph.updateNodePosition(positions.nodes, undefined, !animate); } /** diff --git a/packages/g6/src/runtime/graph.ts b/packages/g6/src/runtime/graph.ts index 535fbed7c8..451ed0ea39 100644 --- a/packages/g6/src/runtime/graph.ts +++ b/packages/g6/src/runtime/graph.ts @@ -14,7 +14,7 @@ import { CameraAnimationOptions } from '../types/animate'; import { BehaviorOptionsOf, BehaviorRegistry } from '../types/behavior'; import { ComboModel } from '../types/combo'; import { Padding, Point } from '../types/common'; -import { DataChangeType, GraphCore } from '../types/data'; +import { DataChangeType, DataConfig, GraphCore } from '../types/data'; import { EdgeModel, EdgeModelData } from '../types/edge'; import { Hooks, ViewportChangeHookParams } from '../types/hook'; import { ITEM_TYPE, ShapeStyle, SHAPE_TYPE } from '../types/item'; @@ -250,6 +250,7 @@ export default class Graph changes: GraphChange[]; graphCore: GraphCore; theme: ThemeSpecification; + animate?: boolean; upsertAncestors?: boolean; }>({ name: 'itemchange' }), render: new Hook<{ @@ -259,7 +260,11 @@ export default class Graph }>({ name: 'render', }), - layout: new Hook<{ graphCore: GraphCore }>({ name: 'layout' }), + layout: new Hook<{ + graphCore: GraphCore; + options?: LayoutOptions; + animate?: boolean; + }>({ name: 'layout' }), viewportchange: new Hook({ name: 'viewport' }), modechange: new Hook<{ mode: string }>({ name: 'modechange' }), behaviorchange: new Hook<{ @@ -309,6 +314,12 @@ export default class Graph transient: Canvas; }; }>({ name: 'init' }), + treecollapseexpand: new Hook<{ + ids: ID[]; + animate: boolean; + action: 'collapse' | 'expand'; + graphCore: GraphCore; + }>({ name: 'treecollapseexpand' }), destroy: new Hook<{}>({ name: 'destroy' }), }; } @@ -355,7 +366,7 @@ export default class Graph * @returns * @group Data */ - public async read(data: GraphData) { + public async read(data: DataConfig) { this.hooks.datachange.emit({ data, type: 'replace' }); const emitRender = async () => { this.hooks.render.emit({ @@ -387,7 +398,7 @@ export default class Graph * @group Data */ public async changeData( - data: GraphData, + data: DataConfig, type: 'replace' | 'mergeReplace' = 'mergeReplace', ) { this.hooks.datachange.emit({ data, type }); @@ -1028,9 +1039,21 @@ export default class Graph ComboUserModel | Partial[] | Partial[] >, upsertAncestors?: boolean, + disableAnimate: boolean = false, + callback?: ( + model: NodeModel | EdgeModel | ComboModel, + canceled?: boolean, + ) => void, stack?: boolean, ) { - return this.updatePosition('node', models, upsertAncestors, stack); + return this.updatePosition( + 'node', + models, + upsertAncestors, + disableAnimate, + callback, + stack, + ); } /** @@ -1048,9 +1071,18 @@ export default class Graph ComboUserModel | Partial[] | Partial[] >, upsertAncestors?: boolean, + disableAnimate: boolean = false, + callback?: (model: NodeModel | EdgeModel | ComboModel) => void, stack?: boolean, ) { - return this.updatePosition('combo', models, upsertAncestors, stack); + return this.updatePosition( + 'combo', + models, + upsertAncestors, + disableAnimate, + callback, + stack, + ); } private updatePosition( @@ -1061,6 +1093,11 @@ export default class Graph ComboUserModel | Partial[] | Partial[] >, upsertAncestors?: boolean, + disableAnimate: boolean = false, + callback?: ( + model: NodeModel | EdgeModel | ComboModel, + canceled?: boolean, + ) => void, stack?: boolean, ) { const modelArr = isArray(models) ? models : [models]; @@ -1075,6 +1112,8 @@ export default class Graph theme: specification, upsertAncestors, action: 'updatePosition', + animate: !disableAnimate, + callback, }); this.emit('afteritemchange', { type, @@ -1105,13 +1144,13 @@ export default class Graph * @returns * @group Item */ - public showItem(ids: ID | ID[], disableAniamte?: boolean) { + public showItem(ids: ID | ID[], disableAnimate: boolean = false) { const idArr = isArray(ids) ? ids : [ids]; this.hooks.itemvisibilitychange.emit({ ids: idArr as ID[], value: true, graphCore: this.dataController.graphCore, - animate: !disableAniamte, + animate: !disableAnimate, }); } /** @@ -1120,13 +1159,13 @@ export default class Graph * @returns * @group Item */ - public hideItem(ids: ID | ID[], disableAniamte?: boolean) { + public hideItem(ids: ID | ID[], disableAnimate: boolean = false) { const idArr = isArray(ids) ? ids : [ids]; this.hooks.itemvisibilitychange.emit({ ids: idArr as ID[], value: false, graphCore: this.dataController.graphCore, - animate: !disableAniamte, + animate: !disableAnimate, }); } @@ -1364,7 +1403,10 @@ export default class Graph /** * Layout the graph (with current configurations if cfg is not assigned). */ - public async layout(options?: LayoutOptions) { + public async layout( + options?: LayoutOptions, + disableAnimate: boolean = false, + ) { const { graphCore } = this.dataController; const formattedOptions = { ...this.getSpecification().layout, @@ -1400,6 +1442,7 @@ export default class Graph await this.hooks.layout.emitLinearAsync({ graphCore, options: formattedOptions, + animate: !disableAnimate, }); this.emit('afterlayout'); } @@ -1653,6 +1696,47 @@ export default class Graph return this.itemController.getTransient(String(id)); } + /** + * Collapse sub tree(s). + * @param ids Root id(s) of the sub trees. + * @param disableAnimate Whether disable the animations for this operation. + * @param stack Whether push this operation to stack. + * @returns + * @group Tree + */ + public collapse( + ids: ID | ID[], + disableAnimate: boolean = false, + stack?: boolean, + ) { + this.hooks.treecollapseexpand.emit({ + ids: isArray(ids) ? ids : [ids], + action: 'collapse', + animate: !disableAnimate, + graphCore: this.dataController.graphCore, + }); + } + /** + * Expand sub tree(s). + * @param ids Root id(s) of the sub trees. + * @param disableAnimate Whether disable the animations for this operation. + * @param stack Whether push this operation to stack. + * @returns + * @group Tree + */ + public expand( + ids: ID | ID[], + disableAnimate: boolean = false, + stack?: boolean, + ) { + this.hooks.treecollapseexpand.emit({ + ids: isArray(ids) ? ids : [ids], + action: 'expand', + animate: !disableAnimate, + graphCore: this.dataController.graphCore, + }); + } + /** * Destroy the graph instance and remove the related canvases. * @returns diff --git a/packages/g6/src/stdlib/behavior/collapse-expand-tree.ts b/packages/g6/src/stdlib/behavior/collapse-expand-tree.ts new file mode 100644 index 0000000000..643dfb5981 --- /dev/null +++ b/packages/g6/src/stdlib/behavior/collapse-expand-tree.ts @@ -0,0 +1,106 @@ +import { Behavior } from '../../types/behavior'; +import { IG6GraphEvent } from '../../types/event'; + +const ALLOWED_TRIGGERS = ['click', 'dblclick'] as const; +type Trigger = (typeof ALLOWED_TRIGGERS)[number]; + +interface Options { + /** + * The key to pressed with mouse click to apply multiple selection. + * Defaults to `"shift"`. + * Could be "shift", "ctrl", "alt", or "meta". + */ + trigger: Trigger; + /** + * The event name to trigger when select/unselect an item. + */ + eventName: string; + /** + * Whether disable the collapse / expand animation triggered by this behavior. + */ + disableAnimate: boolean; + /** + * Whether allow the behavior happen on the current item. + */ + shouldBegin: (event: IG6GraphEvent) => boolean; + /** + * Whether to update item state. + * If it returns false, you may probably listen to `eventName` and + * manage states or data manually + */ + shouldUpdate: (event: IG6GraphEvent) => boolean; +} + +const DEFAULT_OPTIONS: Options = { + trigger: 'click', + eventName: '', + disableAnimate: false, + shouldBegin: () => true, + shouldUpdate: () => true, +}; + +export default class CollapseExpandTree extends Behavior { + options: Options; + private timeout: NodeJS.Timeout = undefined; + + constructor(options: Partial) { + super(Object.assign({}, DEFAULT_OPTIONS, options)); + // Validate options + if (options.trigger && !ALLOWED_TRIGGERS.includes(options.trigger)) { + console.warn( + `G6: Invalid trigger option "${options.trigger}" for collapse-expand-tree behavior!`, + ); + this.options.trigger = DEFAULT_OPTIONS.trigger; + } + } + + getEvents = () => { + return this.options.trigger === 'dblclick' + ? { + 'node:dblclick': this.onClick, + } + : { + 'node:click': this.onClickBesideDblClick, + }; + }; + + public onClickBesideDblClick(event: IG6GraphEvent) { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = undefined; + return; + } + this.timeout = setTimeout(() => { + this.timeout = undefined; + this.onClick(event); + }, 200); + } + + public onClick(event: IG6GraphEvent) { + if (!this.options.shouldBegin(event)) return; + const { itemId, itemType } = event; + const { disableAnimate } = this.options; + + const model = this.graph.getNodeData(itemId); + if (!model) { + console.warn(`Node with id ${itemId} is not exist`); + return; + } + let action = 'expand'; + if (model.data.collapsed) { + this.graph.expand(itemId, disableAnimate); + } else { + this.graph.collapse(itemId, disableAnimate); + action = 'collapse'; + } + + // Emit an event. + if (this.options.eventName) { + this.graph.emit(this.options.eventName, { + action, + itemId, + itemType, + }); + } + } +} diff --git a/packages/g6/src/stdlib/index.ts b/packages/g6/src/stdlib/index.ts index ca378bec92..bf9e95c45b 100644 --- a/packages/g6/src/stdlib/index.ts +++ b/packages/g6/src/stdlib/index.ts @@ -1,4 +1,5 @@ import { registry as layoutRegistry } from '@antv/layout'; +import Hierarchy from '@antv/hierarchy'; import { Lib } from '../types/stdlib'; import ActivateRelations from './behavior/activate-relations'; import BrushSelect from './behavior/brush-select'; @@ -30,6 +31,7 @@ import TrackCanvas3D from './behavior/track-canvas-3d'; import OrbitCanvas3D from './behavior/orbit-canvas-3d'; import HoverActivate from './behavior/hover-activate'; import CollapseExpandCombo from './behavior/collapse-expand-combo'; +import CollapseExpandTree from './behavior/collapse-expand-tree'; import { CubicEdge } from './item/edge/cubic'; import { CubicHorizonEdge } from './item/edge/cubic-horizon'; import { CubicVerticalEdge } from './item/edge/cubic-vertical'; @@ -48,7 +50,10 @@ const stdLib = { spec: SpecThemeSolver, subject: SubjectThemeSolver, }, - layouts: layoutRegistry, + layouts: { + ...layoutRegistry, + ...Hierarchy, + }, behaviors: { 'activate-relations': ActivateRelations, 'drag-canvas': DragCanvas, @@ -57,6 +62,7 @@ const stdLib = { 'drag-node': DragNode, 'drag-combo': DragCombo, 'collapse-expand-combo': CollapseExpandCombo, + 'collapse-expand-tree': CollapseExpandTree, 'click-select': ClickSelect, 'brush-select': BrushSelect, 'lasso-select': LassoSelect, diff --git a/packages/g6/src/stdlib/plugin/grid/index.ts b/packages/g6/src/stdlib/plugin/grid/index.ts index fd05897ffb..de33249cad 100644 --- a/packages/g6/src/stdlib/plugin/grid/index.ts +++ b/packages/g6/src/stdlib/plugin/grid/index.ts @@ -35,8 +35,6 @@ export default class Grid extends Base { public init(graph: IGraph) { super.init(graph); const minZoom = graph.getZoom(); - // console.log('minZoom', minZoom); - // console.log('graph', graph.canvas); const graphContainer = graph.container; const canvas = this.canvas || graphContainer.firstChild.nextSibling; const [width, height] = graph.getSize(); diff --git a/packages/g6/src/types/data.ts b/packages/g6/src/types/data.ts index 9701f61ce4..835bdbe47c 100644 --- a/packages/g6/src/types/data.ts +++ b/packages/g6/src/types/data.ts @@ -1,7 +1,8 @@ -import { Graph as GraphLib } from '@antv/graphlib'; +import { Graph as GraphLib, TreeData } from '@antv/graphlib'; import { ComboUserModel } from './combo'; import { NodeDisplayModelData, NodeModelData, NodeUserModel } from './node'; import { EdgeDisplayModelData, EdgeModelData, EdgeUserModel } from './edge'; +import { NodeUserModelData } from './node'; export interface GraphData { nodes?: NodeUserModel[]; @@ -9,16 +10,26 @@ export interface GraphData { combos?: ComboUserModel[]; } -export interface InlineDataConfig { - type: 'inline'; +export interface InlineGraphDataConfig { + type: 'graphData'; value: GraphData; } +export interface InlineTreeDataConfig { + type: 'treeData'; + value: TreeData | TreeData[]; +} export interface FetchDataConfig { type: 'fetch'; value: string; } +export type DataConfig = + | GraphData + | InlineGraphDataConfig + | InlineTreeDataConfig + | FetchDataConfig; + export type GraphCore = GraphLib; export type DisplayGraphCore = GraphLib< NodeDisplayModelData, diff --git a/packages/g6/src/types/graph.ts b/packages/g6/src/types/graph.ts index 1101e6c01e..562a6c2ff2 100644 --- a/packages/g6/src/types/graph.ts +++ b/packages/g6/src/types/graph.ts @@ -232,6 +232,11 @@ export interface IGraph< ComboUserModel | Partial[] | Partial[] >, upsertAncestors?: boolean, + disableAnimate?: boolean, + callback?: ( + model: NodeModel | EdgeModel | ComboModel, + canceled?: boolean, + ) => void, stack?: boolean, ) => NodeModel | ComboModel | NodeModel[] | ComboModel[]; @@ -249,6 +254,8 @@ export interface IGraph< ComboUserModel | Partial[] | Partial[] >, upsertAncestors?: boolean, + disableAnimate?: boolean, + callback?: (model: NodeModel | EdgeModel | ComboModel) => void, stack?: boolean, ) => NodeModel | ComboModel | NodeModel[] | ComboModel[]; @@ -444,14 +451,14 @@ export interface IGraph< * @returns * @group Data */ - showItem: (ids: ID | ID[], disableAniamte?: boolean) => void; + showItem: (ids: ID | ID[], disableAnimate?: boolean) => void; /** * Hide the item(s). * @param ids the item id(s) to be hidden * @returns * @group Item */ - hideItem: (ids: ID | ID[], disableAniamte?: boolean) => void; + hideItem: (ids: ID | ID[], disableAnimate?: boolean) => void; /** * Make the item(s) to the front. * @param ids the item id(s) to front @@ -543,7 +550,7 @@ export interface IGraph< /** * Layout the graph (with current configurations if cfg is not assigned). */ - layout: (options?: LayoutOptions) => Promise; + layout: (options?: LayoutOptions, disableAnimate?: boolean) => Promise; stopLayout: () => void; // ===== interaction ===== @@ -627,4 +634,24 @@ export interface IGraph< type: string; [cfgName: string]: unknown; }) => void; + + // ===== tree operations ===== + /** + * Collapse sub tree(s). + * @param ids Root id(s) of the sub trees. + * @param disableAnimate Whether disable the animations for this operation. + * @param stack Whether push this operation to stack. + * @returns + * @group Tree + */ + collapse: (ids: ID | ID[], disableAnimate?: boolean, stack?: boolean) => void; + /** + * Expand sub tree(s). + * @param ids Root id(s) of the sub trees. + * @param disableAnimate Whether disable the animations for this operation. + * @param stack Whether push this operation to stack. + * @returns + * @group Tree + */ + expand: (ids: ID | ID[], disableAnimate?: boolean, stack?: boolean) => void; } diff --git a/packages/g6/src/types/hook.ts b/packages/g6/src/types/hook.ts index c03dc0d1e8..ebf247b703 100644 --- a/packages/g6/src/types/hook.ts +++ b/packages/g6/src/types/hook.ts @@ -2,13 +2,14 @@ import { Canvas } from '@antv/g'; import { GraphChange, ID } from '@antv/graphlib'; import { CameraAnimationOptions } from './animate'; import { BehaviorOptionsOf } from './behavior'; -import { DataChangeType, GraphCore, GraphData } from './data'; -import { EdgeModelData } from './edge'; +import { DataChangeType, DataConfig, GraphCore } from './data'; +import { EdgeModel, EdgeModelData } from './edge'; import { ITEM_TYPE, ShapeStyle, SHAPE_TYPE } from './item'; import { LayoutOptions } from './layout'; -import { NodeModelData } from './node'; +import { NodeModel, NodeModelData } from './node'; import { ThemeSpecification } from './theme'; import { GraphTransformOptions } from './view'; +import { ComboModel } from './combo'; export interface IHook { name: string; @@ -35,7 +36,7 @@ export interface Hooks { // data datachange: IHook<{ type: DataChangeType; - data: GraphData; + data: DataConfig; }>; itemchange: IHook<{ type: ITEM_TYPE; @@ -43,14 +44,20 @@ export interface Hooks { graphCore: GraphCore; theme: ThemeSpecification; upsertAncestors?: boolean; + animate?: boolean; action?: 'updatePosition'; + callback?: (model: NodeModel | EdgeModel | ComboModel) => void; }>; render: IHook<{ graphCore: GraphCore; theme: ThemeSpecification; transientCanvas: Canvas; }>; // TODO: define param template - layout: IHook<{ graphCore: GraphCore; options?: LayoutOptions }>; // TODO: define param template + layout: IHook<{ + graphCore: GraphCore; + options?: LayoutOptions; + animate?: boolean; + }>; // TODO: define param template // 'updatelayout': IHook; // TODO: define param template modechange: IHook<{ mode: string }>; behaviorchange: IHook<{ @@ -101,6 +108,11 @@ export interface Hooks { transient: Canvas; }; }>; + treecollapseexpand: IHook<{ + ids: ID[]; + action: 'collapse' | 'expand'; + graphCore: GraphCore; + animate?: boolean; + }>; destroy: IHook<{}>; - // TODO: more timecycles here } diff --git a/packages/g6/src/types/item.ts b/packages/g6/src/types/item.ts index 80b97cc0d1..8288184ec4 100644 --- a/packages/g6/src/types/item.ts +++ b/packages/g6/src/types/item.ts @@ -44,6 +44,7 @@ import { NodeShapeMap, NodeUserModel, } from './node'; +import { ComboStyleSet, EdgeStyleSet, NodeStyleSet } from './theme'; export type GShapeStyle = CircleStyleProps & RectStyleProps & @@ -248,6 +249,8 @@ export interface IItem { displayModel: ItemDisplayModel, diffData?: { previous: ItemModelData; current: ItemModelData }, diffState?: { previous: State[]; current: State[] }, + animate?: boolean, + onfinish?: Function, ) => void; /** * Updates the shapes. @@ -256,7 +259,14 @@ export interface IItem { update: ( model: ItemModel, diffData: { previous: ItemModelData; current: ItemModelData }, - isUpdate?: boolean, + isReplace?: boolean, + itemTheme?: { + styles: NodeStyleSet | EdgeStyleSet | ComboStyleSet; + lodStrategy: LodStrategyObj; + }, + onlyMove?: boolean, + animate?: boolean, + onfinish?: Function, ) => void; /** @@ -269,6 +279,7 @@ export interface IItem { updatePosition: ( displayModel: ItemDisplayModel, diffData?: { previous: ItemModelData; current: ItemModelData }, + animate?: boolean, onfinish?: Function, ) => void; diff --git a/packages/g6/src/types/node.ts b/packages/g6/src/types/node.ts index da9321a95d..115b7ca594 100644 --- a/packages/g6/src/types/node.ts +++ b/packages/g6/src/types/node.ts @@ -45,6 +45,10 @@ export interface NodeUserModelData extends PlainObject { * Reserved for combo. */ parentId?: ID; + /** + * Whether to be a root at when used as a tree. + */ + isRoot?: boolean; /** * The icon to show on the node. * More styles should be configured in node mapper. diff --git a/packages/g6/src/types/spec.ts b/packages/g6/src/types/spec.ts index 09bc8c659f..c2bf0b0cbf 100644 --- a/packages/g6/src/types/spec.ts +++ b/packages/g6/src/types/spec.ts @@ -1,12 +1,7 @@ import { Canvas } from '@antv/g'; import { AnimateCfg } from './animate'; import { Point } from './common'; -import { - FetchDataConfig, - GraphData, - InlineDataConfig, - TransformerFn, -} from './data'; +import { DataConfig, TransformerFn } from './data'; import { EdgeDisplayModel, EdgeEncode, @@ -36,7 +31,6 @@ export interface Specification< B extends BehaviorRegistry, T extends ThemeRegistry, > { - type: 'graph' | 'tree'; container?: string | HTMLElement; backgroundCanvas?: Canvas; canvas?: Canvas; @@ -61,7 +55,7 @@ export interface Specification< optimizeThreshold?: number; /** data */ - data: GraphData | InlineDataConfig | FetchDataConfig; // TODO: more + data?: DataConfig; transform?: | string[] | { diff --git a/packages/g6/src/util/animate.ts b/packages/g6/src/util/animate.ts index 5b18a482f8..e301488d82 100644 --- a/packages/g6/src/util/animate.ts +++ b/packages/g6/src/util/animate.ts @@ -248,7 +248,7 @@ const runAnimateGroupOnShapes = ( maxDurationIdx = i; } if (animation) { - animation.oncancel = () => { + animation.onManualCancel = () => { hasCanceled = true; cancelAnimations(); }; @@ -293,10 +293,37 @@ const runAnimateOnShape = ( } }); } - if (JSON.stringify(animateArr[0]) === JSON.stringify(animateArr[1])) return; + if (!checkFrames(animateArr, shape)) return; return shape.animate(animateArr, animateConfig); }; +/** + * Check and format the frames. If the frames are same, return false. If frames contains undefined x or y, format them. + * @param frames + * @param shape + * @returns + */ +const checkFrames = (frames, shape) => { + if (JSON.stringify(frames[0]) === JSON.stringify(frames[1])) return false; + ['x', 'y'].forEach((dim) => { + if (!frames[0].hasOwnProperty(dim)) return; + let val; + const formatted = [...frames]; + if (frames[0][dim] === undefined && frames[0][dim] !== frames[1][dim]) + val = frames[1][dim]; + if (frames[1][dim] === undefined && frames[0][dim] !== frames[1][dim]) + val = frames[1][dim]; + if (val !== undefined) { + shape.style[dim] = val; + delete formatted[0][dim]; + delete formatted[1][dim]; + } + }); + if (JSON.stringify(frames[0]) === JSON.stringify(frames[1])) return false; + + return true; +}; + /** * Handle shape and group animations. * Should be called after canvas ready and shape appended. @@ -318,7 +345,7 @@ export const animateShapes = ( onAnimatesEnd: Function = () => {}, ): IAnimation[] => { if (!animates?.[timing]) { - onAnimatesEnd(); + onAnimatesEnd(false); return; } const segmentedTiming = @@ -335,7 +362,7 @@ export const animateShapes = ( let canceled = false; const onfinish = () => { if (i >= groupKeys.length) { - onAnimatesEnd(); + !canceled && onAnimatesEnd(canceled); return; } const groupAnimations = runAnimateGroupOnShapes( @@ -345,7 +372,10 @@ export const animateShapes = ( mergedStyles, timing, onfinish, // execute next order group - () => (canceled = true), + () => { + canceled = true; + onAnimatesEnd(canceled); + }, canceled, ).filter(Boolean); groupAnimations.forEach((animation) => { @@ -429,8 +459,9 @@ export const fadeOut = (id, shape, hiddenShape, animateConfig) => { * Make the animation to the end frame and clear it from the target shape. * @param animation */ -export const stopAnimate = (animation) => { +export const stopAnimate = (animation: IAnimation): Promise => { const timing = animation.effect.getTiming(); animation.currentTime = Number(timing.duration) + Number(timing.delay || 0); - animation.cancel(); + animation.finish(); + return animation.finished; }; diff --git a/packages/g6/src/util/data.ts b/packages/g6/src/util/data.ts index 9b5c9c27e6..f06f25ae74 100644 --- a/packages/g6/src/util/data.ts +++ b/packages/g6/src/util/data.ts @@ -1,6 +1,14 @@ import { NodeUserModel } from 'types'; import { IGraph } from '../types/graph'; -import { GraphCore } from '../types/data'; +import { GraphCore, GraphData } from '../types/data'; +import { TreeData } from '@antv/graphlib'; +import { NodeUserModelData } from 'types/node'; +import { isArray } from '@antv/util'; +import { + depthFirstSearch, + breadthFirstSearch, + connectedComponent, +} from '@antv/algorithm'; /** * Deconstruct data and distinguish nodes and combos from graphcore data. @@ -35,19 +43,30 @@ export const graphCoreTreeDfs = ( nodes: NodeUserModel[], fn, mode: 'TB' | 'BT' = 'TB', + treeKey = 'combo', + stopFns: { + stopBranchFn?: (node: NodeUserModel) => boolean; + stopAllFn?: (node: NodeUserModel) => boolean; + } = {}, ) => { if (!nodes?.length) return; - nodes.forEach((node) => { - if (!graphCore.hasNode(node.id)) return; + const { stopBranchFn, stopAllFn } = stopFns; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (!graphCore.hasNode(node.id)) continue; + if (stopBranchFn?.(node)) continue; // Stop this branch + if (stopAllFn?.(node)) return; // Stop all if (mode === 'TB') fn(node); // Traverse from top to bottom graphCoreTreeDfs( graphCore, - graphCore.getChildren(node.id, 'combo'), + graphCore.getChildren(node.id, treeKey), fn, mode, + treeKey, + stopFns, ); if (mode !== 'TB') fn(node); // Traverse from bottom to top - }); + } }; /** @@ -176,3 +195,105 @@ export const validateComboStrucutre = ( } return true; }; + +/** + * Transform tree graph data into graph data. Edges from parent-child structure. + * @param treeData Tree structured data or an array of it. + * @returns Graph formatted data object with nodes, edges and combos. + */ +export const treeData2GraphData = ( + treeData: TreeData | TreeData[], +) => { + const graphData = { + nodes: [], + edges: [], + combos: [], + }; + const trees = isArray(treeData) ? treeData : [treeData]; + trees.forEach((tree) => { + traverse(tree, (child) => { + graphData.nodes.push({ + id: child.id, + data: child.data, + }); + child.children?.forEach((subChild) => { + graphData.edges.push({ + id: `tree-edge-${child.id}-${subChild.id}`, + source: child.id, + target: subChild.id, + data: {}, + }); + }); + }); + }); + return graphData; +}; + +/** + * Transform graph data into tree graph data. + * @param nodeMap + * @param graphData Graph data. + * @param propRootIds Ids of root nodes. There should be at least one node for each connected component, or the first node in a connected component will be added to the roots array. + * @param algo + * @returns + */ +export const graphData2TreeData = ( + nodeMap: { [id: string]: any }, + graphData: GraphData, + propRootIds = [], +) => { + const trees = []; + const connectedComponents = connectedComponent(graphData as any, false); + const rootIds = []; + const componentsNodeIds = []; + connectedComponents.forEach((com, i) => { + componentsNodeIds[i] = com.map((node) => node.id); + if (propRootIds.length) { + const root = componentsNodeIds[0].find((id) => propRootIds.includes(id)); + rootIds.push(root !== undefined ? root : com[0].id); + } else { + rootIds.push(com[0].id); + } + }); + + rootIds.forEach((id, i) => { + nodeMap[id] = { id, children: [] }; + trees.push(nodeMap[id]); + depthFirstSearch( + graphData as any, + id, + { + enter: ({ previous, current }) => { + if ( + !previous || + current === id || + !componentsNodeIds[i].includes(current) + ) + return; + nodeMap[previous] = nodeMap[previous] || { + id: previous, + children: [], + }; + nodeMap[current] = { id: current, children: [] }; + nodeMap[previous].children.push(nodeMap[current]); + }, + }, + false, + ); + }); + return trees; +}; + +/** + * Travere a tree data from top to bottom. + * @param treeData + * @param callback + */ +export const traverse = (treeData, callback) => { + callback(treeData); + if (treeData.children) { + treeData.children.forEach((child) => { + if (child) traverse(child, callback); + }); + } +}; diff --git a/packages/g6/src/util/event.ts b/packages/g6/src/util/event.ts index 463808ddba..eb1a085df2 100644 --- a/packages/g6/src/util/event.ts +++ b/packages/g6/src/util/event.ts @@ -83,6 +83,7 @@ type GroupedChanges = { EdgeUpdated: EdgeUpdated[]; EdgeDataUpdated: EdgeDataUpdated[]; TreeStructureChanged: TreeStructureChanged[]; + ComboStructureChanged: TreeStructureChanged[]; }; /** @@ -104,6 +105,7 @@ export const getGroupedChanges = ( EdgeUpdated: [], EdgeDataUpdated: [], TreeStructureChanged: [], + ComboStructureChanged: [], }; changes.forEach((change) => { const { type: changeType } = change; @@ -119,7 +121,10 @@ export const getGroupedChanges = ( return; } } else if (changeType === 'TreeStructureChanged') { - groupedChanges[changeType].push(change); + if (change.treeKey === 'combo') + groupedChanges.ComboStructureChanged.push(change); + else if (change.treeKey === 'tree') + groupedChanges.TreeStructureChanged.push(change); return; } else { const { id: oid } = change.value; diff --git a/packages/g6/src/util/layout.ts b/packages/g6/src/util/layout.ts new file mode 100644 index 0000000000..bdaab3e3e1 --- /dev/null +++ b/packages/g6/src/util/layout.ts @@ -0,0 +1,65 @@ +import Hierarchy from '@antv/hierarchy'; +import { traverse } from './data'; +import { TreeGraphData } from '@antv/g6'; + +/** + * Judge the direction according to options of a tree layout. + * @param type Tree layout type. + * @param options Tree layout options. + * @returns + */ +export const isTreeLayoutHorizontal = (type, options) => { + const { direction } = options; + switch (type) { + case 'compactBox': + case 'dendrogram': + return direction !== 'TB' && direction !== ' BT' && direction !== ' V'; + case 'indented': + return true; + case 'mindmap': + return direction !== 'V'; + } +}; + +/** + * Layout nodes on a single tree. + * @param treeData + * @param layoutType Tree layout type. + * @param layoutOptions Tree layout options. + * @param nodeMap + * @param nodePositions An array to store the result. + * @param begin The begin position for this tree, might be calculated from last tree. + * @returns Positions array. + */ +export const layoutOneTree = ( + treeData: TreeGraphData, + layoutType: string, + layoutOptions, + nodeMap, + nodePositions, + begin = [0, 0], +) => { + const { treeGap = 50 } = layoutOptions; + const isHorizontal = isTreeLayoutHorizontal(layoutType, layoutOptions); + const layoutData = Hierarchy[layoutType](treeData, layoutOptions); + const range = [Infinity, -Infinity]; + const treeNodeIds = []; + traverse(layoutData, (child) => { + const { id, x, y } = child; + treeNodeIds.push(id); + const dim = isHorizontal ? 'y' : 'x'; + if (range[0] > child[dim]) range[0] = child[dim]; + if (range[1] < child[dim]) range[1] = child[dim]; + nodeMap[id].data = { x, y }; + }); + const diff = begin[isHorizontal ? 1 : 0] - range[0]; + treeNodeIds.forEach((id) => { + const { x, y } = nodeMap[id].data; + nodePositions.push({ + id, + data: isHorizontal ? { x, y: y + diff } : { x: x + diff, y }, + }); + }); + begin[isHorizontal ? 1 : 0] += range[1] + diff + treeGap; + return nodePositions; +}; diff --git a/packages/g6/tests/demo/behaviors/brush-select.ts b/packages/g6/tests/demo/behaviors/brush-select.ts index 082f1f1e90..f7c5533907 100644 --- a/packages/g6/tests/demo/behaviors/brush-select.ts +++ b/packages/g6/tests/demo/behaviors/brush-select.ts @@ -5,7 +5,6 @@ export default () => { container, width, height, - type: 'graph', plugins: ['grid'], layout: { type: 'grid', diff --git a/packages/g6/tests/demo/behaviors/click-select.ts b/packages/g6/tests/demo/behaviors/click-select.ts index c497fab4c0..cdb5969576 100644 --- a/packages/g6/tests/demo/behaviors/click-select.ts +++ b/packages/g6/tests/demo/behaviors/click-select.ts @@ -5,7 +5,6 @@ export default () => { container, width, height, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/demo/combo/combo-basic.ts b/packages/g6/tests/demo/combo/combo-basic.ts index c6b9fcffcf..f928b55d65 100644 --- a/packages/g6/tests/demo/combo/combo-basic.ts +++ b/packages/g6/tests/demo/combo/combo-basic.ts @@ -5,7 +5,6 @@ export default () => { container, width, height, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/demo/demo/bugReproduce.ts b/packages/g6/tests/demo/demo/bugReproduce.ts index 87754e3bbe..f6e9d0af3d 100644 --- a/packages/g6/tests/demo/demo/bugReproduce.ts +++ b/packages/g6/tests/demo/demo/bugReproduce.ts @@ -58,7 +58,6 @@ export default () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, diff --git a/packages/g6/tests/demo/demo/demo.ts b/packages/g6/tests/demo/demo/demo.ts index ccf180208a..fed8875c0a 100644 --- a/packages/g6/tests/demo/demo/demo.ts +++ b/packages/g6/tests/demo/demo/demo.ts @@ -124,7 +124,6 @@ const create2DGraph = ( container: container as HTMLElement, width, height: 1400, - type: 'graph', renderer: rendererType, data: dataFor2D, modes: { @@ -223,7 +222,6 @@ const create3DGraph = async () => { container: container as HTMLDivElement, width, height: 1400, - type: 'graph', renderer: 'webgl-3d', data: dataFor3D, // layout: { diff --git a/packages/g6/tests/demo/demo/menu.ts b/packages/g6/tests/demo/demo/menu.ts index 47c5278720..db6176dae0 100644 --- a/packages/g6/tests/demo/demo/menu.ts +++ b/packages/g6/tests/demo/demo/menu.ts @@ -44,7 +44,6 @@ export default () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, diff --git a/packages/g6/tests/demo/demo/quadratic.ts b/packages/g6/tests/demo/demo/quadratic.ts index 092784bfc5..e48dd1231f 100644 --- a/packages/g6/tests/demo/demo/quadratic.ts +++ b/packages/g6/tests/demo/demo/quadratic.ts @@ -66,7 +66,6 @@ export default () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, diff --git a/packages/g6/tests/demo/demo/rect.ts b/packages/g6/tests/demo/demo/rect.ts index ae834bbcae..d3803f97ca 100644 --- a/packages/g6/tests/demo/demo/rect.ts +++ b/packages/g6/tests/demo/demo/rect.ts @@ -51,7 +51,6 @@ export default () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, diff --git a/packages/g6/tests/demo/demo/tooltip.ts b/packages/g6/tests/demo/demo/tooltip.ts index b367fa280e..df867e7d44 100644 --- a/packages/g6/tests/demo/demo/tooltip.ts +++ b/packages/g6/tests/demo/demo/tooltip.ts @@ -44,7 +44,6 @@ export default () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, diff --git a/packages/g6/tests/demo/index.ts b/packages/g6/tests/demo/index.ts index fee891f839..55cfbbc2c2 100644 --- a/packages/g6/tests/demo/index.ts +++ b/packages/g6/tests/demo/index.ts @@ -32,6 +32,7 @@ import fisheye from './plugins/fisheye'; import tooltip from './demo/tooltip'; import comboBasic from './combo/combo-basic'; import animations_node_build_in from './animations/node-build-in'; +import treeGraph from './tree/tree-graph'; export { behaviors_activateRelations, @@ -68,4 +69,5 @@ export { tooltip, comboBasic, animations_node_build_in, + treeGraph, }; diff --git a/packages/g6/tests/demo/item/edge/cubic-edge.ts b/packages/g6/tests/demo/item/edge/cubic-edge.ts index 951c37f40a..ed78f526e6 100644 --- a/packages/g6/tests/demo/item/edge/cubic-edge.ts +++ b/packages/g6/tests/demo/item/edge/cubic-edge.ts @@ -231,7 +231,6 @@ export default () => { container, width: 500, height: 500, - type: 'graph', data: defaultData, modes: { // supported behavior diff --git a/packages/g6/tests/demo/item/edge/cubic-horizon-edge.ts b/packages/g6/tests/demo/item/edge/cubic-horizon-edge.ts index 3326f99a97..5e76d86e6f 100644 --- a/packages/g6/tests/demo/item/edge/cubic-horizon-edge.ts +++ b/packages/g6/tests/demo/item/edge/cubic-horizon-edge.ts @@ -234,7 +234,6 @@ export default () => { container, width: 500, height: 500, - type: 'graph', data: defaultData, modes: { // 支持的 behavior diff --git a/packages/g6/tests/demo/item/edge/cubic-vertical-edge.ts b/packages/g6/tests/demo/item/edge/cubic-vertical-edge.ts index 33f6f149dc..af23ea8803 100644 --- a/packages/g6/tests/demo/item/edge/cubic-vertical-edge.ts +++ b/packages/g6/tests/demo/item/edge/cubic-vertical-edge.ts @@ -231,7 +231,6 @@ export default () => { container, width: 500, height: 500, - type: 'graph', data: defaultData, modes: { // supported behavior diff --git a/packages/g6/tests/demo/item/edge/line-edge.ts b/packages/g6/tests/demo/item/edge/line-edge.ts index 453c92e1c1..c95868a39d 100644 --- a/packages/g6/tests/demo/item/edge/line-edge.ts +++ b/packages/g6/tests/demo/item/edge/line-edge.ts @@ -227,7 +227,6 @@ export default (context: TestCaseContext) => { // 2.create graph graph = new Graph({ ...context, - type: 'graph', data: defaultData, modes: { // supported behavior diff --git a/packages/g6/tests/demo/layouts/d3force.ts b/packages/g6/tests/demo/layouts/d3force.ts index 73bae6b95a..7a9cd88123 100644 --- a/packages/g6/tests/demo/layouts/d3force.ts +++ b/packages/g6/tests/demo/layouts/d3force.ts @@ -6,7 +6,6 @@ export default (context: TestCaseContext) => { const { width, height } = context; return new G6.Graph({ ...context, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'd3force', diff --git a/packages/g6/tests/demo/layouts/force-wasm-3d.ts b/packages/g6/tests/demo/layouts/force-wasm-3d.ts index fb96d0a18c..a4271efa17 100644 --- a/packages/g6/tests/demo/layouts/force-wasm-3d.ts +++ b/packages/g6/tests/demo/layouts/force-wasm-3d.ts @@ -17,7 +17,6 @@ export default async () => { container, width, height, - type: 'graph', data: JSON.parse(JSON.stringify(data)), renderer: 'webgl-3d', modes: { diff --git a/packages/g6/tests/demo/layouts/force-wasm.ts b/packages/g6/tests/demo/layouts/force-wasm.ts index 8cd0f7080e..d940fc9f8e 100644 --- a/packages/g6/tests/demo/layouts/force-wasm.ts +++ b/packages/g6/tests/demo/layouts/force-wasm.ts @@ -13,7 +13,6 @@ export default async () => { container, width, height, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'force-wasm', diff --git a/packages/g6/tests/demo/layouts/forceatlas2-wasm.ts b/packages/g6/tests/demo/layouts/forceatlas2-wasm.ts index 9e49d9b5d1..999aeca9aa 100644 --- a/packages/g6/tests/demo/layouts/forceatlas2-wasm.ts +++ b/packages/g6/tests/demo/layouts/forceatlas2-wasm.ts @@ -17,7 +17,6 @@ export default async () => { container, width, height, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'forceatlas2-wasm', diff --git a/packages/g6/tests/demo/layouts/fruchterman-gpu.ts b/packages/g6/tests/demo/layouts/fruchterman-gpu.ts index 96b9730614..643f458067 100644 --- a/packages/g6/tests/demo/layouts/fruchterman-gpu.ts +++ b/packages/g6/tests/demo/layouts/fruchterman-gpu.ts @@ -10,7 +10,6 @@ export default async () => { container, width, height, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'fruchterman-gpu', diff --git a/packages/g6/tests/demo/layouts/fruchterman-wasm.ts b/packages/g6/tests/demo/layouts/fruchterman-wasm.ts index 6e94adf373..8cecc62d14 100644 --- a/packages/g6/tests/demo/layouts/fruchterman-wasm.ts +++ b/packages/g6/tests/demo/layouts/fruchterman-wasm.ts @@ -17,7 +17,6 @@ export default async () => { container, width, height, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'fruchterman-wasm', diff --git a/packages/g6/tests/demo/performance/layout-3d.ts b/packages/g6/tests/demo/performance/layout-3d.ts index 7472559a11..59ee5ff303 100644 --- a/packages/g6/tests/demo/performance/layout-3d.ts +++ b/packages/g6/tests/demo/performance/layout-3d.ts @@ -77,7 +77,6 @@ export default async () => { container: $container1, width: WIDTH, height: HEIGHT, - type: 'graph', renderer: 'webgl-3d', modes: { default: [ @@ -144,7 +143,6 @@ export default async () => { container: $container2, width: WIDTH, height: HEIGHT, - type: 'graph', renderer: 'webgl-3d', modes: { default: [ diff --git a/packages/g6/tests/demo/performance/layout.ts b/packages/g6/tests/demo/performance/layout.ts index 5249211fa6..fb80ad7b6f 100644 --- a/packages/g6/tests/demo/performance/layout.ts +++ b/packages/g6/tests/demo/performance/layout.ts @@ -135,7 +135,6 @@ export default async () => { container: $container1, width: WIDTH, height: HEIGHT, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'force-wasm', @@ -171,7 +170,6 @@ export default async () => { container: $container2, width: WIDTH, height: HEIGHT, - type: 'graph', data: JSON.parse(JSON.stringify(data)), layout: { type: 'force', diff --git a/packages/g6/tests/demo/performance/performance.ts b/packages/g6/tests/demo/performance/performance.ts index afa276ce2c..b7f144a54e 100644 --- a/packages/g6/tests/demo/performance/performance.ts +++ b/packages/g6/tests/demo/performance/performance.ts @@ -1944,7 +1944,6 @@ const createGraph = async () => { container: container as HTMLElement, width, height: 1200, - type: 'graph', // renderer: 'webgl', data: { nodes, edges }, layout: { diff --git a/packages/g6/tests/demo/plugins/fisheye.ts b/packages/g6/tests/demo/plugins/fisheye.ts index 6d16f4e700..903c8afe18 100644 --- a/packages/g6/tests/demo/plugins/fisheye.ts +++ b/packages/g6/tests/demo/plugins/fisheye.ts @@ -139,7 +139,6 @@ export default async () => { container: 'container', width, height, - type: 'graph', layout: { type: 'force', rankdir: 'LR', diff --git a/packages/g6/tests/demo/tree/tree-graph.ts b/packages/g6/tests/demo/tree/tree-graph.ts new file mode 100644 index 0000000000..2d97c9dd39 --- /dev/null +++ b/packages/g6/tests/demo/tree/tree-graph.ts @@ -0,0 +1,266 @@ +import Stats from 'stats-js'; +import G6 from '../../../src/index'; +import { container, height, width } from '../../datasets/const'; +import { CanvasEvent } from '@antv/g'; + +const treeDataCfg = { + type: 'treeData', + value: [ + { + id: 'root', + data: { + // collapsed: true, + }, + children: [ + { + id: 'c1', + data: {}, + children: [ + { + id: 'c1-c1', + data: {}, + }, + ], + }, + { + id: 'c2', + data: {}, + }, + ], + }, + { + id: 'root2', + data: {}, + children: [ + { + id: 't2c1', + data: {}, + children: [ + { + id: 't2c1-c1', + data: {}, + }, + ], + }, + { + id: 't2c2', + data: {}, + }, + ], + }, + ], +}; + +const graphDataCfg = { + type: 'graphData', + value: { + nodes: [ + { id: 'node1', data: { isRoot: true, collapsed: true } }, + { id: 'node2', data: {} }, + { id: 'node3', data: {} }, + { id: 'node4', data: {} }, + { id: 'node5', data: {} }, + ], + edges: [ + { id: 'edge1', source: 'node1', target: 'node2', data: {} }, + { id: 'edge2', source: 'node1', target: 'node3', data: {} }, + { id: 'edge3', source: 'node1', target: 'node4', data: {} }, + // { id: 'edge4', source: 'node2', target: 'node3', data: {} }, + // { id: 'edge5', source: 'node3', target: 'node4', data: {} }, + { id: 'edge6', source: 'node4', target: 'node5', data: {} }, + ], + }, +}; + +const dataGenerator = (nodeNum, edgeNum) => { + const nodes: any = []; + const edges: any = []; + for (let i = 0; i < nodeNum; i++) { + nodes.push({ + id: `node-${i}`, + data: { + x: Math.random() * 1000, + y: Math.random() * 1000, + }, + }); + } + for (let i = 0; i < edgeNum; i++) { + edges.push({ + id: `edge-${i}`, + source: nodes[Math.floor(Math.random() * nodeNum)].id, + target: nodes[Math.floor(Math.random() * nodeNum)].id, + data: {}, + }); + } + return { nodes, edges }; +}; + +export default async () => { + // const data = dataGenerator(90000, 10000); + // console.log('data', data); + const graph = new G6.Graph({ + container, + width, + height, + layout: { + type: 'compactBox', + // type: 'grid', + }, + node: (innerModel) => { + const { x, y, labelShape } = innerModel.data; + return { + ...innerModel, + data: { + x, + y, + animates: { + update: [ + { + fields: ['x', 'y'], + duration: 500, + shapeId: 'group', + order: 0, + }, + ], + hide: [ + { + fields: ['opacity'], + duration: 200, + shapeId: 'keyShape', + }, + { + fields: ['opacity'], + duration: 200, + shapeId: 'labelShape', + }, + ], + show: [ + { + fields: ['opacity'], + duration: 1000, + shapeId: 'keyShape', + }, + { + fields: ['opacity'], + duration: 1000, + shapeId: 'labelShape', + }, + ], + }, + // animate in shapes, unrelated to each other, excuted parallely + labelShape: { + text: innerModel.id, + ...labelShape, + }, + }, + }; + }, + edge: (innerModel) => { + return { + ...innerModel, + data: { + ...innerModel.data, + animates: { + // hide: [ + // { + // fields: ['opacity'], + // duration: 200, + // shapeId: 'keyShape', + // }, + // { + // fields: ['opacity'], + // duration: 200, + // shapeId: 'labelShape', + // }, + // ], + // show: [ + // { + // fields: ['opacity'], + // duration: 1000, + // shapeId: 'keyShape', + // }, + // { + // fields: ['opacity'], + // duration: 1000, + // shapeId: 'labelShape', + // }, + // ], + }, + }, + }; + }, + // node: { + // animates: { + // update: [ + // { + // fields: ['x', 'y'], + // duration: 2000, + // shapeId: 'group', + // }, + // ], + // }, + // labelShape: { + // text: { + // fields: ['id'], + // formatter: (model) => model.id, + // }, + // }, + // }, + data: treeDataCfg, + // data: graphDataCfg, + modes: { + default: [ + 'drag-canvas', + 'zoom-canvas', + { + type: 'collapse-expand-tree', + trigger: 'click', + }, + ], + }, + }); + + const stats = new Stats(); + stats.showPanel(0); + document.body.appendChild(stats.dom); + + graph.canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => { + if (stats) { + stats.update(); + } + }); + + let collapsed = true; + graph.translateTo({ x: 100, y: 100 }); + + graph.on('canvas:click', (e) => { + /** === change graph data / tree data === */ + // graph.changeData(treeDataCfg); + /** === change layout === */ + // graph.layout({ type: 'compactBox' }); + /** === collapse / expand === */ + const ids = ['root2', 'root']; //['root']; //['node1']; // + collapsed ? graph.expand(ids) : graph.collapse(ids); + collapsed = !collapsed; + }); + + graph.on('canvas:contextmenu', (e) => { + console.log('contextmenu'); + /** === updateData === */ + // graph.updateData('node', { + // id: 'node2', + // data: { labelShape: { text: 'updated!!' } }, + // }); + + /** === remove a child / subtree root === */ + // graph.removeData('node', ['node4']); + + /** === add a child === */ + // graph.addData('node', [{ id: 'newnode', data: {} }]); + // graph.addData('edge', [ + // { id: 'newedge', source: 'node1', target: 'newnode', data: {} }, + // ]); + }); + + return graph; +}; diff --git a/packages/g6/tests/demo/visual/visual.ts b/packages/g6/tests/demo/visual/visual.ts index c3c4f0eb7f..9756bb97a3 100644 --- a/packages/g6/tests/demo/visual/visual.ts +++ b/packages/g6/tests/demo/visual/visual.ts @@ -10,7 +10,6 @@ const createGraph = () => { container: container as HTMLElement, width, height: 1200, - type: 'graph', // renderer: 'webgl', data: { nodes: [ diff --git a/packages/g6/tests/intergration/layouts/circular.ts b/packages/g6/tests/intergration/layouts/circular.ts new file mode 100644 index 0000000000..7edeb8bb45 --- /dev/null +++ b/packages/g6/tests/intergration/layouts/circular.ts @@ -0,0 +1,16 @@ +import G6 from '../../../src/index'; +import { container, data, height, width } from '../../datasets/const'; + +export default () => { + return new G6.Graph({ + container, + width, + height, + data, + layout: { + type: 'circular', + center: [250, 250], + radius: 200, + }, + }); +}; diff --git a/packages/g6/tests/intergration/layouts/force-3d.ts b/packages/g6/tests/intergration/layouts/force-3d.ts new file mode 100644 index 0000000000..265ec3d0aa --- /dev/null +++ b/packages/g6/tests/intergration/layouts/force-3d.ts @@ -0,0 +1,56 @@ +import G6 from '../../../src/index'; +import { container, data, height, width } from '../../datasets/const'; + +export default () => { + return new G6.Graph({ + container, + width, + height, + renderer: 'webgl-3d', + modes: { + default: [ + { + type: 'orbit-canvas-3d', + trigger: 'drag', + }, + 'zoom-canvas-3d', + ], + }, + data: JSON.parse(JSON.stringify(data)), + layout: { + type: 'force', + dimensions: 3, + iterations: 100, + center: [width / 2, height / 2, 0], + }, + edge: { + type: 'line-edge', + keyShape: { + lineWidth: 2, + stroke: 'grey', + }, + }, + node: { + type: 'sphere-node', + keyShape: { + opacity: 0.6, + }, + // labelShape: { + // text: 'node-label', + // }, + // iconShape: { + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + // }, + }, + nodeState: { + selected: { + keyShape: { + fill: '#f00', + }, + labelShape: { + fontSize: 20, + }, + }, + }, + }); +}; diff --git a/packages/g6/tests/unit/behavior-spec.ts b/packages/g6/tests/unit/behavior-spec.ts index 84afb3ba70..ac1ec54943 100644 --- a/packages/g6/tests/unit/behavior-spec.ts +++ b/packages/g6/tests/unit/behavior-spec.ts @@ -8,7 +8,6 @@ describe('behavior', () => { it('behavior in spec, add / remove / update a behavior in defualt mode', () => { const graph = new G6.Graph({ container, - type: 'graph', data: { nodes: [], edges: [] }, modes: { default: [ @@ -116,7 +115,6 @@ describe('behavior', () => { }); const graph = new CustomGraph({ container, - type: 'graph', data: { nodes: [], edges: [] }, modes: { default: [ diff --git a/packages/g6/tests/unit/behaviors/brush-select-spec.ts b/packages/g6/tests/unit/behaviors/brush-select-spec.ts index a9cde47758..5959f6d274 100644 --- a/packages/g6/tests/unit/behaviors/brush-select-spec.ts +++ b/packages/g6/tests/unit/behaviors/brush-select-spec.ts @@ -9,7 +9,6 @@ describe('brush-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -142,7 +141,6 @@ describe('brush-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -253,7 +251,6 @@ describe('brush-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -368,7 +365,6 @@ describe('brush-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -527,7 +523,6 @@ describe('brush-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -660,7 +655,6 @@ describe('brush-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -746,7 +740,6 @@ describe('brush-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -836,7 +829,6 @@ describe('brush-select behavior with trigger', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -937,7 +929,6 @@ describe('brush-select behavior with trigger', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -1074,7 +1065,6 @@ describe('brush-select behavior with shouldBegin and shouldUpdate', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -1210,7 +1200,6 @@ describe('brush-select behavior with brushStyle', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/unit/behaviors/drag-canvas-spec.ts b/packages/g6/tests/unit/behaviors/drag-canvas-spec.ts index 607a869bd4..dd8461931b 100644 --- a/packages/g6/tests/unit/behaviors/drag-canvas-spec.ts +++ b/packages/g6/tests/unit/behaviors/drag-canvas-spec.ts @@ -8,7 +8,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -105,7 +104,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -172,7 +170,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -232,7 +229,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -283,7 +279,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -355,7 +350,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -426,7 +420,6 @@ describe('drag-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/unit/behaviors/lasso-select-spec.ts b/packages/g6/tests/unit/behaviors/lasso-select-spec.ts index 9169551d39..263be6ff9b 100644 --- a/packages/g6/tests/unit/behaviors/lasso-select-spec.ts +++ b/packages/g6/tests/unit/behaviors/lasso-select-spec.ts @@ -9,7 +9,6 @@ describe('lasso-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -158,7 +157,6 @@ describe('lasso-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -277,7 +275,6 @@ describe('lasso-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -400,7 +397,6 @@ describe('lasso-select behavior with selectSetMode', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -579,7 +575,6 @@ describe('lasso-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -724,7 +719,6 @@ describe('lasso-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -818,7 +812,6 @@ describe('lasso-select behavior with itemTypes', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -912,7 +905,6 @@ describe('lasso-select behavior with trigger', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -1017,7 +1009,6 @@ describe('lasso-select behavior with trigger', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -1170,7 +1161,6 @@ describe('lasso-select behavior with shouldBegin and shouldUpdate', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -1303,7 +1293,6 @@ describe('lasso-select behavior with brushStyle', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/unit/behaviors/zoom-canvas-spec.ts b/packages/g6/tests/unit/behaviors/zoom-canvas-spec.ts index abdc81a15f..bcf45303f5 100644 --- a/packages/g6/tests/unit/behaviors/zoom-canvas-spec.ts +++ b/packages/g6/tests/unit/behaviors/zoom-canvas-spec.ts @@ -8,7 +8,6 @@ describe('zoom-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -93,7 +92,6 @@ describe('zoom-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -149,7 +147,6 @@ describe('zoom-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, @@ -226,7 +223,6 @@ describe('zoom-canvas behavior', () => { container, width: 500, height: 500, - type: 'graph', layout: { type: 'grid', }, diff --git a/packages/g6/tests/unit/click-select-spec.ts b/packages/g6/tests/unit/click-select-spec.ts index 13d36e7623..30aea7e92c 100644 --- a/packages/g6/tests/unit/click-select-spec.ts +++ b/packages/g6/tests/unit/click-select-spec.ts @@ -10,7 +10,6 @@ describe('click-select', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200, keyShape: { fill: '#0f0' } } }, diff --git a/packages/g6/tests/unit/data-spec.ts b/packages/g6/tests/unit/data-spec.ts index 35d3c3c63b..a4ff4abe9b 100644 --- a/packages/g6/tests/unit/data-spec.ts +++ b/packages/g6/tests/unit/data-spec.ts @@ -24,7 +24,6 @@ describe('data', () => { container, width: 500, height: 500, - type: 'graph', data, // with data, graph will be rendered in constructor }); graph.on('afterrender', () => { diff --git a/packages/g6/tests/unit/drag-node-spec.ts b/packages/g6/tests/unit/drag-node-spec.ts index d04613682b..8fca46cb67 100644 --- a/packages/g6/tests/unit/drag-node-spec.ts +++ b/packages/g6/tests/unit/drag-node-spec.ts @@ -11,7 +11,6 @@ const createGraph = (dragNodeOptions: DragNodeOptions): IGraph => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200, keyShape: { fill: '#0f0' } } }, diff --git a/packages/g6/tests/unit/edge-spec.ts b/packages/g6/tests/unit/edge-spec.ts index 7dbdae8809..db01d91da3 100644 --- a/packages/g6/tests/unit/edge-spec.ts +++ b/packages/g6/tests/unit/edge-spec.ts @@ -25,7 +25,6 @@ describe('edge item', () => { container, width: 500, height: 500, - type: 'graph', modes: { default: ['drag-node'], }, @@ -265,7 +264,6 @@ describe('edge mapper', () => { container, width: 500, height: 500, - type: 'graph', }; it('function mapper', (done) => { const graph = new G6.Graph({ @@ -373,7 +371,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -507,7 +504,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -769,7 +765,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -897,7 +892,6 @@ describe('cubic-edge unit test', () => { container, width, height, - type: 'graph', data: defaultData, modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], @@ -1137,7 +1131,6 @@ describe('cubic-edge unit test', () => { container, width: 500, height: 500, - type: 'graph', data: stateData, modes: { // Supported behavior @@ -1272,7 +1265,6 @@ describe('cubic-horizon-edge unit test', () => { container, width, height, - type: 'graph', data: defaultData, modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], @@ -1415,7 +1407,6 @@ describe('cubic-horizon-edge unit test', () => { container, width, height, - type: 'graph', data: defaultData, modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], @@ -1654,7 +1645,6 @@ describe('cubic-horizon-edge unit test', () => { container, width: 500, height: 500, - type: 'graph', data: stateData, modes: { default: ['activate-relations'], @@ -1788,7 +1778,6 @@ describe('cubic-vertical-edge unit test', () => { container, width, height, - type: 'graph', data: defaultData, modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], @@ -2027,7 +2016,6 @@ describe('cubic-vertical-edge unit test', () => { container, width: 500, height: 500, - type: 'graph', data: stateData, modes: { default: ['activate-relations'], @@ -2161,7 +2149,6 @@ describe('cubic-vertical-edge unit test', () => { container, width, height, - type: 'graph', data: defaultData, modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], @@ -2400,7 +2387,6 @@ describe('cubic-vertical-edge unit test', () => { container, width: 500, height: 500, - type: 'graph', data: stateData, modes: { default: ['activate-relations'], diff --git a/packages/g6/tests/unit/item-3d-spec.ts b/packages/g6/tests/unit/item-3d-spec.ts index 5ca0e68f05..5e2cc5a584 100644 --- a/packages/g6/tests/unit/item-3d-spec.ts +++ b/packages/g6/tests/unit/item-3d-spec.ts @@ -31,7 +31,6 @@ describe('node item', () => { container, width: 500, height: 500, - type: 'graph', renderer: 'webgl-3d', // renderer: 'canvas', modes: { diff --git a/packages/g6/tests/unit/item-animate-spec.ts b/packages/g6/tests/unit/item-animate-spec.ts index 493fca8037..8cc87abba5 100644 --- a/packages/g6/tests/unit/item-animate-spec.ts +++ b/packages/g6/tests/unit/item-animate-spec.ts @@ -124,7 +124,6 @@ const createGraph = (props) => { container, width: 500, height: 500, - type: 'graph', data: clonedData, ...props, }); diff --git a/packages/g6/tests/unit/layout-spec.ts b/packages/g6/tests/unit/layout-spec.ts index c879cf7f1e..f20a101ddf 100644 --- a/packages/g6/tests/unit/layout-spec.ts +++ b/packages/g6/tests/unit/layout-spec.ts @@ -12,7 +12,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, }); @@ -35,7 +34,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -123,7 +121,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -147,7 +144,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -185,7 +181,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -224,7 +219,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -262,7 +256,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'd3force', @@ -289,7 +282,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -342,7 +334,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -388,7 +379,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'd3force', @@ -417,7 +407,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'd3force', @@ -470,7 +459,6 @@ describe('layout', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'myCustomLayout', diff --git a/packages/g6/tests/unit/node-spec.ts b/packages/g6/tests/unit/node-spec.ts index b4486dd8ae..8da1cd4246 100644 --- a/packages/g6/tests/unit/node-spec.ts +++ b/packages/g6/tests/unit/node-spec.ts @@ -26,7 +26,6 @@ describe('node item', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -138,7 +137,6 @@ describe('node mapper', () => { container, width: 500, height: 500, - type: 'graph', }; it('function mapper', (done) => { const graph = new G6.Graph({ @@ -323,7 +321,6 @@ describe('register node', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -465,7 +462,6 @@ describe('register node', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -533,7 +529,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -677,7 +672,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -920,7 +914,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { diff --git a/packages/g6/tests/unit/plugins/grid-spec.ts b/packages/g6/tests/unit/plugins/grid-spec.ts index 174f3e6e86..b48bdf2f9f 100644 --- a/packages/g6/tests/unit/plugins/grid-spec.ts +++ b/packages/g6/tests/unit/plugins/grid-spec.ts @@ -10,7 +10,6 @@ const createGraph = (plugins) => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200, nodeType: 'a' } }, @@ -65,9 +64,9 @@ const createGraph = (plugins) => { describe('grid plugin', () => { test('grid with string config', () => { const graph = createGraph([ - { - img: 'url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2UwZTBlMCIgb3BhY2l0eT0iMC4yIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNIDQwIDAgTCAwIDAgMCA0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48L3N2Zz4=)' - } + { + img: 'url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2UwZTBlMCIgb3BhY2l0eT0iMC4yIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNIDQwIDAgTCAwIDAgMCA0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48L3N2Zz4=)', + }, ]); }); -}); \ No newline at end of file +}); diff --git a/packages/g6/tests/unit/plugins/legend-spec.ts b/packages/g6/tests/unit/plugins/legend-spec.ts index ea79b6dec2..845fbf5168 100644 --- a/packages/g6/tests/unit/plugins/legend-spec.ts +++ b/packages/g6/tests/unit/plugins/legend-spec.ts @@ -10,7 +10,6 @@ const createGraph = (plugins) => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200, nodeType: 'a' } }, diff --git a/packages/g6/tests/unit/plugins/menu-spec.ts b/packages/g6/tests/unit/plugins/menu-spec.ts index 52cbadfa9d..a8f0ac1335 100644 --- a/packages/g6/tests/unit/plugins/menu-spec.ts +++ b/packages/g6/tests/unit/plugins/menu-spec.ts @@ -9,7 +9,6 @@ const createGraph = (plugins) => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200 } }, diff --git a/packages/g6/tests/unit/plugins/minimap-spec.ts b/packages/g6/tests/unit/plugins/minimap-spec.ts index d3b1ef24fd..62052a61e4 100644 --- a/packages/g6/tests/unit/plugins/minimap-spec.ts +++ b/packages/g6/tests/unit/plugins/minimap-spec.ts @@ -9,7 +9,6 @@ const createGraph = (plugins) => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200 } }, diff --git a/packages/g6/tests/unit/plugins/tooltip-spec.ts b/packages/g6/tests/unit/plugins/tooltip-spec.ts index f08bd8424a..388a9517fa 100644 --- a/packages/g6/tests/unit/plugins/tooltip-spec.ts +++ b/packages/g6/tests/unit/plugins/tooltip-spec.ts @@ -9,7 +9,6 @@ const createGraph = (plugins) => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { id: 'node1', data: { x: 100, y: 200 } }, diff --git a/packages/g6/tests/unit/quadratic-spec.ts b/packages/g6/tests/unit/quadratic-spec.ts index d0c570f0f4..7e4a696ad9 100644 --- a/packages/g6/tests/unit/quadratic-spec.ts +++ b/packages/g6/tests/unit/quadratic-spec.ts @@ -82,7 +82,6 @@ describe('edge item', () => { width, height, data, - type: 'graph', modes: { default: ['click-select', 'drag-canvas', 'zoom-canvas', 'drag-node'], }, @@ -282,7 +281,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -416,7 +414,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -550,7 +547,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -684,7 +680,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { @@ -818,7 +813,6 @@ describe('state', () => { container, width: 500, height: 500, - type: 'graph', data: { nodes: [ { diff --git a/packages/g6/tests/unit/rect-spec.ts b/packages/g6/tests/unit/rect-spec.ts index fbee5ca28e..55c922da20 100644 --- a/packages/g6/tests/unit/rect-spec.ts +++ b/packages/g6/tests/unit/rect-spec.ts @@ -8,7 +8,6 @@ describe('behavior', () => { it('behavior in spec, add / remove / update a behavior in defualt mode', () => { const graph = new G6.Graph({ container, - type: 'graph', data: { nodes: [], edges: [] }, modes: { default: ['drag-canvas', 'click-select', 'drag-canvas', 'zoom-canvas'], diff --git a/packages/g6/tests/unit/show-animate-spec.ts b/packages/g6/tests/unit/show-animate-spec.ts index a543022204..03e660f988 100644 --- a/packages/g6/tests/unit/show-animate-spec.ts +++ b/packages/g6/tests/unit/show-animate-spec.ts @@ -1887,7 +1887,6 @@ const createGraph = (props) => { // renderer: 'webgl', width: 500, height: 500, - type: 'graph', data: clonedData, // data: { // nodes: [clonedData.nodes[0], clonedData.nodes[10]], diff --git a/packages/g6/tests/unit/theme-spec.ts b/packages/g6/tests/unit/theme-spec.ts index efac229cca..3cd517a1b1 100644 --- a/packages/g6/tests/unit/theme-spec.ts +++ b/packages/g6/tests/unit/theme-spec.ts @@ -88,7 +88,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'grid', @@ -203,7 +202,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data: clone(data), layout: { type: 'grid', @@ -430,7 +428,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data: clone(data), layout: { type: 'grid', @@ -712,7 +709,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'grid', diff --git a/packages/g6/tests/unit/theme-subject-spec.ts b/packages/g6/tests/unit/theme-subject-spec.ts index f1f35b63a7..7b7e32245d 100644 --- a/packages/g6/tests/unit/theme-subject-spec.ts +++ b/packages/g6/tests/unit/theme-subject-spec.ts @@ -27,7 +27,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'grid', @@ -126,7 +125,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'grid', @@ -255,7 +253,6 @@ describe('theme', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'grid', diff --git a/packages/g6/tests/unit/view-spec.ts b/packages/g6/tests/unit/view-spec.ts index 6a394a9480..1787e1bbe6 100644 --- a/packages/g6/tests/unit/view-spec.ts +++ b/packages/g6/tests/unit/view-spec.ts @@ -12,7 +12,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -42,7 +41,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -85,7 +83,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -131,7 +128,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -208,7 +204,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -259,7 +254,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -385,7 +379,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -422,7 +415,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -473,7 +465,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular', @@ -542,7 +533,6 @@ describe('viewport', () => { container, width: 500, height: 500, - type: 'graph', data, layout: { type: 'circular',