From 3c73d17cd23723671550070d91ef745df76445f6 Mon Sep 17 00:00:00 2001 From: Yanyan Wang Date: Tue, 8 Nov 2022 11:14:22 +0800 Subject: [PATCH] perf: improve the performance of setItemState and active-relations; perf: keyShape is hiden when a combo is collapsed with collapsedSubstituIcon; fix: drag-node incorrectly stopped by right click; fix: timebar plugin destroy problem, closes: #3998; fix: controllerCfg does not take effect in timebar with tick type, closes: #3843; feat: timebar plugin supports config the default time type; feat: timebar with play and pause API; chore: use addItem and removeItem instead of changeData in timebar; (#4033) --- CHANGELOG.md | 11 + packages/core/package.json | 2 +- packages/core/src/element/combo.ts | 7 +- packages/core/src/global.ts | 2 +- packages/core/src/graph/controller/state.ts | 163 ++---------- packages/core/src/graph/graph.ts | 8 +- .../tests/unit/graph/controller/state-spec.ts | 2 +- packages/element/package.json | 4 +- packages/g6/package.json | 4 +- packages/g6/src/index.ts | 4 +- packages/pc/package.json | 8 +- .../pc/src/behavior/activate-relations.ts | 241 ++++++++++-------- packages/pc/src/behavior/drag-node.ts | 88 +++++-- packages/pc/src/global.ts | 2 +- .../unit/behavior/active-relations-spec.ts | 51 ++-- .../pc/tests/unit/behavior/drag-node-spec.ts | 112 ++++---- .../tests/unit/combo-collapse-layout-spec.ts | 2 +- packages/pc/tests/unit/graph/graph-spec.ts | 7 +- packages/pc/tests/unit/graph/svg-spec.ts | 23 +- packages/pc/tests/unit/layout/dagre-spec.ts | 80 ++++++ packages/plugin/package.json | 8 +- packages/plugin/src/timeBar/controllerBtn.ts | 23 +- packages/plugin/src/timeBar/index.ts | 158 ++++++++---- packages/plugin/src/timeBar/timeBarSlice.ts | 44 ++-- packages/plugin/src/timeBar/trendTimeBar.ts | 20 +- packages/plugin/tests/unit/timebar-spec.ts | 83 +++--- .../src/Register/getDataFromReactNode.ts | 2 +- packages/site/docs/api/Plugins.en.md | 16 +- packages/site/docs/api/Plugins.zh.md | 17 +- packages/site/gatsby-browser.js | 6 +- 30 files changed, 680 insertions(+), 518 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 709e1db27cc..f97befd0749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # ChangeLog +### 4.7.11 + +- perf: improve the performance of setItemState and active-relations; +- perf: keyShape is hiden when a combo is collapsed with collapsedSubstituIcon; +- fix: drag-node incorrectly stopped by right click; +- fix: timebar plugin destroy problem, closes: #3998; +- fix: controllerCfg does not take effect in timebar with tick type, closes: #3843; +- feat: timebar plugin supports config the default time type; +- feat: timebar with play and pause API; +- chore: use addItem and removeItem instead of changeData in timebar; + ### 4.7.10 - perf: force layout with animation calls graph refreshPositions instead positionsAnimate while refreshing; diff --git a/packages/core/package.json b/packages/core/package.json index 6b6146cab4c..808167ccca9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-core", - "version": "0.7.10", + "version": "0.7.11", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", diff --git a/packages/core/src/element/combo.ts b/packages/core/src/element/combo.ts index 8efabdcafcc..b6b6da93019 100644 --- a/packages/core/src/element/combo.ts +++ b/packages/core/src/element/combo.ts @@ -149,8 +149,9 @@ const singleCombo: ShapeOptions = { const subsitututeIconConfig = Object.assign({}, this.options.collapsedSubstituteIcon, collapsedSubstituteIcon) const { show, img, width, height } = subsitututeIconConfig; const group = item.getContainer(); - const collapsedIconShape = group.find(ele => ele.get('name') === 'combo-collapsed-substitute-icon'); + let collapsedIconShape = group.find(ele => ele.get('name') === 'combo-collapsed-substitute-icon'); const iconShapeExist = collapsedIconShape && !collapsedIconShape.destroyed; + const keyShape = item.get('keyShape'); if (collapsed && show) { if (iconShapeExist) { collapsedIconShape.show(); @@ -159,7 +160,7 @@ const singleCombo: ShapeOptions = { width: width || keyShapeStyle.r * 2 || keyShapeStyle.width, height: height || keyShapeStyle.r * 2 || keyShapeStyle.height, } - group.addShape('image', { + collapsedIconShape = group.addShape('image', { attrs: { img, x: -sizeAttr.width / 2, @@ -170,8 +171,10 @@ const singleCombo: ShapeOptions = { draggable: true }); } + keyShape.hide(); } else if (iconShapeExist) { collapsedIconShape.hide(); + keyShape.show(); } }, updateShape(cfg: ComboConfig, item: Item, keyShapeStyle: ShapeStyle) { diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index a5aac151c8d..a36de84b69f 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -64,7 +64,7 @@ const colorSet = { }; export default { - version: '0.7.10', + version: '0.7.11', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/packages/core/src/graph/controller/state.ts b/packages/core/src/graph/controller/state.ts index e63ba1f3842..3d6e0214953 100644 --- a/packages/core/src/graph/controller/state.ts +++ b/packages/core/src/graph/controller/state.ts @@ -1,75 +1,17 @@ -import { each, isString } from '@antv/util'; -import { Item, States } from '../../types'; +import { isString } from '@antv/util'; +import { Item } from '../../types'; import { IAbstractGraph } from '../../interface/graph'; -import { INode } from '../../interface/item'; - -interface CachedStates { - enabled: States; - disabled: States; -} - -let timer: any = null; export default class StateController { private graph: IAbstractGraph; - private cachedStates: CachedStates; - public destroyed: boolean; constructor(graph: IAbstractGraph) { this.graph = graph; - /** - * this.cachedStates = { - * enabled: { - * hover: [Node] - * }, - * disabled: {} - * } - */ - this.cachedStates = { - enabled: {}, - disabled: {}, - }; this.destroyed = false; } - /** - * 检查 cache 的可用性 - * - * @private - * @param {Item} item - * @param {string} state - * @param {object} cache - * @returns - * @memberof State - */ - private static checkCache(item: Item, state: string, cache: { [key: string]: any }) { - if (!cache[state]) { - return; - } - const index = cache[state].indexOf(item); - if (index >= 0) { - cache[state].splice(index, 1); - } - } - - /** - * 缓存 state - * - * @private - * @param {Item} item Item 实例 - * @param {string} state 状态名称 - * @param {object} states - * @memberof State - */ - private static cacheState(item: Item, state: string, states: States) { - if (!states[state]) { - states[state] = []; - } - states[state].push(item as INode); - } - /** * 更新 Item 的状态 * @@ -78,33 +20,15 @@ export default class StateController { * @param {boolean} enabled 状态是否可用 * @memberof State */ - public updateState(item: Item, state: string, enabled: boolean) { - const { checkCache, cacheState } = StateController; - if (item.destroyed) { - return; - } - - const { cachedStates } = this; - - const enabledStates = cachedStates.enabled; - const disabledStates = cachedStates.disabled; - - if (enabled) { - checkCache(item, state, disabledStates); - cacheState(item, state, enabledStates); - } else { - checkCache(item, state, enabledStates); - cacheState(item, state, disabledStates); - } - - if (timer) { - clearTimeout(timer); - } - - timer = setTimeout(() => { - timer = null; - this.updateGraphStates(); - }, 16); + public updateState(item: Item, state: string, enabled: string | boolean) { + const graphStates = this.graph.get('states'); + let key = state; + if (isString(enabled)) key = `${state}:${enabled}`; + if (!graphStates[key]) graphStates[key] = []; + if (enabled) graphStates[key].push(item); + else graphStates[key] = graphStates[key].filter((itemInState) => itemInState !== item); + this.graph.set('states', graphStates); + this.graph.emit('graphstatechange', { states: graphStates }); } /** @@ -115,67 +39,22 @@ export default class StateController { * @param {boolean} enabled * @memberof State */ - public updateStates(item: Item, states: string | string[], enabled: boolean) { - if (isString(states)) { - this.updateState(item, states, enabled); - } else { - states.forEach((state) => { - this.updateState(item, state, enabled); - }); - } - } - - /** - * 更新 states - * - * @memberof State - */ - public updateGraphStates() { - const states = this.graph.get('states') as States; - const { cachedStates } = this; - - each(cachedStates.disabled, (val, key) => { - if (states[key]) { - states[key] = states[key].filter((item) => val.indexOf(item) < 0 && !val.destroyed); - } - }); - - each(cachedStates.enabled, (val: INode[], key) => { - if (!states[key]) { - states[key] = val; - } else { - const map: { [key: string]: boolean } = {}; - states[key].forEach((item) => { - if (!item.destroyed) { - map[item.get('id')] = true; - } - }); - val.forEach((item: Item) => { - if (!item.destroyed) { - const id = item.get('id'); - if (!map[id]) { - map[id] = true; - states[key].push(item as INode); - } - } - }); - } + public updateStates(item: Item, states: string | string[], enabled: string | boolean) { + const graphStates = this.graph.get('states'); + const stateNames = isString(states) ? [states] : states; + stateNames.forEach(stateName => { + let key = stateName; + if (!graphStates[key]) graphStates[key] = []; + if (isString(enabled)) key = `${stateName}:${enabled}`; + if (enabled) graphStates[key].push(item); + else graphStates[key] = graphStates[key].filter((itemInState) => itemInState !== item); }); - + this.graph.set('states', graphStates); this.graph.emit('graphstatechange', { states }); - this.cachedStates = { - enabled: {}, - disabled: {}, - }; } public destroy() { (this.graph as IAbstractGraph | null) = null; - (this.cachedStates as CachedStates | null) = null; - if (timer) { - clearTimeout(timer); - } - timer = null; this.destroyed = true; } } diff --git a/packages/core/src/graph/graph.ts b/packages/core/src/graph/graph.ts index 664456e622b..c8db2128de8 100644 --- a/packages/core/src/graph/graph.ts +++ b/packages/core/src/graph/graph.ts @@ -1418,12 +1418,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs itemController.setItemState(item, state, value); const stateController: StateController = this.get('stateController'); - - if (isString(value)) { - stateController.updateState(item, `${state}:${value}`, true); - } else { - stateController.updateState(item, state, value); - } + stateController.updateState(item, state, value); } /** @@ -1731,6 +1726,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs setTimeout(() => { canvas.set('localRefresh', localRefresh); }, 16); + this.set('data', data); this.emit('afterchangedata'); return this; } diff --git a/packages/core/tests/unit/graph/controller/state-spec.ts b/packages/core/tests/unit/graph/controller/state-spec.ts index 85e52e56732..65cd25e3d93 100644 --- a/packages/core/tests/unit/graph/controller/state-spec.ts +++ b/packages/core/tests/unit/graph/controller/state-spec.ts @@ -48,7 +48,7 @@ describe('graph state controller', () => { graph.setItemState('node2', 'selected', true); expect(itemCount).toBe(2); setTimeout(() => { - expect(graphCount).toBe(1); + expect(graphCount).toBe(2); done(); }, 100); }); diff --git a/packages/element/package.json b/packages/element/package.json index 975321c87c1..b381a1c25fe 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-element", - "version": "0.7.10", + "version": "0.7.11", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", @@ -61,7 +61,7 @@ }, "dependencies": { "@antv/g-base": "^0.5.1", - "@antv/g6-core": "0.7.10", + "@antv/g6-core": "0.7.11", "@antv/util": "~2.0.5" }, "devDependencies": { diff --git a/packages/g6/package.json b/packages/g6/package.json index 1658649d388..ebbc0acdfc9 100644 --- a/packages/g6/package.json +++ b/packages/g6/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6", - "version": "4.7.10", + "version": "4.7.11", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", @@ -66,7 +66,7 @@ ] }, "dependencies": { - "@antv/g6-pc": "0.7.10" + "@antv/g6-pc": "0.7.11" }, "devDependencies": { "@babel/core": "^7.7.7", diff --git a/packages/g6/src/index.ts b/packages/g6/src/index.ts index 561c8f77d2d..fba69104836 100644 --- a/packages/g6/src/index.ts +++ b/packages/g6/src/index.ts @@ -1,7 +1,7 @@ import G6 from '@antv/g6-pc'; -G6.version = '4.7.10'; +G6.version = '4.7.11'; export * from '@antv/g6-pc'; export default G6; -export const version = '4.7.10'; +export const version = '4.7.11'; diff --git a/packages/pc/package.json b/packages/pc/package.json index 4a8ccce34c3..d74e69f0fe5 100644 --- a/packages/pc/package.json +++ b/packages/pc/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-pc", - "version": "0.7.10", + "version": "0.7.11", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", @@ -75,9 +75,9 @@ "@antv/g-canvas": "^0.5.2", "@antv/g-math": "^0.1.1", "@antv/g-svg": "^0.5.1", - "@antv/g6-core": "0.7.10", - "@antv/g6-element": "0.7.10", - "@antv/g6-plugin": "0.7.10", + "@antv/g6-core": "0.7.11", + "@antv/g6-element": "0.7.11", + "@antv/g6-plugin": "0.7.11", "@antv/hierarchy": "^0.6.7", "@antv/layout": "^0.3.0", "@antv/matrix-util": "^3.1.0-beta.3", diff --git a/packages/pc/src/behavior/activate-relations.ts b/packages/pc/src/behavior/activate-relations.ts index 8a3622b23fa..e31595af917 100644 --- a/packages/pc/src/behavior/activate-relations.ts +++ b/packages/pc/src/behavior/activate-relations.ts @@ -1,4 +1,5 @@ import { G6Event, IG6GraphEvent, INode } from '@antv/g6-core'; +import { throttle } from '@antv/util'; export default { getDefaultCfg(): object { @@ -67,134 +68,150 @@ export default { self.clearActiveState(e); }, setAllItemStates(e: IG6GraphEvent) { - const item: INode = e.item as INode; - const graph = this.graph; - this.item = item; - if (!this.shouldUpdate(e.item, { event: e, action: 'activate' })) { - return; - } - const self = this; - const activeState = this.activeState; - const inactiveState = this.inactiveState; - const nodes = graph.getNodes(); - const combos = graph.getCombos(); - const edges = graph.getEdges(); - const vEdges = graph.get('vedges'); - const nodeLength = nodes.length; - const comboLength = combos.length; - const edgeLength = edges.length; - const vEdgeLength = vEdges.length; + this.throttleSetAllItemStates(e, this); + }, + clearActiveState(e: any) { + this.throttleClearActiveState(e, this); + }, + throttleSetAllItemStates: throttle( + (e, self) => { + const item: INode = e.item as INode; + const graph = self.graph; + if (!graph || graph.destroyed) return; + self.item = item; + if (!self.shouldUpdate(e.item, { event: e, action: 'activate' })) { + return; + } + const activeState = self.activeState; + const inactiveState = self.inactiveState; + const nodes = graph.getNodes(); + const combos = graph.getCombos(); + const edges = graph.getEdges(); + const vEdges = graph.get('vedges'); + const nodeLength = nodes.length; + const comboLength = combos.length; + const edgeLength = edges.length; + const vEdgeLength = vEdges.length; + const inactiveItems = self.inactiveItems || {}; + const activeItems = self.activeItems || {}; - for (let i = 0; i < nodeLength; i++) { - const node = nodes[i]; - const hasSelected = node.hasState('selected'); - if (self.resetSelected) { - if (hasSelected) { - graph.setItemState(node, 'selected', false); + for (let i = 0; i < nodeLength; i++) { + const node = nodes[i]; + const hasSelected = node.hasState('selected'); + if (self.resetSelected) { + if (hasSelected) { + graph.setItemState(node, 'selected', false); + } + } + graph.setItemState(node, activeState, false); + delete activeItems[node.getID()]; + if (inactiveState) { + graph.setItemState(node, inactiveState, true); + inactiveItems[node.getID()] = node; } } - graph.setItemState(node, activeState, false); - if (inactiveState) { - graph.setItemState(node, inactiveState, true); - } - } - for (let i = 0; i < comboLength; i++) { - const combo = combos[i]; - const hasSelected = combo.hasState('selected'); - if (self.resetSelected) { - if (hasSelected) { - graph.setItemState(combo, 'selected', false); + for (let i = 0; i < comboLength; i++) { + const combo = combos[i]; + const hasSelected = combo.hasState('selected'); + if (self.resetSelected) { + if (hasSelected) { + graph.setItemState(combo, 'selected', false); + } + } + graph.setItemState(combo, activeState, false); + delete activeItems[combo.getID()]; + if (inactiveState) { + graph.setItemState(combo, inactiveState, true); + inactiveItems[combo.getID()] = combo; } } - graph.setItemState(combo, activeState, false); - if (inactiveState) { - graph.setItemState(combo, inactiveState, true); + + for (let i = 0; i < edgeLength; i++) { + const edge = edges[i]; + graph.setItemState(edge, activeState, false); + delete activeItems[edge.getID()]; + if (inactiveState) { + graph.setItemState(edge, inactiveState, true); + inactiveItems[edge.getID()] = edge; + } } - } - for (let i = 0; i < edgeLength; i++) { - const edge = edges[i]; - graph.setItemState(edge, activeState, false); - if (inactiveState) { - graph.setItemState(edge, inactiveState, true); + for (let i = 0; i < vEdgeLength; i++) { + const vEdge = vEdges[i]; + graph.setItemState(vEdge, activeState, false); + delete activeItems[vEdge.getID()]; + if (inactiveState) { + graph.setItemState(vEdge, inactiveState, true); + inactiveItems[vEdge.getID()] = vEdge; + } } - } - for (let i = 0; i < vEdgeLength; i++) { - const vEdge = vEdges[i]; - graph.setItemState(vEdge, activeState, false); if (inactiveState) { - graph.setItemState(vEdge, inactiveState, true); + graph.setItemState(item, inactiveState, false); + delete inactiveItems[item.getID()]; } - } - - if (inactiveState) { - graph.setItemState(item, inactiveState, false); - } - graph.setItemState(item, activeState, true); + graph.setItemState(item, activeState, true); + activeItems[item.getID()] = item; - const rEdges = item.getEdges(); - const rEdgeLegnth = rEdges.length; - for (let i = 0; i < rEdgeLegnth; i++) { - const edge = rEdges[i]; - let otherEnd: INode; - if (edge.getSource() === item) { - otherEnd = edge.getTarget(); - } else { - otherEnd = edge.getSource(); - } - if (inactiveState) { - graph.setItemState(otherEnd, inactiveState, false); + const rEdges = item.getEdges(); + const rEdgeLegnth = rEdges.length; + for (let i = 0; i < rEdgeLegnth; i++) { + const edge = rEdges[i]; + let otherEnd: INode; + if (edge.getSource() === item) { + otherEnd = edge.getTarget(); + } else { + otherEnd = edge.getSource(); + } + if (inactiveState) { + graph.setItemState(otherEnd, inactiveState, false); + delete inactiveItems[otherEnd.getID()]; + } + graph.setItemState(otherEnd, activeState, true); + graph.setItemState(edge, inactiveState, false); + graph.setItemState(edge, activeState, true); + activeItems[otherEnd.getID()] = otherEnd; + delete inactiveItems[edge.getID()]; + activeItems[edge.getID()] = edge; + edge.toFront(); } - graph.setItemState(otherEnd, activeState, true); - graph.setItemState(edge, inactiveState, false); - graph.setItemState(edge, activeState, true); - edge.toFront(); - } - graph.emit('afteractivaterelations', { item: e.item, action: 'activate' }); - }, - clearActiveState(e: any) { - const self = this; - const graph = self.get('graph'); - if (!self.shouldUpdate(e.item, { event: e, action: 'deactivate' })) { - return; + self.activeItems = activeItems; + self.inactiveItems = inactiveItems; + graph.emit('afteractivaterelations', { item: e.item, action: 'activate' }); + }, + 50, + { + trailing: true, + leading: true } + ), + throttleClearActiveState: throttle( + (e, self) => { + const graph = self.get('graph'); + if (!graph || graph.destroyed) return; + if (!self.shouldUpdate(e.item, { event: e, action: 'deactivate' })) return; - const activeState = this.activeState; - const inactiveState = this.inactiveState; + const activeState = self.activeState; + const inactiveState = self.inactiveState; - const autoPaint = graph.get('autoPaint'); - graph.setAutoPaint(false); - const nodes = graph.getNodes() || []; - const combos = graph.getCombos() || []; - const edges = graph.getEdges() || []; - const vEdges = graph.get('vedges') || []; - const nodeLength = nodes.length; - const comboLength = combos.length; - const edgeLength = edges.length; - const vEdgeLength = vEdges.length; + const activeItems = self.activeItems || {}; + const inactiveItems = self.inactiveItems || {}; - for (let i = 0; i < nodeLength; i++) { - const node = nodes[i]; - graph.clearItemStates(node, [activeState, inactiveState]); + Object.values(activeItems).forEach(item => { + graph.clearItemStates(item, activeState); + }); + Object.values(inactiveItems).forEach(item => { + graph.clearItemStates(item, inactiveState); + }); + graph.emit('afteractivaterelations', { + item: e.item || self.get('item'), + action: 'deactivate', + }); + }, + 50, + { + trailing: true, + leading: true } - for (let i = 0; i < comboLength; i++) { - const combo = combos[i]; - graph.clearItemStates(combo, [activeState, inactiveState]); - } - for (let i = 0; i < edgeLength; i++) { - const edge = edges[i]; - graph.clearItemStates(edge, [activeState, inactiveState, 'deactivate']); - } - for (let i = 0; i < vEdgeLength; i++) { - const vEdge = vEdges[i]; - graph.clearItemStates(vEdge, [activeState, inactiveState, 'deactivate']); - } - graph.paint(); - graph.setAutoPaint(autoPaint); - graph.emit('afteractivaterelations', { - item: e.item || self.get('item'), - action: 'deactivate', - }); - }, + ) }; diff --git a/packages/pc/src/behavior/drag-node.ts b/packages/pc/src/behavior/drag-node.ts index efb2dbe3cfa..e9194a35bfa 100644 --- a/packages/pc/src/behavior/drag-node.ts +++ b/packages/pc/src/behavior/drag-node.ts @@ -30,9 +30,9 @@ export default { }, getEvents(): { [key in G6Event]?: string } { return { - 'node:dragstart': 'onDragStart', - 'node:drag': 'onDrag', - 'node:dragend': 'onDragEnd', + 'node:mousedown': 'onMouseDown', // G's dragstart event is not triggered sometimes when the drag events are not finished properly. Listen to mousedown and drag instead of dragstart + 'drag': 'onDragMove', // global drag, mouseup, and dragend to avoid mouse moving too fast to go out of a node while draging + 'dragend': 'onDragEnd', 'combo:dragenter': 'onDragEnter', 'combo:dragleave': 'onDragLeave', 'combo:drop': 'onDropCombo', @@ -54,11 +54,11 @@ export default { } return true; }, - onTouchStart(e: IG6GraphEvent) { - if (!e.item) return; + onTouchStart(evt: IG6GraphEvent) { + if (!evt.item) return; const self = this; try { - const touches = (e.originalEvent as TouchEvent).touches; + const touches = (evt.originalEvent as TouchEvent).touches; const event1 = touches[0]; const event2 = touches[1]; @@ -66,11 +66,16 @@ export default { return; } - e.preventDefault(); + evt.preventDefault(); } catch (e) { console.warn('Touch original event not exist!'); } - self.onDragStart(e); + this.mousedown = { + item: evt.item, + target: evt.target + }; + this.dragstart = true; + self.onDragStart(evt); }, onTouchMove(e: IG6GraphEvent) { const self = this; @@ -90,17 +95,44 @@ export default { } self.onDrag(e); }, + /** + * cache the manipulated item and target, since drag and dragend are global events but not node:* + * @param evt event param + */ + onMouseDown(evt: IG6GraphEvent) { + this.mousedown = { + item: evt.item, + target: evt.target + }; + }, + /** + * trigger dragstart/drag by mousedown and drag events + * @param evt event param + */ + onDragMove(evt: IG6GraphEvent) { + if (!this.mousedown) return; + if (!this.dragstart) { + // dragstart + this.dragstart = true; + this.onDragStart(evt); + } else { + // drag + this.onDrag({ + ...evt, + ...this.mousedown + }); + } + }, /** * 开始拖动节点 * @param evt */ onDragStart(evt: IG6GraphEvent) { this.currentShouldEnd = true; - if (!this.shouldBegin.call(this, evt)) { + if (!this.shouldBegin.call(this, { ...evt, ...this.mousedown })) { return; } - - const item: INode = evt.item as INode; + const { item, target } = this.mousedown; if (!item || item.destroyed || item.hasLocked()) { return; } @@ -112,7 +144,6 @@ export default { this.cachedCaptureItems.push(item); // 如果拖动的target 是linkPoints / anchorPoints 则不允许拖动 - const { target } = evt; if (target) { const isAnchorPoint = target.get('isAnchorPoint'); if (isAnchorPoint) { @@ -181,6 +212,13 @@ export default { this.point = {}; this.originPoint = {}; + + // 绑定浏览器右键监听,触发拖拽结束,结束拖拽时移除 + if (typeof window !== 'undefined') { + const self = this; + this.handleDOMContextMenu = () => self.onDragEnd(); + document.body.addEventListener('contextmenu', this.handleDOMContextMenu); + } }, /** @@ -188,13 +226,8 @@ export default { * @param evt */ onDrag(evt: IG6GraphEvent) { - if (!this.origin) { - return; - } - - if (!this.shouldUpdate.call(this, evt)) { - return; - } + if (!this.mousedown || !this.origin) return; + if (!this.shouldUpdate.call(this, evt)) return; if (this.get('enableDelegate')) { this.updateDelegate(evt); @@ -219,6 +252,8 @@ export default { * @param evt */ onDragEnd(evt: IG6GraphEvent) { + this.mousedown = false; + this.dragstart = false; if (!this.origin) { return; } @@ -277,6 +312,11 @@ export default { this.originPoint = {}; this.targets.length = 0; this.targetCombo = null; + + // 结束拖拽时移除浏览器右键监听 + if (typeof window !== 'undefined') { + document.body.removeEventListener('contextmenu', this.handleDOMContextMenu); + } }, /** * 拖动过程中将节点放置到 combo 上 @@ -498,18 +538,18 @@ export default { /** * 更新拖动元素时的delegate - * @param {Event} e 事件句柄 + * @param {Event} evt 事件句柄 * @param {number} x 拖动单个元素时候的x坐标 * @param {number} y 拖动单个元素时候的y坐标 */ - updateDelegate(e) { + updateDelegate(evt) { const { graph } = this; if (!this.delegateRect) { // 拖动多个 const parent = graph.get('group'); const attrs = deepMix({}, Global.delegateStyle, this.delegateStyle); - const { x: cx, y: cy, width, height, minX, minY } = this.calculationGroupPosition(e); + const { x: cx, y: cy, width, height, minX, minY } = this.calculationGroupPosition(evt); this.originPoint = { x: cx, y: cy, width, height, minX, minY }; // model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标 this.delegateRect = parent.addShape('rect', { @@ -525,8 +565,8 @@ export default { this.delegate = this.delegateRect; this.delegateRect.set('capture', false); } else { - const clientX = e.x - this.origin.x + this.originPoint.minX; - const clientY = e.y - this.origin.y + this.originPoint.minY; + const clientX = evt.x - this.origin.x + this.originPoint.minX; + const clientY = evt.y - this.origin.y + this.originPoint.minY; this.delegateRect.attr({ x: clientX, y: clientY, diff --git a/packages/pc/src/global.ts b/packages/pc/src/global.ts index 139ce0042d6..30e9ad33297 100644 --- a/packages/pc/src/global.ts +++ b/packages/pc/src/global.ts @@ -7,7 +7,7 @@ const textColor = 'rgb(0, 0, 0)'; const colorSet = getColorsWithSubjectColor(subjectColor, backColor); export default { - version: '0.7.10', + version: '0.7.11', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/packages/pc/tests/unit/behavior/active-relations-spec.ts b/packages/pc/tests/unit/behavior/active-relations-spec.ts index 3c3b57330a9..229ba095fb2 100644 --- a/packages/pc/tests/unit/behavior/active-relations-spec.ts +++ b/packages/pc/tests/unit/behavior/active-relations-spec.ts @@ -133,6 +133,8 @@ describe('activate-relations', () => { expect(edges.length).toEqual(0); expect(graph.findAllByState('node', 'inactive').length).toEqual(0); expect(graph.findAllByState('edge', 'inactive').length).toEqual(0); + graph.removeBehaviors(['activate-relations'], 'default'); + graph.off('afteractivaterelations'); done(); } } @@ -152,13 +154,16 @@ describe('activate-relations', () => { graph.emit('node:click', { item: node2 }); graph.emit('canvas:click', {}); - graph.emit('node:touchstart', { item: node1 }); - graph.emit('canvas:touchstart', {}); - graph.emit('node:touchstart', { item: node2 }); - graph.emit('canvas:touchstart', {}); + // TODO: 上面几行影响了下面执行 + setTimeout(() => { + graph.emit('node:touchstart', { originalEvent: { touches: [1, 0] }, item: node1 }); + graph.emit('canvas:touchstart', {}); + setTimeout(() => { + graph.emit('node:touchstart', { originalEvent: { touches: [1, 0] }, item: node2 }); + graph.emit('canvas:touchstart', {}); + }, 50) + }, 50) - graph.removeBehaviors(['activate-relations'], 'default'); - graph.off('afteractivaterelations'); }); it('custom state', (done) => { const graph2 = new Graph({ @@ -183,6 +188,7 @@ describe('activate-relations', () => { graph2.addItem('edge', { source: 'node1', target: 'node2' }); graph2.addItem('edge', { source: 'node1', target: 'node3' }); graph2.on('afteractivaterelations', (e) => { + console.log('afteractivaterelations', e.item === g2node1, e.action) const action = e.action; if (e.item === g2node1) { if (action === 'activate') { @@ -211,6 +217,7 @@ describe('activate-relations', () => { const edges = graph2.findAllByState('edge', 'highlight'); expect(nodes.length).toEqual(0); expect(edges.length).toEqual(0); + graph2.destroy(); done(); } } @@ -229,7 +236,6 @@ describe('activate-relations', () => { graph2.emit('node:mouseleave', { item: g2node1 }); graph2.emit('node:mouseenter', { item: g2node2 }); graph2.emit('node:mouseleave', { item: g2node2 }); - graph2.destroy(); }); it('should not update', () => { graph.addBehaviors( @@ -260,7 +266,7 @@ describe('activate-relations', () => { graph.off('node:click'); graph.off('canvas:click'); }); - it('combine selected state', () => { + it('combine selected state', done => { graph.addBehaviors( [ { @@ -284,18 +290,21 @@ describe('activate-relations', () => { let nodes = graph.findAllByState('node', 'selected'); expect(nodes.length).toEqual(1); graph.emit('node:mouseenter', { item: node2 }); - nodes = graph.findAllByState('node', 'selected'); - expect(nodes.length).toEqual(0); - nodes = graph.findAllByState('node', 'active'); - const edges = graph.findAllByState('edge', 'active'); - expect(nodes.length).toEqual(2); - expect(edges.length).toEqual(1); - graph.emit('node:click', { item: node1 }); - nodes = graph.findAllByState('node', 'selected'); - expect(nodes.length).toEqual(1); - graph.emit('node:mouseleave', {}); - graph.removeBehaviors(['activate-relations'], 'default'); - graph.destroy(); + setTimeout(() => { + nodes = graph.findAllByState('node', 'selected'); + expect(nodes.length).toEqual(0); + nodes = graph.findAllByState('node', 'active'); + const edges = graph.findAllByState('edge', 'active'); + expect(nodes.length).toEqual(2); + expect(edges.length).toEqual(1); + graph.emit('node:click', { item: node1 }); + nodes = graph.findAllByState('node', 'selected'); + expect(nodes.length).toEqual(1); + graph.emit('node:mouseleave', {}); + graph.removeBehaviors(['activate-relations'], 'default'); + graph.destroy(); + done(); + }, 100); }); }); @@ -706,7 +715,7 @@ describe('active-relations with combos', () => { }, groupByTypes: false }); - + graph.read(data2); graph.on('canvas:click', e => { console.log(graph.getEdges()) diff --git a/packages/pc/tests/unit/behavior/drag-node-spec.ts b/packages/pc/tests/unit/behavior/drag-node-spec.ts index 8d4136b187c..229032d005d 100644 --- a/packages/pc/tests/unit/behavior/drag-node-spec.ts +++ b/packages/pc/tests/unit/behavior/drag-node-spec.ts @@ -40,14 +40,15 @@ describe('drag-node', () => { r: 20, style: { lineWidth: 2, fill: '#666' }, }); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); const dragMatrix = node.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(50); expect(dragMatrix[7]).toEqual(50); graph.emit('canvas:drop', { x: 120, y: 120, item: node }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); const matrix = node.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(70); @@ -134,9 +135,10 @@ describe('drag-node', () => { }) as INode; graph.paint(); node.lock(); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); const dragMatrix = node.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(50); expect(dragMatrix[7]).toEqual(50); @@ -193,14 +195,15 @@ describe('drag-node', () => { }); graph.paint(); const node = graph.getNodes()[2]; - graph.emit('node:dragstart', { x: 100, y: 50, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); - graph.emit('node:drag', { x: 150, y: 150, item: node }); + graph.emit('node:mousedown', { x: 100, y: 50, item: node }); + graph.emit('drag', { x: 100, y: 50, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); + graph.emit('drag', { x: 150, y: 150, item: node }); const dragMatrix = node.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(100); expect(dragMatrix[7]).toEqual(50); - graph.emit('node:dragend', { x: 200, y: 200, item: node }); + graph.emit('dragend', { x: 200, y: 200, item: node }); }); it('drag selected nodes', () => { const graph: Graph = new Graph({ @@ -253,15 +256,16 @@ describe('drag-node', () => { }); graph.paint(); const node0 = graph.getNodes()[0]; - graph.emit('node:dragstart', { x: 50, y: 50, item: node0 }); - graph.emit('node:drag', { x: 120, y: 120, item: node0 }); - graph.emit('node:drag', { x: 150, y: 150, item: node0 }); + graph.emit('node:mousedown', { x: 50, y: 50, item: node0 }); + graph.emit('drag', { x: 50, y: 50, item: node0 }); + graph.emit('drag', { x: 120, y: 120, item: node0 }); + graph.emit('drag', { x: 150, y: 150, item: node0 }); const dragMatrix = node0.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(50); expect(dragMatrix[7]).toEqual(50); graph.emit('canvas:drop', { x: 200, y: 200, item: node0 }); - graph.emit('node:dragend', { x: 200, y: 200, item: node0 }); + graph.emit('dragend', { x: 200, y: 200, item: node0 }); const matrix = node0.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(200); @@ -314,13 +318,14 @@ describe('drag-node', () => { const node0 = graph.getNodes()[0]; graph.setItemState(graph.getNodes()[0], 'selected', true); graph.paint(); - graph.emit('node:dragstart', { x: 50, y: 50, item: node0 }); - graph.emit('node:drag', { x: 150, y: 150, item: node0 }); + graph.emit('node:mousedown', { x: 50, y: 50, item: node0 }); + graph.emit('drag', { x: 50, y: 50, item: node0 }); + graph.emit('drag', { x: 150, y: 150, item: node0 }); const dragMatrix = node0.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(150); expect(dragMatrix[7]).toEqual(150); // if the enableDelegate is false, dragend will not change the node position - graph.emit('node:dragend', { x: 200, y: 200, item: node0 }); + graph.emit('dragend', { x: 200, y: 200, item: node0 }); const matrix = node0.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(150); @@ -371,8 +376,9 @@ describe('drag-node', () => { expect(path[0][2]).toEqual(57.77817459305202); expect(mathEqual(289, path[1][1])).toEqual(true); expect(mathEqual(300, path[1][2])).toEqual(true); - graph.emit('node:dragstart', { x: 100, y: 100, item: source }); - graph.emit('node:drag', { x: 120, y: 120, item: source }); + graph.emit('node:mousedown', { x: 100, y: 100, item: source }); + graph.emit('drag', { x: 100, y: 100, item: source }); + graph.emit('drag', { x: 120, y: 120, item: source }); path = edge .get('group') .get('children')[0] @@ -384,7 +390,7 @@ describe('drag-node', () => { expect(mathEqual(289, path[1][1])).toEqual(true); expect(mathEqual(300, path[1][2])).toEqual(true); graph.emit('canvas:drop', { x: 140, y: 140, item: source }); - graph.emit('node:dragend', { x: 140, y: 140, item: source }); + graph.emit('dragend', { x: 140, y: 140, item: source }); setTimeout(() => { path = edge .get('group') @@ -424,9 +430,10 @@ describe('drag-node', () => { y: 50, style: { lineWidth: 2, fill: '#666' }, }); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); expect(clicked).toBe(false); graph.destroy(); }); @@ -477,9 +484,10 @@ describe('drag-node', () => { expect(matrix[1]).toEqual(0.7848921132128235); expect(matrix[3]).toEqual(-0.7848921132128235); expect(matrix[4]).toEqual(0.6196324480014811); - graph.emit('node:dragstart', { x: 100, y: 100, item: target }); - graph.emit('node:drag', { x: 120, y: 120, item: target }); - graph.emit('node:dragend', { x: 80, y: 120, item: target }); + graph.emit('node:mousedown', { x: 100, y: 100, item: target }); + graph.emit('drag', { x: 100, y: 100, item: target }); + graph.emit('drag', { x: 120, y: 120, item: target }); + graph.emit('dragend', { x: 80, y: 120, item: target }); matrix = label.getMatrix(); expect(matrix[0]).toEqual(0.627307162629268); expect(matrix[1]).toEqual(0.7787719330548688); @@ -515,13 +523,14 @@ describe('drag-node', () => { style: { lineWidth: 2, fill: '#666' }, }); graph.paint(); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); const matrix = node.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(50); expect(matrix[7]).toEqual(50); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); const stack = graph.getUndoStack(); expect(stack.linkedList.head).not.toBe(null); graph.destroy(); @@ -552,8 +561,9 @@ describe('drag-node', () => { style: { lineWidth: 2, fill: '#666' }, }); graph.paint(); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); const matrix = node.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(50); @@ -588,9 +598,10 @@ describe('drag-node', () => { id: 'node1', }); - graph.emit('node:dragstart', { x: 0, y: 0, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 0, y: 0, item: node }); + graph.emit('drag', { x: 0, y: 0, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); const matrix = node.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(120); @@ -635,8 +646,9 @@ describe('drag-node', () => { }); const keyShape = edge.get('keyShape'); const path = keyShape.attr('path'); - graph.emit('node:dragstart', { item: src, x: 55, y: 55 }); - graph.emit('node:drag', { item: src, x: 66, y: 66 }); + graph.emit('node:mousedown', { item: src, x: 55, y: 55 }); + graph.emit('drag', { item: src, x: 55, y: 55 }); + graph.emit('drag', { item: src, x: 66, y: 66 }); expect(keyShape.attr('path')).toEqual(path); graph.destroy(); }); @@ -665,7 +677,7 @@ describe('drag-node', () => { r: 20, style: { lineWidth: 2, fill: '#666' }, }); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); expect(node.getContainer().getMatrix()[6]).toEqual(50); expect(node.getContainer().getMatrix()[7]).toEqual(50); graph.emit('canvas:mouseleave', { x: 600, y: 600, item: node }); @@ -704,8 +716,9 @@ describe('drag-node', () => { r: 20, style: { lineWidth: 2, fill: '#666' }, }); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); const matrix = node.get('group').getMatrix(); expect(matrix[0]).toEqual(1); expect(matrix[6]).toEqual(50); @@ -760,9 +773,10 @@ describe('drag-node', () => { const node = graph.getNodes()[0]; const anchorPoint = node.get('group').get('children')[1]; - graph.emit('node:dragstart', { x: 100, y: 100, target: anchorPoint }); - graph.emit('node:drag', { x: 120, y: 120, target: anchorPoint }); - graph.emit('node:dragend', { x: 120, y: 120, target: anchorPoint }); + graph.emit('node:mousedown', { x: 100, y: 100, target: anchorPoint }); + graph.emit('drag', { x: 100, y: 100, target: anchorPoint }); + graph.emit('drag', { x: 120, y: 120, target: anchorPoint }); + graph.emit('dragend', { x: 120, y: 120, target: anchorPoint }); const dragMatrix = node.get('group').getMatrix(); expect(dragMatrix[6]).toEqual(50); expect(dragMatrix[7]).toEqual(50); @@ -813,16 +827,18 @@ describe('drag-node', () => { const node = graph.getNodes()[0]; const combo = graph.getCombos()[0]; - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); graph.emit('combo:drop', { x: 120, y: 120, item: combo }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); expect(combo.getChildren().nodes.length).toBe(1); - graph.emit('node:dragstart', { x: 100, y: 100, item: node }); - graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:mousedown', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 100, y: 100, item: node }); + graph.emit('drag', { x: 120, y: 120, item: node }); graph.emit('canvas:drop', { x: 120, y: 120 }); - graph.emit('node:dragend', { x: 120, y: 120, item: node }); + graph.emit('dragend', { x: 120, y: 120, item: node }); expect(combo.getChildren().nodes.length).toBe(0); graph.destroy(); }); diff --git a/packages/pc/tests/unit/combo-collapse-layout-spec.ts b/packages/pc/tests/unit/combo-collapse-layout-spec.ts index 0da00497a1c..46537876030 100644 --- a/packages/pc/tests/unit/combo-collapse-layout-spec.ts +++ b/packages/pc/tests/unit/combo-collapse-layout-spec.ts @@ -51,10 +51,10 @@ describe('combo layout with collapsed', () => { graph.data(data); graph.render(); - console.log('data', data) setTimeout(() => { expect(data.combos[0].x).toBe(250) expect(data.combos[0].y).toBe(375) + graph.destroy(); done() }, 0); }); diff --git a/packages/pc/tests/unit/graph/graph-spec.ts b/packages/pc/tests/unit/graph/graph-spec.ts index 717cb7e7785..9b7d949b197 100644 --- a/packages/pc/tests/unit/graph/graph-spec.ts +++ b/packages/pc/tests/unit/graph/graph-spec.ts @@ -1330,9 +1330,10 @@ describe('auto rotate label on edge', () => { it('drag node', () => { const node = graph.getNodes()[1]; - graph.emit('node:dragstart', { x: 80, y: 150, item: node }); - graph.emit('node:drag', { x: 200, y: 200, item: node }); - graph.emit('node:dragend', { x: 200, y: 200, item: node }); + graph.emit('node:mousedown', { x: 80, y: 150, item: node }); + graph.emit('drag', { x: 80, y: 150, item: node }); + graph.emit('drag', { x: 200, y: 200, item: node }); + graph.emit('dragend', { x: 200, y: 200, item: node }); const edge1 = graph.getEdges()[0]; console.log('edge1', edge1); const label1 = edge1.get('group').get('children')[1]; diff --git a/packages/pc/tests/unit/graph/svg-spec.ts b/packages/pc/tests/unit/graph/svg-spec.ts index a5612745ca0..b8bc78317ee 100644 --- a/packages/pc/tests/unit/graph/svg-spec.ts +++ b/packages/pc/tests/unit/graph/svg-spec.ts @@ -1087,9 +1087,10 @@ describe('auto rotate label on edge', () => { it('drag node', () => { const node = graph.getNodes()[1]; - graph.emit('node:dragstart', { x: 80, y: 150, item: node }); - graph.emit('node:drag', { x: 200, y: 200, item: node }); - graph.emit('node:dragend', { x: 200, y: 200, item: node }); + graph.emit('node:mousedown', { x: 80, y: 150, item: node }); + graph.emit('drag', { x: 80, y: 150, item: node }); + graph.emit('drag', { x: 200, y: 200, item: node }); + graph.emit('dragend', { x: 200, y: 200, item: node }); const edge1 = graph.getEdges()[0]; const label1 = edge1.get('group').get('children')[1]; const label1Matrix = label1.attr('matrix'); @@ -1307,10 +1308,11 @@ describe('behaviors', () => { }); it('drag-node', (done) => { - graph.emit('node:dragstart', { item, target: item, x: 0, y: 0 }); - graph.emit('node:drag', { item, target: item, x: 50, y: 150 }); - graph.emit('node:drag', { item, target: item, x: 50, y: 250 }); - graph.emit('node:dragend', { item, target: item, x: 50, y: 250 }); + graph.emit('node:mousedown', { item, target: item, x: 0, y: 0 }); + graph.emit('drag', { item, target: item, x: 0, y: 0 }); + graph.emit('drag', { item, target: item, x: 50, y: 150 }); + graph.emit('drag', { item, target: item, x: 50, y: 250 }); + graph.emit('dragend', { item, target: item, x: 50, y: 250 }); expect(item.getModel().x).toBe(100); expect(item.getModel().y).toBe(300); const edge = graph.getEdges()[0]; @@ -1322,9 +1324,10 @@ describe('behaviors', () => { const item2 = graph.getNodes()[1]; graph.setItemState(item, 'selected', true); graph.setItemState(item2, 'selected', true); - graph.emit('node:dragstart', { item, target: item, x: 0, y: 0 }); - graph.emit('node:drag', { item, target: item, x: 50, y: 50 }); - graph.emit('node:dragend', { item, target: item, x: 50, y: 50 }); + graph.emit('node:mousedown', { item, target: item, x: 0, y: 0 }); + graph.emit('drag', { item, target: item, x: 0, y: 0 }); + graph.emit('drag', { item, target: item, x: 50, y: 50 }); + graph.emit('dragend', { item, target: item, x: 50, y: 50 }); expect(item.getModel().x).toBe(150); expect(item.getModel().y).toBe(350); expect(item2.getModel().x).toBe(130); diff --git a/packages/pc/tests/unit/layout/dagre-spec.ts b/packages/pc/tests/unit/layout/dagre-spec.ts index bb816e507c1..6701160ab53 100644 --- a/packages/pc/tests/unit/layout/dagre-spec.ts +++ b/packages/pc/tests/unit/layout/dagre-spec.ts @@ -513,6 +513,86 @@ describe('dagre layout with combo', () => { graph.destroy(); }) }); + it.only('layout failed', () => { + const tdata = { + nodes: [ + { + id: "8a804cc2816b0a9a018170412b180ca7-8a804cc282e8aa680182ed506db90670", + layer: 0, + name: "根节点", + }, + { + id: "8a804cc283ea578c0183ee15132910ca", + layer: 1, + name: "中间件服务器(123.11.1.1)", + }, + { + id: "8a804cc283f44c940183f44fde860434", + layer: 2, + name: "66redis", + }, + { + comboId: "8a804cc283f44c940183f4519e74074e", + id: "8a804cc283f44c940183f4519e740752", + layer: 2, + name: "集群nacos测试(节点2)", + }, + { + comboId: "8a804cc283f44c940183f4519e74074e", + id: "8a804cc283f44c940183f4519e980754", + layer: 2, + name: "集群nacos测试(节点3)", + }, + { + comboId: "8a804cc283f44c940183f4519e74074e", + id: "8a804cc28407ea7301840dfcbcf91f4f", + layer: 2, + name: "集群nacos测试(节点1)", + } + ], + combos: [ + { id: "8a804cc283f44c940183f4519e74074e", "label": "集群nacos测试" } + ], + edges: [ + { + source: "8a804cc2816b0a9a018170412b180ca7-8a804cc282e8aa680182ed506db90670", + target: "8a804cc283ea578c0183ee15132910ca" + }, + { + source: "8a804cc283ea578c0183ee15132910ca", + target: "8a804cc283f44c940183f44fde860434" + }, + { + source: "8a804cc283ea578c0183ee15132910ca", + target: "8a804cc283f44c940183f44fde860434" + }, + { + source: "8a804cc283ea578c0183ee15132910ca", + target: "8a804cc283f44c940183f4519e740752" + } + ] + }; + const graph = new G6.Graph({ + container: div, + width: 800, + height: 500, + fitView: true, + fitViewPadding: 50, + layout: { + type: 'dagre', + controlPoints: true, + sortByCombo: true, + ranksep: 20, + nodesep: 10, + }, + modes: { + default: ['drag-combo', 'drag-canvas', 'drag-node'], + }, + }); + graph.data(tdata); + graph.render(); + console.log('graph.get', graph.getNodes()) + }); }); describe('dagre layout', () => { diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 21ba4cccb96..30c442f8c52 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-plugin", - "version": "0.7.10", + "version": "0.7.11", "description": "G6 Plugin", "main": "lib/index.js", "module": "es/index.js", @@ -22,8 +22,8 @@ "@antv/g-base": "^0.5.1", "@antv/g-canvas": "^0.5.2", "@antv/g-svg": "^0.5.2", - "@antv/g6-core": "0.7.10", - "@antv/g6-element": "0.7.10", + "@antv/g6-core": "0.7.110", + "@antv/g6-element": "0.7.11", "@antv/matrix-util": "^3.1.0-beta.3", "@antv/scale": "^0.3.4", "@antv/util": "^2.0.9", @@ -59,6 +59,6 @@ "jquery": "^3.5.1", "rimraf": "^3.0.2", "ts-jest": "^26.4.4", - "@antv/g6": "4.5.1" + "@antv/g6": "^4.7.10" } } \ No newline at end of file diff --git a/packages/plugin/src/timeBar/controllerBtn.ts b/packages/plugin/src/timeBar/controllerBtn.ts index 6683cfac1d6..af363a8b560 100644 --- a/packages/plugin/src/timeBar/controllerBtn.ts +++ b/packages/plugin/src/timeBar/controllerBtn.ts @@ -85,6 +85,10 @@ const DEFAULT_CONTROLLER_CONFIG = { const SPEED_CONTROLLER_OFFSET = 110; const TOGGLE_MODEL_OFFSET = 50; +export const TIME_TYPE = { + SINGLE: 'single' as 'single', + RANGE: 'range' as 'range' +} export type ControllerCfg = Partial<{ readonly group: IGroup; @@ -141,6 +145,8 @@ export type ControllerCfg = Partial<{ readonly timePointControllerText?: string; /** 播放时间类型切换器单一文本时的文本,默认为‘时间范围’ */ readonly timeRangeControllerText?: string; + /** 时间播放类型默认值,不配置则为 'range' 即‘时间范围’ */ + readonly defaultTimeType?: 'single' | 'range'; }>; export default class ControllerBtn { @@ -189,7 +195,7 @@ export default class ControllerBtn { }); this.speedAxisY = []; this.currentSpeed = this.controllerCfg.speed; - this.currentType = 'range'; + this.currentType = this.controllerCfg.defaultTimeType || TIME_TYPE.RANGE; this.fontFamily = cfg.fontFamily || 'Arial, sans-serif'; this.init(); } @@ -429,7 +435,7 @@ export default class ControllerBtn { } private renderToggleTime() { - const { width } = this.controllerCfg; + const { width, defaultTimeType } = this.controllerCfg; const timeTypeControllerStyle = { ...DEFAULT_TIMETYPE_CONTROLLER_STYLE, @@ -449,13 +455,14 @@ export default class ControllerBtn { name: 'toggle-group', }); + const isChecked = defaultTimeType === TIME_TYPE.SINGLE; this.toggleGroup.addShape('rect', { attrs: { x: width - TOGGLE_MODEL_OFFSET, y: this.speedAxisY[0] + 3.5, ...box, }, - isChecked: false, + isChecked, name: 'toggle-model', }); @@ -472,17 +479,17 @@ export default class ControllerBtn { name: 'check-icon' }); - this.checkedIcon.hide(); + if (!isChecked) this.checkedIcon.hide(); this.checkedText = this.toggleGroup.addShape('text', { attrs: { - text: this.controllerCfg?.timePointControllerText || '单一时间', + text: isChecked ? this.controllerCfg?.timeRangeControllerText || '时间范围' : this.controllerCfg?.timePointControllerText || '单一时间', x: width - TOGGLE_MODEL_OFFSET + 15, y: this.speedAxisY[0] + 4, fontFamily: typeof window !== 'undefined' ? window.getComputedStyle(document.body, null).getPropertyValue('font-family') || - 'Arial, sans-serif' + 'Arial, sans-serif' : 'Arial, sans-serif', ...text, } as any, @@ -558,11 +565,11 @@ export default class ControllerBtn { if (!isChecked) { this.checkedIcon.show(); this.checkedText.attr('text', this.controllerCfg?.timeRangeControllerText || '时间范围'); - this.currentType = 'single'; + this.currentType = TIME_TYPE.SINGLE; } else { this.checkedIcon.hide(); this.checkedText.attr('text', this.controllerCfg?.timePointControllerText || '单一时间'); - this.currentType = 'range'; + this.currentType = TIME_TYPE.RANGE; } evt.target.set('isChecked', !isChecked); this.group.emit(TIMEBAR_CONFIG_CHANGE, { diff --git a/packages/plugin/src/timeBar/index.ts b/packages/plugin/src/timeBar/index.ts index cc4617a4236..37b4639f2e1 100644 --- a/packages/plugin/src/timeBar/index.ts +++ b/packages/plugin/src/timeBar/index.ts @@ -204,6 +204,29 @@ export default class TimeBar extends Base { this.set('fontFamily', fontFamily); } + /** + * 触发时间轴播放 + */ + public play() { + this.togglePlay(true); + } + /** + * 触发时间轴暂停 + */ + public pause() { + this.togglePlay(false); + } + + /** + * 时间轴播放状态(播放/暂停)的切换 + */ + private togglePlay(play) { + const timebar = this.get('timebar'); + if (!timebar) return; + timebar.isPlay = !!play; + timebar.changePlayStatus(); + } + private renderTrend() { const { width, @@ -272,6 +295,7 @@ export default class TimeBar extends Base { width, height: 42, padding: 2, + controllerCfg, ...tick, }); } @@ -298,7 +322,18 @@ export default class TimeBar extends Base { } private filterData(evt) { - const { value } = evt; + let { value } = evt; + if (!value) { + value = []; + const type = this._cfgs.type; + if (!type || type === 'trend' || type === 'simple') { + value[0] = this._cfgs.slider.start; + value[1] = this._cfgs.slider.end; + } else if (type === 'tick') { + value[0] = this._cfgs.tick.start; + value[1] = this._cfgs.tick.end; + } + } let trendData = null; const type = this._cfgs.type; @@ -350,35 +385,58 @@ export default class TimeBar extends Base { const shouldIgnore = this.get('shouldIgnore'); const minDate = trendData[min].date, maxDate = trendData[max].date; if (changeData || changeData === undefined) { - let filterNodes = this.cacheGraphData.nodes; - let filterEdges = this.cacheGraphData.edges; + let originNodes = this.cacheGraphData.nodes; + let originEdges = this.cacheGraphData.edges; + const currentNodeExistMap = {}; + const currentEdgeExistMap = {}; + graph.getNodes().forEach(node => currentNodeExistMap[node.getID()] = true); + graph.getEdges().forEach(edge => currentEdgeExistMap[edge.getID()] = true); + if (filterItemTypes.includes('node')) { - filterNodes = filterNodes.filter((node: any) => { + originNodes.forEach((node: any) => { const date = +(getDate?.(node) || node.date); - return (date >= minDate && date <= maxDate) || shouldIgnore?.('node', node, { min: minDate, max: maxDate }); - } - ); - const nodeIds = filterNodes.map((node) => node.id); - if (filterEdges) { - // 过滤 source 或 target 不在 min 和 max 范围内的边 - filterEdges = filterEdges.filter((edge) => ( - (nodeIds.includes(edge.source) && nodeIds.includes(edge.target)) || - shouldIgnore?.('edge', edge, { min: minDate, max: maxDate }) - )); - } + const hitRange = (date >= minDate && date <= maxDate) || shouldIgnore?.('node', node, { min: minDate, max: maxDate }); + const exist = currentNodeExistMap[node.id]; + if (exist && !hitRange) { + graph.removeItem(node.id); + currentNodeExistMap[node.id] = false; + } else if (!exist && hitRange) { + graph.addItem('node', node); + currentNodeExistMap[node.id] = true; + } + }); + // 过滤 source 或 target 不在 min 和 max 范围内的边 + originEdges?.forEach((edge) => { + const shouldShow = (currentNodeExistMap[edge.source] && currentNodeExistMap[edge.target]) || shouldIgnore?.('edge', edge, { min: minDate, max: maxDate }); + const exist = !!graph.findById(edge.id); + if (exist && !shouldShow) { + graph.removeItem(edge.id); + currentEdgeExistMap[edge.id] = false; + } else if (!exist && shouldShow) { + graph.addItem('edge', edge); + currentEdgeExistMap[edge.id] = true; + } else if (!exist) { + currentEdgeExistMap[edge.id] = false; + } + }); } if (this.get('filterEdge') || filterItemTypes.includes('edge')) { - filterEdges = filterEdges.filter((edge) => { + originEdges?.filter((edge) => { const date = +(getDate?.(edge) || edge.date); - return (date >= minDate && date <= maxDate) || shouldIgnore?.('edge', edge, { min: minDate, max: maxDate }); + const hitRange = (date >= minDate && date <= maxDate) || shouldIgnore?.('edge', edge, { min: minDate, max: maxDate }); + const endsExist = currentNodeExistMap[edge.source] && currentNodeExistMap[edge.target]; + const shouldShow = hitRange && endsExist; + const exist = currentEdgeExistMap[edge.id]; + if (exist && !shouldShow) { + currentEdgeExistMap[edge.id] = false; + graph.removeItem(edge.id); + } else if (!exist && shouldShow) { + currentEdgeExistMap[edge.id] = true; + graph.addItem('edge', edge); + } }); } - - graph.changeData({ - nodes: filterNodes, - edges: filterEdges, - }); } else { if (filterItemTypes.includes('node')) { graph.getNodes().forEach(node => { @@ -400,7 +458,9 @@ export default class TimeBar extends Base { if (date < trendData[min].date || date > trendData[max].date) { graph.hideItem(edge); } else { - graph.showItem(edge); + const sourceVisible = edge.getSource().isVisible(); + const targetVisible = edge.getTarget().isVisible(); + if (sourceVisible && targetVisible) graph.showItem(edge); } }); } @@ -408,40 +468,40 @@ export default class TimeBar extends Base { } } - private initEvent() { - let start = 0; - let end = 0; - const type = this._cfgs.type; - if (!type || type === 'trend' || type === 'simple') { - start = this._cfgs.slider.start; - end = this._cfgs.slider.end; - } else if (type === 'tick') { - start = this._cfgs.tick.start; - end = this._cfgs.tick.end; - } - + private afterrenderListener = e => this.filterData({}); + private valueChangeListener = throttle( + e => this.filterData(e), // 不可简写,否则 filterData 中 this 指针不对 + 200, + { + trailing: true, + leading: true, + }, + ) as any; + + public changeData = e => { const graph: IGraph = this.get('graph'); - graph.on('afterrender', (e) => { - this.filterData({ value: [start, end] }); - }); + this.cacheGraphData = graph.get('data'); + this.filterData({}); + } - // 时间轴的值发生改变的事件 + private initEvent() { + const graph: IGraph = this.get('graph'); + // 图数据变化,更新时间轴的原始数据 + graph.on('afterchangedata', this.changeData); + // 图渲染,触发时间轴筛选 + graph.on('afterrender', this.afterrenderListener); + // 时间轴的值发生改变的事件,触发筛选 graph.on( VALUE_CHANGE, - throttle( - (e) => { - this.filterData(e); - }, - 200, - { - trailing: true, - leading: true, - }, - ) as any, + this.valueChangeListener ); } public destroy() { + const graph: IGraph = this.get('graph'); + graph.off('afterchangedata', this.changeData); + graph.off('afterrender', this.afterrenderListener); + graph.off(VALUE_CHANGE, this.valueChangeListener); const timebar = this.get('timebar'); if (timebar && timebar.destory) { timebar.destory(); diff --git a/packages/plugin/src/timeBar/timeBarSlice.ts b/packages/plugin/src/timeBar/timeBarSlice.ts index c8c84d8d1e9..85196ab508c 100644 --- a/packages/plugin/src/timeBar/timeBarSlice.ts +++ b/packages/plugin/src/timeBar/timeBarSlice.ts @@ -6,7 +6,7 @@ import { ICanvas, IGroup } from '@antv/g-base'; import { isNumber, isString } from '@antv/util'; import { ShapeStyle, IAbstractGraph as IGraph } from '@antv/g6-core'; import TimeBarTooltip from './timeBarTooltip'; -import ControllerBtn from './controllerBtn'; +import ControllerBtn, { ControllerCfg } from './controllerBtn'; import { VALUE_CHANGE, TIMELINE_START, @@ -63,6 +63,7 @@ export interface TimeBarSliceConfig extends TimeBarSliceOption { readonly x: number; readonly y: number; readonly tickLabelStyle?: Object; + readonly controllerCfg?: ControllerCfg } export default class TimeBarSlice { @@ -132,6 +133,8 @@ export default class TimeBarSlice { private fontFamily: string = 'Arial, sans-serif'; + private controllerCfg: ControllerCfg; + constructor(cfgs?: TimeBarSliceConfig) { const { graph, @@ -150,7 +153,10 @@ export default class TimeBarSlice { unselectedTickStyle = DEFAULT_UNSELECTEDTICK_STYLE, tooltipBackgroundColor, tooltipFomatter, - tickLabelStyle + tickLabelStyle, + controllerCfg = { + speed: 1, + } } = cfgs; this.graph = graph; @@ -169,6 +175,8 @@ export default class TimeBarSlice { this.tickLabelStyle = tickLabelStyle || {}; this.selectedTickStyle = selectedTickStyle; this.unselectedTickStyle = unselectedTickStyle; + this.controllerCfg = controllerCfg; + this.currentSpeed = controllerCfg.speed || 1; this.x = x; this.y = y; @@ -180,7 +188,7 @@ export default class TimeBarSlice { this.fontFamily = typeof window !== 'undefined' ? window.getComputedStyle(document.body, null).getPropertyValue('font-family') || - 'Arial, sans-serif' + 'Arial, sans-serif' : 'Arial, sans-serif'; this.renderSlices(); @@ -357,6 +365,7 @@ export default class TimeBarSlice { hideTimeTypeController: true, speed: this.currentSpeed, fontFamily: this.fontFamily || 'Arial, sans-serif', + ...this.controllerCfg, }); } @@ -520,19 +529,19 @@ export default class TimeBarSlice { private startPlay() { return typeof window !== 'undefined' ? window.requestAnimationFrame(() => { - const speed = this.currentSpeed; - - // 一分钟刷新一次 - if (this.frameCount % (60 / speed) === 0) { - this.frameCount = 0; - this.updateStartEnd(1); - } - this.frameCount++; - - if (this.isPlay) { - this.playHandler = this.startPlay(); - } - }) + const speed = this.currentSpeed; + + // 一分钟刷新一次 + if (this.frameCount % (60 / speed) === 0) { + this.frameCount = 0; + this.updateStartEnd(1); + } + this.frameCount++; + + if (this.isPlay) { + this.playHandler = this.startPlay(); + } + }) : undefined; } @@ -573,10 +582,7 @@ export default class TimeBarSlice { } public destory() { - this.graph.off(VALUE_CHANGE, () => { /* do nothing */}); - const group = this.sliceGroup; - group.off('click'); group.off('dragstart'); group.off('dragover'); diff --git a/packages/plugin/src/timeBar/trendTimeBar.ts b/packages/plugin/src/timeBar/trendTimeBar.ts index 17a27896be5..b3ef1299ae2 100644 --- a/packages/plugin/src/timeBar/trendTimeBar.ts +++ b/packages/plugin/src/timeBar/trendTimeBar.ts @@ -4,7 +4,7 @@ import { ext } from '@antv/matrix-util'; import Trend, { TrendCfg } from './trend'; import Handler from './handler'; import { isString } from '@antv/util'; -import ControllerBtn, { ControllerCfg } from './controllerBtn'; +import ControllerBtn, { ControllerCfg, TIME_TYPE } from './controllerBtn'; import { ShapeStyle, IAbstractGraph as IGraph } from '@antv/g6-core'; import { VALUE_CHANGE, @@ -268,7 +268,7 @@ export default class TrendTimeBar { this.tickLabelStyle = { ...TICK_LABEL_STYLE, ...tick.tickLabelStyle }; this.tickLineStyle = { ...TICK_LINE_STYLE, ...tick.tickLineStyle }; - this.currentMode = 'range'; + this.currentMode = controllerCfg.defaultTimeType || TIME_TYPE.RANGE; // 初始信息 this.start = start; this.end = end; @@ -579,6 +579,12 @@ export default class TrendTimeBar { // 绑定事件鼠标事件 this.bindEvents(); + + if (this.currentMode === TIME_TYPE.SINGLE) { + this.minHandlerShape.hide(); + this.foregroundShape.hide(); + this.minTextShape.hide(); + } } /** @@ -645,11 +651,11 @@ export default class TrendTimeBar { this.group.on(TIMEBAR_CONFIG_CHANGE, ({ type, speed }) => { this.currentSpeed = speed; this.currentMode = type; - if (type === 'single') { + if (type === TIME_TYPE.SINGLE) { this.minHandlerShape.hide(); this.foregroundShape.hide(); this.minTextShape.hide(); - } else if (type === 'range') { + } else if (type === TIME_TYPE.RANGE) { this.minHandlerShape.show(); this.foregroundShape.show(); this.minTextShape.show(); @@ -838,10 +844,10 @@ export default class TrendTimeBar { this.maxHandlerShape.setX(max - handlerWidth / 2); each(maxAttrs, (v, k) => this.maxTextShape.attr(k, v)); - if (this.currentMode === 'range') { + if (this.currentMode === TIME_TYPE.RANGE) { // 因为存储的 start、end 可能不一定是按大小存储的,所以排序一下,对外是 end >= start this.graph.emit(VALUE_CHANGE, { value: [this.start, this.end].sort() }); - } else if (this.currentMode === 'single') { + } else if (this.currentMode === TIME_TYPE.SINGLE) { this.graph.emit(VALUE_CHANGE, { value: [this.end, this.end] }); } } @@ -937,7 +943,7 @@ export default class TrendTimeBar { } public destory() { - this.graph.off(VALUE_CHANGE, () => { /* do nothing */}); + this.graph.off(VALUE_CHANGE, () => { /* do nothing */ }); const group = this.group; diff --git a/packages/plugin/tests/unit/timebar-spec.ts b/packages/plugin/tests/unit/timebar-spec.ts index dc4066e6a62..14a34a07e09 100644 --- a/packages/plugin/tests/unit/timebar-spec.ts +++ b/packages/plugin/tests/unit/timebar-spec.ts @@ -6,47 +6,52 @@ div.id = 'timebar-plugin'; document.body.appendChild(div); // div.style.backgroundColor = '#252728' -const data: GraphData = { - nodes: [ - { - id: 'node1', - label: 'node1', - x: 100, - y: 100, - }, - { - id: 'node2', - label: 'node2', - x: 150, - y: 300, - }, - ], - edges: [ - { - source: 'node1', - target: 'node2', - }, - ], -}; - -for (let i = 0; i < 100; i++) { - const id = `node-${i}`; - data.nodes.push({ - id, - label: `node${i}`, - date: i, - value: Math.round(Math.random() * 300), - }); +const generateData = (nodeNum = 100) => { + const data: GraphData = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 100, + }, + { + id: 'node2', + label: 'node2', + x: 150, + y: 300, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], + }; + + for (let i = 0; i < nodeNum; i++) { + const id = `node-${i}`; + data.nodes.push({ + id, + label: `node${i}`, + date: i, + value: Math.round(Math.random() * 300), + }); - const edgeDate = Math.round(Math.random() * 100); - data.edges.push({ - date: edgeDate, - label: `${edgeDate}`, - source: `node-${Math.round(Math.random() * 90)}`, - target: `node-${Math.round(Math.random() * 90)}`, - }); + const edgeDate = Math.round(Math.random() * 100); + data.edges.push({ + date: edgeDate, + label: `${edgeDate}`, + source: `node-${Math.floor(Math.random() * nodeNum)}`, + target: `node-${Math.floor(Math.random() * nodeNum)}`, + }); + } + return data; } +const data = generateData(); + describe('timeline filter edges', () => { it('timeline filter edges', () => { const timeBarData = []; @@ -75,6 +80,7 @@ describe('timeline filter edges', () => { } }, tick: { + data: timeBarData, tickLabelFormatter: (d) => { const i = d.date; const month = i < 30 ? '01' : '02'; @@ -108,6 +114,7 @@ describe('timeline filter edges', () => { width: 480, fill: '#fff', stroke: '#fff', + // defaultTimeType: 'single', preBtnStyle: { fill: '#155EE1', stroke: '#155EE1', diff --git a/packages/react-node/src/Register/getDataFromReactNode.ts b/packages/react-node/src/Register/getDataFromReactNode.ts index 85a3254aa39..1fed76fda28 100644 --- a/packages/react-node/src/Register/getDataFromReactNode.ts +++ b/packages/react-node/src/Register/getDataFromReactNode.ts @@ -17,7 +17,7 @@ const getShapeFromReact = (REl: ReactElement): RawNode => { const { style: attrs = {}, type, ...props } = data; let { children: ochildren } = REl.props; if (type === 'text') { - attrs.text = ochildren.join ? ochildren.join('') : ochildren; + attrs.text = ochildren?.join ? ochildren.join('') : ochildren; return { type, attrs, diff --git a/packages/site/docs/api/Plugins.en.md b/packages/site/docs/api/Plugins.en.md index 0b037c86b12..9780c99a24b 100644 --- a/packages/site/docs/api/Plugins.en.md +++ b/packages/site/docs/api/Plugins.en.md @@ -678,9 +678,17 @@ TimeBar Plugin exposes several timing events. They could be listened by `graph.o | timebarstartplay | Emitted when the timeline starts to play. | | timebarendplay | Emitted when the timeline ends playing. | -### Definition of the Configurations +### API + +#### play + +Controll the timebar instance begin to play. e.g. `timebar.play()`. + +#### pause + +Controll the timebar instance to pause. e.g. `timebar.pause()`. -#### Definition of the Interfaces +### Definition of the Interfaces The complete interfaces for the TimeBar is shown below: @@ -968,7 +976,9 @@ type ControllerCfg = Partial<{ readonly containerStyle?: ExtendedShapeStyle; /** the text for the right-bottom switch controlling play with single time point or time range */ readonly timePointControllerText?: string; - readonly timeRangeControllerText?: string + readonly timeRangeControllerText?: string; + /** [Supported from v4.7.11] the default type of the playing, 'single' means single time point, and 'range' means time range. 'range' by default */ + readonly defaultTimeType?: 'single' | 'range'; }> ``` diff --git a/packages/site/docs/api/Plugins.zh.md b/packages/site/docs/api/Plugins.zh.md index 4ffa0eafb05..461264590a5 100644 --- a/packages/site/docs/api/Plugins.zh.md +++ b/packages/site/docs/api/Plugins.zh.md @@ -688,9 +688,17 @@ TimeBar 插件暴露除了几个时机事件,方便用户监听内部状态的 | timebarstartplay | 时间轴开始播放时触发 | | timebarendplay | 时间轴播放结束时触发 | -### 参数定义 +### API + +#### play + +使用 API 控制时间轴开始播放。e.g. `timebar.play()`。 -#### 接口定义 +#### pause + +使用 API 控制时间轴暂停播放。e.g. `timebar.pause()`。 + +### 接口定义 完整的 TimeBar 的接口定义如下: @@ -725,7 +733,7 @@ interface TimeBarConfig extends IPluginBaseConfig { // [v4.5.1 起废弃,由 filterItemTypes 代替] 是否过滤边,若为 true,则需要配合边数据上有 date 字段,过滤节点同时将不满足 date 在选中范围内的边也过滤出去;若为 false,则仅过滤节点以及两端节点都被过滤出去的边 readonly filterEdge?: boolean; - // [v4.5.1 起支持] 是否通过 graph.changeData 改变图上数据从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 + // [v4.5.1 起支持] 是否通过增删图上元素(graph.addItem graph.removeItem)从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 readonly changeData?: boolean; // TimeBar 时间范围变化时的回调函数,当不定义该函数时,时间范围变化时默认过滤图上的数据 @@ -956,6 +964,9 @@ type ControllerCfg = Partial<{ /** ‘播放’ 与 ‘暂停’ 按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ readonly playBtnStyle?: ShapeStyle; + /** [v4.7.11 起支持配置] 时间播放类型默认值,不配置则为 'range' 即‘时间范围’ */ + readonly defaultTimeType?: 'single' | 'range'; + /** ‘速度控制器’ 的样式,包括速度的指针、速度指示滚轮(横线)、文本的样式,同时可以为 speedControllerStyle 及其子图形样式配置 scale、offsetX、offsetY 单独控制该控制器及其子图形的缩放以及平移) */ readonly speedControllerStyle?: { offsetX?: number, diff --git a/packages/site/gatsby-browser.js b/packages/site/gatsby-browser.js index e6efdddcbb1..607f249cd41 100644 --- a/packages/site/gatsby-browser.js +++ b/packages/site/gatsby-browser.js @@ -1,6 +1,6 @@ -// window.g6 = require('@antv/g6/es'); // import the source for debugging -// window.g6 = require('@antv/g6/lib'); // import the source for debugging -window.g6 = require('@antv/g6/dist/g6.min.js'); // import the package for webworker +window.g6 = require('@antv/g6/es'); // import the source for debugging +window.g6 = require('@antv/g6/lib'); // import the source for debugging +// window.g6 = require('@antv/g6/dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); window.Chart = require('@antv/chart-node-g6'); window.AntVUtil = require('@antv/util');