From 27d980ee410503900a2852be7457ba95cb35da16 Mon Sep 17 00:00:00 2001 From: Yanyan Wang Date: Wed, 29 Jun 2022 16:09:41 +0800 Subject: [PATCH] perf: optimize the performance of combo graph first rendering; (#3763) --- CHANGELOG.md | 4 + packages/core/package.json | 2 +- packages/core/src/global.ts | 2 +- packages/core/src/graph/graph.ts | 343 +++++++++++-------------------- packages/element/package.json | 4 +- packages/g6/package.json | 4 +- packages/g6/src/index.ts | 4 +- packages/pc/package.json | 8 +- packages/pc/src/global.ts | 2 +- packages/plugin/package.json | 6 +- 10 files changed, 143 insertions(+), 236 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1343ba569..f4b13a8c25c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # ChangeLog +#### 4.6.12 + +- perf: optimize the performance of combo graph first rendering; + #### 4.6.11 - fix: star node with leftBottom linkPoint show and hide problem; diff --git a/packages/core/package.json b/packages/core/package.json index 326a510ffea..6f1504e8dc0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-core", - "version": "0.6.11", + "version": "0.6.12", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index 5e4806c71f0..3c7839fcd4c 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -64,7 +64,7 @@ const colorSet = { }; export default { - version: '0.6.11', + version: '0.6.12', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/packages/core/src/graph/graph.ts b/packages/core/src/graph/graph.ts index 85076fc1686..44a929741fb 100644 --- a/packages/core/src/graph/graph.ts +++ b/packages/core/src/graph/graph.ts @@ -1262,7 +1262,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs // Add undefined as a placeholder for the next cycle. This way we return items matching the input order for (let i = 0; i < items.length; i++) { const item = items[i]; - if (item.type !== 'edge') { + if (item.type !== 'edge' && item.type !== 'vedge') { returnItems.push(this.innerAddItem(item.type, item.model, itemController)); } else { returnItems.push(undefined); @@ -1272,7 +1272,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs // 2. add all the edges for (let i = 0; i < items.length; i++) { const item = items[i]; - if (item.type === 'edge') { + if (item.type === 'edge' || item.type === 'vedge') { returnItems[i] = this.innerAddItem(item.type, item.model, itemController); } } @@ -1478,9 +1478,11 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs this.emit('beforerender'); - each(nodes, (node: NodeConfig) => { - self.add('node', node, false, false); - }); + self.addItems( + nodes.map(node => ({ type: 'node', model: node })), + false, + false + ); // process the data to tree structure if (combos?.length !== 0) { @@ -1490,9 +1492,11 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs self.addCombos(combos); } - each(edges, (edge: EdgeConfig) => { - self.add('edge', edge, false, false); - }); + self.addItems( + edges.map(edge => ({ type: 'edge', model: edge })), + false, + false + ); const animate = self.get('animate'); @@ -2567,8 +2571,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs const edges = this.getEdges().concat(this.get('vedges')); // find all the descendant nodes and combos - let cnodes = []; - let ccombos = []; + let cNodesCombos = []; const comboTrees = this.get('comboTrees'); let found = false; (comboTrees || []).forEach(ctree => { @@ -2582,110 +2585,77 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs // if the combo is found, concat the descendant nodes and combos const item = this.findById(subTree.id) as ICombo; if (item && item.getType && item.getType() === 'combo') { - cnodes = cnodes.concat(item.getNodes()); - ccombos = ccombos.concat(item.getCombos()); + cNodesCombos = cNodesCombos.concat(item.getNodes()); + cNodesCombos = cNodesCombos.concat(item.getCombos()); } } return true; }); }); - const edgeWeightMap = {}; - const addedVEdges = []; + const addedVEdgeMap = {}; edges.forEach(edge => { - if (edge.isVisible() && !edge.getModel().isVEdge) return; + const { isVEdge, size = 1 } = edge.getModel(); + if (edge.isVisible() && !isVEdge) return; let source = edge.getSource(); let target = edge.getTarget(); - if ( - ((cnodes.includes(source) || ccombos.includes(source)) && - !cnodes.includes(target) && - !ccombos.includes(target)) || - source.getModel().id === comboModel.id + let otherEnd = null; + let otherEndIsSource; + if (source.getModel().id === comboModel.id || + (cNodesCombos.includes(source) && !cNodesCombos.includes(target))) { + // source is the current combo, or descent node/combo is the source but not the target) + otherEnd = target; + otherEndIsSource = false; + } else if (target.getModel().id === comboModel.id || + (!cNodesCombos.includes(source) && cNodesCombos.includes(target)) ) { - const edgeModel = edge.getModel(); - if (edgeModel.isVEdge) { - this.removeItem(edge, false); - return; - } - - let targetModel = target.getModel(); - while (!target.isVisible()) { - target = this.findById( - (targetModel.parentId as string) || (targetModel.comboId as string), - ) as ICombo; - if (!target || (!targetModel.parentId && !targetModel.comboId)) return; // all the ancestors are hidden, then ignore the edge - targetModel = target.getModel(); - } - - const targetId = targetModel.id; + // target is the current combo, or descent node/combo is the target but not the source) + otherEnd = source; + otherEndIsSource = true; + } - if (edgeWeightMap[`${comboModel.id}-${targetId}`]) { - edgeWeightMap[`${comboModel.id}-${targetId}`] += edgeModel.size || 1; - return; - } - // the source is in the combo, the target is not - const vedge = this.addItem( - 'vedge', - { - source: comboModel.id, - target: targetId, - isVEdge: true, - }, - false, - ); - edgeWeightMap[`${comboModel.id}-${targetId}`] = edgeModel.size || 1; - addedVEdges.push(vedge); - } else if ( - (!cnodes.includes(source) && - !ccombos.includes(source) && - (cnodes.includes(target) || ccombos.includes(target))) || - target.getModel().id === comboModel.id - ) { - const edgeModel = edge.getModel(); - if (edgeModel.isVEdge) { + if (otherEnd) { + if (isVEdge) { this.removeItem(edge, false); return; } - let sourceModel = source.getModel(); - while (!source.isVisible()) { - source = this.findById( - (sourceModel.parentId as string) || (sourceModel.comboId as string), - ) as ICombo; - if (!source || (!sourceModel.parentId && !sourceModel.comboId)) return; // all the ancestors are hidden, then ignore the edge - sourceModel = source.getModel(); + let otherEndModel = otherEnd.getModel(); + while (!otherEnd.isVisible()) { + const { parentId: otherEndPId, comboId: otherEndCId } = otherEndModel; + const otherEndParentId = otherEndPId || otherEndCId; + otherEnd = this.findById(otherEndParentId) as ICombo; + if (!otherEnd || !otherEndParentId) return; // all the ancestors are hidden, then ignore the edge + otherEndModel = otherEnd.getModel(); } - const sourceId = sourceModel.id; - if (edgeWeightMap[`${sourceId}-${comboModel.id}`]) { - edgeWeightMap[`${sourceId}-${comboModel.id}`] += edgeModel.size || 1; + + const otherEndId = otherEndModel.id; + + const vEdgeInfo = otherEndIsSource ? { + source: otherEndId, + target: comboModel.id, + size, + isVEdge: true, + } : { + source: comboModel.id, + target: otherEndId, + size, + isVEdge: true, + }; + const key = `${vEdgeInfo.source}-${vEdgeInfo.target}`; + if (addedVEdgeMap[key]) { + addedVEdgeMap[key].size += size; return; } - // the target is in the combo, the source is not - const vedge = this.addItem( - 'vedge', - { - target: comboModel.id, - source: sourceId, - isVEdge: true, - }, - false, - ); - edgeWeightMap[`${sourceId}-${comboModel.id}`] = edgeModel.size || 1; - addedVEdges.push(vedge); + addedVEdgeMap[key] = vEdgeInfo; } }); // update the width of the virtual edges, which is the sum of merged actual edges // be attention that the actual edges with same endpoints but different directions will be represented by two different virtual edges - addedVEdges.forEach(vedge => { - const vedgeModel = vedge.getModel(); - this.updateItem( - vedge, - { - size: edgeWeightMap[`${vedgeModel.source}-${vedgeModel.target}`], - }, - false, - ); - }); + this.addItems( + Object.values(addedVEdgeMap).map(edgeInfo => ({ type: 'vedge', model: edgeInfo as EdgeConfig })), + false + ); this.emit('aftercollapseexpandcombo', { action: 'collapse', item: combo }); } @@ -2713,8 +2683,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs const edges = this.getEdges().concat(this.get('vedges')); // find all the descendant nodes and combos - let cnodes = []; - let ccombos = []; + let cNodesCombos = []; const comboTrees = this.get('comboTrees'); let found = false; (comboTrees || []).forEach(ctree => { @@ -2726,173 +2695,107 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs if (found) { const item = this.findById(subTree.id) as ICombo; if (item && item.getType && item.getType() === 'combo') { - cnodes = cnodes.concat(item.getNodes()); - ccombos = ccombos.concat(item.getCombos()); + cNodesCombos = cNodesCombos.concat(item.getNodes()); + cNodesCombos = cNodesCombos.concat(item.getCombos()); } } return true; }); }); - const edgeWeightMap = {}; - const addedVEdges = {}; + const addedVEdgeMap = {}; edges.forEach(edge => { if (edge.isVisible() && !edge.getModel().isVEdge) return; let source = edge.getSource(); let target = edge.getTarget(); let sourceId = source.get('id'); let targetId = target.get('id'); - if ( - ((cnodes.includes(source) || ccombos.includes(source)) && - !cnodes.includes(target) && - !ccombos.includes(target)) || - sourceId === comboModel.id + let otherEnd = null; + let otherEndIsSource; + if (sourceId === comboModel.id || + (cNodesCombos.includes(source) && !cNodesCombos.includes(target)) ) { // the source is in the combo, the target is not - - // ignore the virtual edges - if (edge.getModel().isVEdge) { - this.removeItem(edge, false); - return; - } - - let targetModel = target.getModel(); - // find the nearest visible ancestor - while (!target.isVisible()) { - target = this.findById( - (targetModel.comboId as string) || (targetModel.parentId as string), - ) as ICombo; - if (!target || (!targetModel.parentId && !targetModel.comboId)) { - return; // if all the ancestors of the oppsite are all hidden, ignore the edge - } - targetModel = target.getModel(); - } - targetId = targetModel.id; - - let sourceModel = source.getModel(); - // find the nearest visible ancestor - while (!source.isVisible()) { - source = this.findById( - (sourceModel.comboId as string) || (sourceModel.parentId as string), - ) as ICombo; - if (!source || (!sourceModel.parentId && !sourceModel.comboId)) { - return; // if all the ancestors of the oppsite are all hidden, ignore the edge - } - if (sourceModel.comboId === comboModel.id || sourceModel.parentId === comboModel.id) { - break; // if the next ancestor is the combo, break the while - } - sourceModel = source.getModel(); - } - sourceId = sourceModel.id; - - if (targetId) { - const vedgeId = `${sourceId}-${targetId}`; - // update the width of the virtual edges, which is the sum of merged actual edges - // be attention that the actual edges with same endpoints but different directions will be represented by two different virtual edges - if (edgeWeightMap[vedgeId]) { - edgeWeightMap[vedgeId] += edge.getModel().size || 1; - this.updateItem( - addedVEdges[vedgeId], - { - size: edgeWeightMap[vedgeId], - }, - false, - ); - return; - } - const vedge = this.addItem( - 'vedge', - { - source: sourceId, - target: targetId, - isVEdge: true, - }, - false, - ); - - edgeWeightMap[vedgeId] = edge.getModel().size || 1; - addedVEdges[vedgeId] = vedge; - } - } else if ( - (!cnodes.includes(source) && - !ccombos.includes(source) && - (cnodes.includes(target) || ccombos.includes(target))) || - targetId === comboModel.id + otherEnd = target; + otherEndIsSource = false; + } else if (targetId === comboModel.id || + (!cNodesCombos.includes(source) && (cNodesCombos.includes(target))) ) { // the target is in the combo, the source is not + otherEnd = source; + otherEndIsSource = true; + } else if (cNodesCombos.includes(source) && cNodesCombos.includes(target)) { + // both source and target are in the combo, if the target and source are both visible, show the edge + if (source.isVisible() && target.isVisible()) { + edge.show(); + } + } + if (otherEnd) { + const { isVEdge, size = 1 } = edge.getModel(); // ignore the virtual edges - if (edge.getModel().isVEdge) { + if (isVEdge) { this.removeItem(edge, false); return; } - let sourceModel = source.getModel(); + let otherEndModel = otherEnd.getModel(); // find the nearest visible ancestor - while (!source.isVisible()) { - source = this.findById( - (sourceModel.comboId as string) || (sourceModel.parentId as string), - ) as ICombo; - if (!source || (!sourceModel.parentId && !sourceModel.comboId)) { + while (!otherEnd.isVisible()) { + const { parentId: otherEndPId, comboId: otherEndCId } = otherEndModel; + const otherEndParentId = otherEndPId || otherEndCId; + otherEnd = this.findById(otherEndParentId) as ICombo; + if (!otherEnd || !otherEndParentId) { return; // if all the ancestors of the oppsite are all hidden, ignore the edge } - sourceModel = source.getModel(); + otherEndModel = otherEnd.getModel(); } - sourceId = sourceModel.id; + const otherEndId = otherEndModel.id; - let targetModel = target.getModel(); + let selfEnd = otherEndIsSource ? target : source; + let selfEndModel = selfEnd.getModel(); // find the nearest visible ancestor - while (!target.isVisible()) { - target = this.findById( - (targetModel.comboId as string) || (targetModel.parentId as string), - ) as ICombo; - if (!target || (!targetModel.parentId && !targetModel.comboId)) { + while (!selfEnd.isVisible()) { + const { parentId: selfEndPId, comboId: selfEndCId } = otherEndModel; + const selfEndParentId = selfEndPId || selfEndCId; + selfEnd = this.findById(selfEndParentId) as ICombo; + if (!selfEnd || !selfEndParentId) { return; // if all the ancestors of the oppsite are all hidden, ignore the edge } - if (targetModel.comboId === comboModel.id || targetModel.parentId === comboModel.id) { + if (selfEndModel.comboId === comboModel.id || selfEndModel.parentId === comboModel.id) { break; // if the next ancestor is the combo, break the while } - targetModel = target.getModel(); + selfEndModel = selfEnd.getModel(); } - targetId = targetModel.id; + const selfEndId = selfEndModel.id; - if (sourceId) { - const vedgeId = `${sourceId}-${targetId}`; + if (otherEndId) { + const vEdgeInfo = otherEndIsSource ? { + source: otherEndId, + target: selfEndId, + isVEdge: true, + size + } : { + source: selfEndId, + target: otherEndId, + isVEdge: true, + size + } + const vedgeId = `${vEdgeInfo.source}-${vEdgeInfo.target}`; // update the width of the virtual edges, which is the sum of merged actual edges // be attention that the actual edges with same endpoints but different directions will be represented by two different virtual edges - if (edgeWeightMap[vedgeId]) { - edgeWeightMap[vedgeId] += edge.getModel().size || 1; - this.updateItem( - addedVEdges[vedgeId], - { - size: edgeWeightMap[vedgeId], - }, - false, - ); + if (addedVEdgeMap[vedgeId]) { + addedVEdgeMap[vedgeId].size += size; return; } - const vedge = this.addItem( - 'vedge', - { - target: targetId, - source: sourceId, - isVEdge: true, - }, - false, - ); - edgeWeightMap[vedgeId] = edge.getModel().size || 1; - addedVEdges[vedgeId] = vedge; - } - } else if ( - (cnodes.includes(source) || ccombos.includes(source)) && - (cnodes.includes(target) || ccombos.includes(target)) - ) { - // both source and target are in the combo, if the target and source are both visible, show the edge - if (source.isVisible() && target.isVisible()) { - edge.show(); + addedVEdgeMap[vedgeId] = vEdgeInfo; } } }); + this.addItems( + Object.values(addedVEdgeMap).map(edgeInfo => ({ type: 'vedge', model: edgeInfo as EdgeConfig })), + false + ) this.emit('aftercollapseexpandcombo', { action: 'expand', item: combo }); } diff --git a/packages/element/package.json b/packages/element/package.json index 79f2a811cb2..109cc039be2 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-element", - "version": "0.6.11", + "version": "0.6.12", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", @@ -61,7 +61,7 @@ }, "dependencies": { "@antv/g-base": "^0.5.1", - "@antv/g6-core": "0.6.11", + "@antv/g6-core": "0.6.12", "@antv/util": "~2.0.5" }, "devDependencies": { diff --git a/packages/g6/package.json b/packages/g6/package.json index a9aeee31e2e..9669cdfc08e 100644 --- a/packages/g6/package.json +++ b/packages/g6/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6", - "version": "4.6.11", + "version": "4.6.12", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", @@ -66,7 +66,7 @@ ] }, "dependencies": { - "@antv/g6-pc": "0.6.11" + "@antv/g6-pc": "0.6.12" }, "devDependencies": { "@babel/core": "^7.7.7", diff --git a/packages/g6/src/index.ts b/packages/g6/src/index.ts index 5890f84d9a4..f8e1dff8465 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.6.11'; +G6.version = '4.6.12'; export * from '@antv/g6-pc'; export default G6; -export const version = '4.6.11'; +export const version = '4.6.12'; diff --git a/packages/pc/package.json b/packages/pc/package.json index 788ebca526b..dd44de2a6c9 100644 --- a/packages/pc/package.json +++ b/packages/pc/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-pc", - "version": "0.6.11", + "version": "0.6.12", "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.6.11", - "@antv/g6-element": "0.6.11", - "@antv/g6-plugin": "0.6.11", + "@antv/g6-core": "0.6.12", + "@antv/g6-element": "0.6.12", + "@antv/g6-plugin": "0.6.12", "@antv/hierarchy": "^0.6.7", "@antv/layout": "^0.2.5", "@antv/matrix-util": "^3.1.0-beta.3", diff --git a/packages/pc/src/global.ts b/packages/pc/src/global.ts index 31aea92d0c7..9a6d336fa48 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.6.11', + version: '0.6.12', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 790824cd2d0..7d8ba123ef3 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-plugin", - "version": "0.6.11", + "version": "0.6.12", "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.6.11", - "@antv/g6-element": "0.6.11", + "@antv/g6-core": "0.6.12", + "@antv/g6-element": "0.6.12", "@antv/matrix-util": "^3.1.0-beta.3", "@antv/scale": "^0.3.4", "@antv/util": "^2.0.9",