UNPKG

@antv/g6

Version:

A Graph Visualization Framework in JavaScript

821 lines 33.1 kB
import { Graph as GraphLib } from '@antv/graphlib'; import { isNil, isNumber, uniq } from '@antv/util'; import { COMBO_KEY, ChangeType, TREE_KEY } from '../constants'; import { isCollapsed } from '../utils/collapsibility'; import { cloneElementData, isElementDataEqual, mergeElementsData } from '../utils/data'; import { arrayDiff } from '../utils/diff'; import { toG6Data, toGraphlibData } from '../utils/graphlib'; import { idOf, parentIdOf } from '../utils/id'; import { positionOf } from '../utils/position'; import { format, print } from '../utils/print'; import { dfs } from '../utils/traverse'; import { add } from '../utils/vector'; export class DataController { constructor() { /** * <zh/> 最近一次删除的 combo 的 id * * <en/> The ids of the last deleted combos * @remarks * <zh/> 当删除 combo 后,会将其 id 从 comboIds 中移除,此时根据 Graphlib 的 changes 事件获取到的 NodeRemoved 无法区分是 combo 还是 node。 * 因此需要记录最近一次删除的 combo 的 id,并用于 isCombo 的判断 * * <en/> When the combo is deleted, its id will be removed from comboIds. At this time, the NodeRemoved obtained according to the changes event of Graphlib cannot distinguish whether it is a combo or a node. * Therefore, it is necessary to record the id of the last deleted combo and use it to judge isCombo */ this.latestRemovedComboIds = new Set(); this.comboIds = new Set(); /** * <zh/> 获取详细数据变更 * * <en/> Get detailed data changes */ this.changes = []; /** * <zh/> 批处理计数器 * * <en/> Batch processing counter */ this.batchCount = 0; /** * <zh/> 是否处于无痕模式 * * <en/> Whether it is in traceless mode */ this.isTraceless = false; this.enableUpdateNodeLikeHierarchy = true; this.model = new GraphLib(); } pushChange(change) { if (this.isTraceless) return; const { type } = change; if (type === ChangeType.NodeUpdated || type === ChangeType.EdgeUpdated || type === ChangeType.ComboUpdated) { const { value, original } = change; this.changes.push({ value: cloneElementData(value), original: cloneElementData(original), type }); } else { this.changes.push({ value: cloneElementData(change.value), type }); } } getChanges() { return this.changes; } clearChanges() { this.changes = []; } batch(callback) { this.batchCount++; this.model.batch(callback); this.batchCount--; } isBatching() { return this.batchCount > 0; } /** * <zh/> 执行操作而不会留下记录 * * <en/> Perform operations without leaving records * @param callback - <zh/> 回调函数 | <en/> callback function * @remarks * <zh/> 通常用于运行时调整元素并同步数据,避免触发数据变更导致重绘 * * <en/> Usually used to adjust elements at runtime and synchronize data to avoid triggering data changes and causing redraws */ silence(callback) { this.isTraceless = true; callback(); this.isTraceless = false; } isCombo(id) { return this.comboIds.has(id) || this.latestRemovedComboIds.has(id); } getData() { return { nodes: this.getNodeData(), edges: this.getEdgeData(), combos: this.getComboData(), }; } getNodeData(ids) { return this.model.getAllNodes().reduce((acc, node) => { const data = toG6Data(node); if (this.isCombo(idOf(data))) return acc; if (ids === undefined) acc.push(data); else ids.includes(idOf(data)) && acc.push(data); return acc; }, []); } getEdgeDatum(id) { return toG6Data(this.model.getEdge(id)); } getEdgeData(ids) { return this.model.getAllEdges().reduce((acc, edge) => { const data = toG6Data(edge); if (ids === undefined) acc.push(data); else ids.includes(idOf(data)) && acc.push(data); return acc; }, []); } getComboData(ids) { return this.model.getAllNodes().reduce((acc, combo) => { const data = toG6Data(combo); if (!this.isCombo(idOf(data))) return acc; if (ids === undefined) acc.push(data); else ids.includes(idOf(data)) && acc.push(data); return acc; }, []); } getRootsData(hierarchyKey = TREE_KEY) { return this.model.getRoots(hierarchyKey).map(toG6Data); } getAncestorsData(id, hierarchyKey) { const { model } = this; if (!model.hasNode(id) || !model.hasTreeStructure(hierarchyKey)) return []; return model.getAncestors(id, hierarchyKey).map(toG6Data); } getDescendantsData(id) { const root = this.getElementDataById(id); const data = []; dfs(root, (node) => { if (node !== root) data.push(node); }, (node) => this.getChildrenData(idOf(node)), 'TB'); return data; } getParentData(id, hierarchyKey) { const { model } = this; if (!hierarchyKey) { print.warn('The hierarchy structure key is not specified'); return undefined; } if (!model.hasNode(id) || !model.hasTreeStructure(hierarchyKey)) return undefined; const parent = model.getParent(id, hierarchyKey); return parent ? toG6Data(parent) : undefined; } getChildrenData(id) { const structureKey = this.getElementType(id) === 'node' ? TREE_KEY : COMBO_KEY; const { model } = this; if (!model.hasNode(id) || !model.hasTreeStructure(structureKey)) return []; return model.getChildren(id, structureKey).map(toG6Data); } /** * <zh/> 获取指定类型元素的数据 * * <en/> Get the data of the specified type of element * @param elementType - <zh/> 元素类型 | <en/> element type * @returns <zh/> 元素数据 | <en/> element data */ getElementsDataByType(elementType) { if (elementType === 'node') return this.getNodeData(); if (elementType === 'edge') return this.getEdgeData(); if (elementType === 'combo') return this.getComboData(); return []; } /** * <zh/> 根据 ID 获取元素的数据,不用关心元素的类型 * * <en/> Get the data of the element by ID, no need to care about the type of the element * @param id - <zh/> 元素 ID 数组 | <en/> element ID array * @returns <zh/> 元素数据 | <en/> data of the element */ getElementDataById(id) { const type = this.getElementType(id); if (type === 'edge') return this.getEdgeDatum(id); return this.getNodeLikeDatum(id); } /** * <zh/> 获取节点的数据 * * <en/> Get node data * @param id - <zh/> 节点 ID | <en/> node ID * @returns <zh/> 节点数据 | <en/> node data */ getNodeLikeDatum(id) { const data = this.model.getNode(id); return toG6Data(data); } /** * <zh/> 获取所有节点和 combo 的数据 * * <en/> Get all node and combo data * @param ids - <zh/> 节点和 combo ID 数组 | <en/> node and combo ID array * @returns <zh/> 节点和 combo 的数据 | <en/> node and combo data */ getNodeLikeData(ids) { return this.model.getAllNodes().reduce((acc, node) => { const data = toG6Data(node); if (ids) ids.includes(idOf(data)) && acc.push(data); else acc.push(data); return acc; }, []); } getElementDataByState(elementType, state) { const elementData = this.getElementsDataByType(elementType); return elementData.filter((datum) => { var _a; return (_a = datum.states) === null || _a === void 0 ? void 0 : _a.includes(state); }); } getElementState(id) { var _a; return ((_a = this.getElementDataById(id)) === null || _a === void 0 ? void 0 : _a.states) || []; } hasNode(id) { return this.model.hasNode(id) && !this.isCombo(id); } hasEdge(id) { return this.model.hasEdge(id); } hasCombo(id) { return this.model.hasNode(id) && this.isCombo(id); } getRelatedEdgesData(id, direction = 'both') { return this.model.getRelatedEdges(id, direction).map(toG6Data); } getNeighborNodesData(id) { return this.model.getNeighbors(id).map(toG6Data); } setData(data) { const { nodes: modifiedNodes = [], edges: modifiedEdges = [], combos: modifiedCombos = [] } = data; const { nodes: originalNodes, edges: originalEdges, combos: originalCombos } = this.getData(); const nodeDiff = arrayDiff(originalNodes, modifiedNodes, (node) => idOf(node), isElementDataEqual); const edgeDiff = arrayDiff(originalEdges, modifiedEdges, (edge) => idOf(edge), isElementDataEqual); const comboDiff = arrayDiff(originalCombos, modifiedCombos, (combo) => idOf(combo), isElementDataEqual); this.batch(() => { const dataToAdd = { nodes: nodeDiff.enter, edges: edgeDiff.enter, combos: comboDiff.enter, }; this.addData(dataToAdd); this.computeZIndex(dataToAdd, 'add', true); const dataToUpdate = { nodes: nodeDiff.update, edges: edgeDiff.update, combos: comboDiff.update, }; this.updateData(dataToUpdate); this.computeZIndex(dataToUpdate, 'update', true); const dataToRemove = { nodes: nodeDiff.exit.map(idOf), edges: edgeDiff.exit.map(idOf), combos: comboDiff.exit.map(idOf), }; this.removeData(dataToRemove); }); } addData(data) { const { nodes, edges, combos } = data; this.batch(() => { // add combo first this.addComboData(combos); this.addNodeData(nodes); this.addEdgeData(edges); }); this.computeZIndex(data, 'add'); } addNodeData(nodes = []) { if (!nodes.length) return; this.model.addNodes(nodes.map((node) => { this.pushChange({ value: node, type: ChangeType.NodeAdded }); return toGraphlibData(node); })); this.updateNodeLikeHierarchy(nodes); this.computeZIndex({ nodes }, 'add'); } addEdgeData(edges = []) { if (!edges.length) return; this.model.addEdges(edges.map((edge) => { this.pushChange({ value: edge, type: ChangeType.EdgeAdded }); return toGraphlibData(edge); })); this.computeZIndex({ edges }, 'add'); } addComboData(combos = []) { if (!combos.length) return; const { model } = this; if (!model.hasTreeStructure(COMBO_KEY)) { model.attachTreeStructure(COMBO_KEY); } model.addNodes(combos.map((combo) => { this.comboIds.add(idOf(combo)); this.pushChange({ value: combo, type: ChangeType.ComboAdded }); return toGraphlibData(combo); })); this.updateNodeLikeHierarchy(combos); this.computeZIndex({ combos }, 'add'); } addChildrenData(parentId, childrenData) { const parentData = this.getNodeLikeDatum(parentId); const childrenId = childrenData.map(idOf); this.addNodeData(childrenData); this.updateNodeData([{ id: parentId, children: [...(parentData.children || []), ...childrenId] }]); this.addEdgeData(childrenId.map((childId) => ({ source: parentId, target: childId }))); } /** * <zh/> 计算 zIndex * * <en/> Calculate zIndex * @param data - <zh/> 新增的数据 | <en/> newly added data * @param type - <zh/> 操作类型 | <en/> operation type * @param force - <zh/> 忽略批处理 | <en/> ignore batch processing * @remarks * <zh/> 调用该函数的情况: * - 新增元素 * - 更新节点/组合的 combo * - 更新节点的 children * * <en/> The situation of calling this function: * - Add element * - Update the combo of the node/combo * - Update the children of the node */ computeZIndex(data, type, force = false) { if (!force && this.isBatching()) return; this.batch(() => { const { nodes = [], edges = [], combos = [] } = data; combos.forEach((combo) => { var _a, _b, _c; const id = idOf(combo); if (type === 'add' && isNumber((_a = combo.style) === null || _a === void 0 ? void 0 : _a.zIndex)) return; if (type === 'update' && !('combo' in combo)) return; const parent = this.getParentData(id, COMBO_KEY); const zIndex = parent ? ((_c = (_b = parent.style) === null || _b === void 0 ? void 0 : _b.zIndex) !== null && _c !== void 0 ? _c : 0) + 1 : 0; this.preventUpdateNodeLikeHierarchy(() => { this.updateComboData([{ id, style: { zIndex } }]); }); }); nodes.forEach((node) => { var _a, _b, _c; const id = idOf(node); if (type === 'add' && isNumber((_a = node.style) === null || _a === void 0 ? void 0 : _a.zIndex)) return; if (type === 'update' && !('combo' in node) && !('children' in node)) return; let zIndex = 0; const comboParent = this.getParentData(id, COMBO_KEY); if (comboParent) { zIndex = (((_b = comboParent.style) === null || _b === void 0 ? void 0 : _b.zIndex) || 0) + 1; } else { const nodeParent = this.getParentData(id, TREE_KEY); if (nodeParent) zIndex = ((_c = nodeParent === null || nodeParent === void 0 ? void 0 : nodeParent.style) === null || _c === void 0 ? void 0 : _c.zIndex) || 0; } this.preventUpdateNodeLikeHierarchy(() => { this.updateNodeData([{ id, style: { zIndex } }]); }); }); edges.forEach((edge) => { var _a, _b, _c, _d, _e; if (isNumber((_a = edge.style) === null || _a === void 0 ? void 0 : _a.zIndex)) return; let { id, source, target } = edge; if (!id) id = idOf(edge); else { const datum = this.getEdgeDatum(id); source = datum.source; target = datum.target; } if (!source || !target) return; const sourceZIndex = ((_c = (_b = this.getNodeLikeDatum(source)) === null || _b === void 0 ? void 0 : _b.style) === null || _c === void 0 ? void 0 : _c.zIndex) || 0; const targetZIndex = ((_e = (_d = this.getNodeLikeDatum(target)) === null || _d === void 0 ? void 0 : _d.style) === null || _e === void 0 ? void 0 : _e.zIndex) || 0; this.updateEdgeData([{ id: idOf(edge), style: { zIndex: Math.max(sourceZIndex, targetZIndex) - 1 } }]); }); }); } /** * <zh/> 计算元素置顶后的 zIndex * * <en/> Calculate the zIndex after the element is placed on top * @param id - <zh/> 元素 ID | <en/> ID of the element * @returns <zh/> zIndex | <en/> zIndex */ getFrontZIndex(id) { var _a; const elementType = this.getElementType(id); const elementData = this.getElementDataById(id); const data = this.getData(); // 排除当前元素 / Exclude the current element Object.assign(data, { [`${elementType}s`]: data[`${elementType}s`].filter((element) => idOf(element) !== id), }); if (elementType === 'combo') { // 如果 combo 展开,则排除 combo 的子节点/combo 及内部边 // If the combo is expanded, exclude the child nodes/combos of the combo and the internal edges if (!isCollapsed(elementData)) { const ancestorIds = new Set(this.getAncestorsData(id, COMBO_KEY).map(idOf)); data.nodes = data.nodes.filter((element) => !ancestorIds.has(idOf(element))); data.combos = data.combos.filter((element) => !ancestorIds.has(idOf(element))); data.edges = data.edges.filter(({ source, target }) => !ancestorIds.has(source) && !ancestorIds.has(target)); } } return Math.max(((_a = elementData.style) === null || _a === void 0 ? void 0 : _a.zIndex) || 0, 0, ...Object.values(data) .flat() .map((datum) => { var _a; return (((_a = datum === null || datum === void 0 ? void 0 : datum.style) === null || _a === void 0 ? void 0 : _a.zIndex) || 0) + 1; })); } updateNodeLikeHierarchy(data) { if (!this.enableUpdateNodeLikeHierarchy) return; const { model } = this; data.forEach((datum) => { const id = idOf(datum); const parent = parentIdOf(datum); if (parent !== undefined) { if (!model.hasTreeStructure(COMBO_KEY)) model.attachTreeStructure(COMBO_KEY); // 解除原父节点的子节点关系,更新原父节点及其祖先的数据 // Remove the child relationship of the original parent node, update the data of the original parent node and its ancestors if (parent === null) { this.refreshComboData(id); } this.setParent(id, parentIdOf(datum), COMBO_KEY); } const children = datum.children || []; if (children.length) { if (!model.hasTreeStructure(TREE_KEY)) model.attachTreeStructure(TREE_KEY); const _children = children.filter((child) => model.hasNode(child)); _children.forEach((child) => this.setParent(child, id, TREE_KEY)); if (_children.length !== children.length) { // 从数据中移除不存在的子节点 // Remove non-existent child nodes from the data this.updateNodeData([{ id, children: _children }]); } } }); } /** * <zh/> 执行变更时不要更新节点层次结构 * * <en/> Do not update the node hierarchy when executing changes * @param callback - <zh/> 变更函数 | <en/> change function */ preventUpdateNodeLikeHierarchy(callback) { this.enableUpdateNodeLikeHierarchy = false; callback(); this.enableUpdateNodeLikeHierarchy = true; } updateData(data) { const { nodes, edges, combos } = data; this.batch(() => { this.updateNodeData(nodes); this.updateComboData(combos); this.updateEdgeData(edges); }); this.computeZIndex(data, 'update'); } updateNodeData(nodes = []) { if (!nodes.length) return; const { model } = this; this.batch(() => { const modifiedNodes = []; nodes.forEach((modifiedNode) => { const id = idOf(modifiedNode); const originalNode = toG6Data(model.getNode(id)); if (isElementDataEqual(originalNode, modifiedNode)) return; const value = mergeElementsData(originalNode, modifiedNode); this.pushChange({ value, original: originalNode, type: ChangeType.NodeUpdated }); model.mergeNodeData(id, value); modifiedNodes.push(value); }); this.updateNodeLikeHierarchy(modifiedNodes); }); this.computeZIndex({ nodes }, 'update'); } /** * <zh/> 将所有数据提交到变更记录中以进行重绘 * * <en/> Submit all data to the change record for redrawing */ refreshData() { const { nodes, edges, combos } = this.getData(); nodes.forEach((node) => { this.pushChange({ value: node, original: node, type: ChangeType.NodeUpdated }); }); edges.forEach((edge) => { this.pushChange({ value: edge, original: edge, type: ChangeType.EdgeUpdated }); }); combos.forEach((combo) => { this.pushChange({ value: combo, original: combo, type: ChangeType.ComboUpdated }); }); } syncNodeLikeDatum(datum) { const { model } = this; const id = idOf(datum); if (!model.hasNode(id)) return; const original = toG6Data(model.getNode(id)); const value = mergeElementsData(original, datum); model.mergeNodeData(id, value); } syncEdgeDatum(datum) { const { model } = this; const id = idOf(datum); if (!model.hasEdge(id)) return; const original = toG6Data(model.getEdge(id)); const value = mergeElementsData(original, datum); model.mergeEdgeData(id, value); } updateEdgeData(edges = []) { if (!edges.length) return; const { model } = this; this.batch(() => { edges.forEach((modifiedEdge) => { const id = idOf(modifiedEdge); const originalEdge = toG6Data(model.getEdge(id)); if (isElementDataEqual(originalEdge, modifiedEdge)) return; if (modifiedEdge.source && originalEdge.source !== modifiedEdge.source) { model.updateEdgeSource(id, modifiedEdge.source); } if (modifiedEdge.target && originalEdge.target !== modifiedEdge.target) { model.updateEdgeTarget(id, modifiedEdge.target); } const updatedData = mergeElementsData(originalEdge, modifiedEdge); this.pushChange({ value: updatedData, original: originalEdge, type: ChangeType.EdgeUpdated }); model.mergeEdgeData(id, updatedData); }); }); this.computeZIndex({ edges }, 'update'); } updateComboData(combos = []) { if (!combos.length) return; const { model } = this; model.batch(() => { const modifiedCombos = []; combos.forEach((modifiedCombo) => { const id = idOf(modifiedCombo); const originalCombo = toG6Data(model.getNode(id)); if (isElementDataEqual(originalCombo, modifiedCombo)) return; const value = mergeElementsData(originalCombo, modifiedCombo); this.pushChange({ value, original: originalCombo, type: ChangeType.ComboUpdated }); model.mergeNodeData(id, value); modifiedCombos.push(value); }); this.updateNodeLikeHierarchy(modifiedCombos); }); this.computeZIndex({ combos }, 'update'); } /** * <zh/> 设置节点的父节点 * * <en/> Set the parent node of the node * @param id - <zh/> 节点 ID | <en/> node ID * @param parent - <zh/> 父节点 ID | <en/> parent node ID * @param hierarchyKey - <zh/> 层次结构类型 | <en/> hierarchy type * @param update - <zh/> 添加新/旧父节点数据更新记录 | <en/> add new/old parent node data update record */ setParent(id, parent, hierarchyKey, update = true) { if (id === parent) return; const elementData = this.getNodeLikeDatum(id); const originalParentId = parentIdOf(elementData); if (originalParentId !== parent && hierarchyKey === COMBO_KEY) { const modifiedDatum = { id, combo: parent }; if (this.isCombo(id)) this.syncNodeLikeDatum(modifiedDatum); else this.syncNodeLikeDatum(modifiedDatum); } this.model.setParent(id, parent, hierarchyKey); if (update && hierarchyKey === COMBO_KEY) { uniq([originalParentId, parent]).forEach((pId) => { if (pId !== undefined) this.refreshComboData(pId); }); } } /** * <zh/> 刷新 combo 数据 * * <en/> Refresh combo data * @param id - <zh/> combo ID | <en/> combo ID * @remarks * <zh/> 不会更改数据,但会触发数据变更事件 * * <en/> Will not change the data, but will trigger data change events */ refreshComboData(id) { const combo = this.getComboData([id])[0]; const ancestors = this.getAncestorsData(id, COMBO_KEY); if (combo) this.pushChange({ value: combo, original: combo, type: ChangeType.ComboUpdated }); ancestors.forEach((value) => { this.pushChange({ value: value, original: value, type: ChangeType.ComboUpdated }); }); } getElementPosition(id) { const datum = this.getElementDataById(id); return positionOf(datum); } translateNodeLikeBy(id, offset) { if (this.isCombo(id)) this.translateComboBy(id, offset); else this.translateNodeBy(id, offset); } translateNodeLikeTo(id, position) { if (this.isCombo(id)) this.translateComboTo(id, position); else this.translateNodeTo(id, position); } translateNodeBy(id, offset) { const curr = this.getElementPosition(id); const position = add(curr, [...offset, 0].slice(0, 3)); this.translateNodeTo(id, position); } translateNodeTo(id, position) { const [x = 0, y = 0, z = 0] = position; this.preventUpdateNodeLikeHierarchy(() => { this.updateNodeData([{ id, style: { x, y, z } }]); }); } translateComboBy(id, offset) { const [dx = 0, dy = 0, dz = 0] = offset; if ([dx, dy, dz].some(isNaN) || [dx, dy, dz].every((o) => o === 0)) return; const combo = this.getComboData([id])[0]; if (!combo) return; const seenNodeLikeIds = new Set(); dfs(combo, (succeed) => { const succeedID = idOf(succeed); if (seenNodeLikeIds.has(succeedID)) return; seenNodeLikeIds.add(succeedID); const [x, y, z] = positionOf(succeed); const value = mergeElementsData(succeed, { style: { x: x + dx, y: y + dy, z: z + dz }, }); this.pushChange({ value, // @ts-ignore original: succeed, type: this.isCombo(succeedID) ? ChangeType.ComboUpdated : ChangeType.NodeUpdated, }); this.model.mergeNodeData(succeedID, value); }, (node) => this.getChildrenData(idOf(node)), 'BT'); } translateComboTo(id, position) { var _a; if (position.some(isNaN)) return; const [tx = 0, ty = 0, tz = 0] = position; const combo = (_a = this.getComboData([id])) === null || _a === void 0 ? void 0 : _a[0]; if (!combo) return; const [comboX, comboY, comboZ] = positionOf(combo); const dx = tx - comboX; const dy = ty - comboY; const dz = tz - comboZ; dfs(combo, (succeed) => { const succeedId = idOf(succeed); const [x, y, z] = positionOf(succeed); const value = mergeElementsData(succeed, { style: { x: x + dx, y: y + dy, z: z + dz }, }); this.pushChange({ value, // @ts-ignore original: succeed, type: this.isCombo(succeedId) ? ChangeType.ComboUpdated : ChangeType.NodeUpdated, }); this.model.mergeNodeData(succeedId, value); }, (node) => this.getChildrenData(idOf(node)), 'BT'); } removeData(data) { const { nodes, edges, combos } = data; this.batch(() => { // remove edges first this.removeEdgeData(edges); this.removeNodeData(nodes); this.removeComboData(combos); this.latestRemovedComboIds = new Set(combos); }); } removeNodeData(ids = []) { if (!ids.length) return; this.batch(() => { ids.forEach((id) => { // 移除关联边、子节点 // remove related edges and child nodes this.removeEdgeData(this.getRelatedEdgesData(id).map(idOf)); // TODO 树图情况下移除子节点 this.pushChange({ value: this.getNodeData([id])[0], type: ChangeType.NodeRemoved }); this.removeNodeLikeHierarchy(id); }); this.model.removeNodes(ids); }); } removeEdgeData(ids = []) { if (!ids.length) return; ids.forEach((id) => this.pushChange({ value: this.getEdgeData([id])[0], type: ChangeType.EdgeRemoved })); this.model.removeEdges(ids); } removeComboData(ids = []) { if (!ids.length) return; this.batch(() => { ids.forEach((id) => { this.pushChange({ value: this.getComboData([id])[0], type: ChangeType.ComboRemoved }); this.removeNodeLikeHierarchy(id); this.comboIds.delete(id); }); this.model.removeNodes(ids); }); } /** * <zh/> 移除节点层次结构,将其子节点移动到父节点的 children 列表中 * * <en/> Remove the node hierarchy and move its child nodes to the parent node's children list * @param id - <zh/> 待处理的节点 | <en/> node to be processed */ removeNodeLikeHierarchy(id) { if (this.model.hasTreeStructure(COMBO_KEY)) { const grandParent = parentIdOf(this.getNodeLikeDatum(id)); // 从父节点的 children 列表中移除 // remove from its parent's children list // 调用 graphlib.setParent,不需要更新数据 this.setParent(id, undefined, COMBO_KEY, false); // 将子节点移动到父节点的 children 列表中 // move the children to the grandparent's children list this.model.getChildren(id, COMBO_KEY).forEach((child) => { const childData = toG6Data(child); const childId = idOf(childData); this.setParent(idOf(childData), grandParent, COMBO_KEY, false); const value = mergeElementsData(childData, { id: idOf(childData), combo: grandParent, }); this.pushChange({ value, original: childData, type: this.isCombo(childId) ? ChangeType.ComboUpdated : ChangeType.NodeUpdated, }); this.model.mergeNodeData(idOf(childData), value); }); if (!isNil(grandParent)) this.refreshComboData(grandParent); } } /** * <zh/> 获取元素的类型 * * <en/> Get the type of the element * @param id - <zh/> 元素 ID | <en/> ID of the element * @returns <zh/> 元素类型 | <en/> type of the element */ getElementType(id) { if (this.model.hasNode(id)) { if (this.isCombo(id)) return 'combo'; return 'node'; } if (this.model.hasEdge(id)) return 'edge'; throw new Error(format(`Unknown element type of id: ${id}`)); } destroy() { const { model } = this; const nodes = model.getAllNodes(); const edges = model.getAllEdges(); model.removeEdges(edges.map((edge) => edge.id)); model.removeNodes(nodes.map((node) => node.id)); // @ts-expect-error force delete this.context = {}; } } //# sourceMappingURL=data.js.map