import type { EdgeData, ID, NodeData } from '../../types'; /** * 图中的节点 * * Node in the graph */ export interface GraphNode { id: ID; data: N; } /** * 图中的边 * * Edge in the graph */ export interface GraphEdge { id: ID; source: ID; target: ID; data: E; } /** * 图数据结构配置 * * Graph data structure configuration */ export interface GraphOptions< N extends NodeData = NodeData, E extends EdgeData = EdgeData, > { tree?: string[] | (GraphNode & { children?: GraphNode[] })[]; nodes?: GraphNode[]; edges?: GraphEdge[]; } /** * 内部图数据结构,用于 antv-dagre 布局算法 * * Internal graph data structure for antv-dagre layout algorithm */ export class DagreGraph< N extends NodeData = NodeData, E extends EdgeData = EdgeData, > { private nodes: Map> = new Map(); private edges: Map> = new Map(); private inEdges: Map> = new Map(); private outEdges: Map> = new Map(); private parentMap: Map> = new Map(); // tree structure name -> node -> parent private childrenMap: Map>> = new Map(); // tree structure name -> node -> children constructor(private options: GraphOptions = {}) { // Initialize tree structures if (options.tree) { if (Array.isArray(options.tree) && options.tree.length > 0) { // Check if it's a tree name array or tree data array if (typeof options.tree[0] === 'string') { // It's a tree name array (options.tree as string[]).forEach((treeName) => { this.parentMap.set(treeName, new Map()); this.childrenMap.set(treeName, new Map()); }); } else { // It's tree data, add it this.attachTreeStructure('default'); this.addTree(options.tree as any); } } } // Add initial nodes and edges if provided if (options.nodes) { options.nodes.forEach((node) => this.addNode(node)); } if (options.edges) { options.edges.forEach((edge) => this.addEdge(edge)); } } /** * 添加节点 * * Add a node */ addNode(node: GraphNode): void { if (!this.nodes.has(node.id)) { this.nodes.set(node.id, node); this.inEdges.set(node.id, new Set()); this.outEdges.set(node.id, new Set()); } } /** * 批量添加节点 * * Add multiple nodes */ addNodes(nodes: GraphNode[]): void { nodes.forEach((node) => this.addNode(node)); } /** * 获取节点 * * Get a node */ getNode(id: ID): GraphNode { return this.nodes.get(id)!; } /** * 检查节点是否存在 * * Check if a node exists */ hasNode(id: ID): boolean { return this.nodes.has(id); } /** * 删除节点 * * Remove a node */ removeNode(id: ID): void { if (!this.nodes.has(id)) return; // Remove all edges connected to this node const inEdgeIds = Array.from(this.inEdges.get(id) || []); const outEdgeIds = Array.from(this.outEdges.get(id) || []); inEdgeIds.forEach((edgeId) => this.removeEdge(edgeId)); outEdgeIds.forEach((edgeId) => this.removeEdge(edgeId)); this.nodes.delete(id); this.inEdges.delete(id); this.outEdges.delete(id); // Remove from tree structures this.parentMap.forEach((treeParentMap) => { treeParentMap.delete(id); }); this.childrenMap.forEach((treeChildrenMap) => { treeChildrenMap.delete(id); }); } /** * 获取所有节点 * * Get all nodes */ getAllNodes(): GraphNode[] { return Array.from(this.nodes.values()); } /** * 添加边 * * Add an edge */ addEdge(edge: GraphEdge): void { if (!this.nodes.has(edge.source) || !this.nodes.has(edge.target)) { throw new Error( `Cannot add edge ${edge.id}: source ${edge.source} or target ${edge.target} does not exist`, ); } this.edges.set(edge.id, edge); this.outEdges.get(edge.source)!.add(edge.id); this.inEdges.get(edge.target)!.add(edge.id); } /** * 批量添加边 * * Add multiple edges */ addEdges(edges: GraphEdge[]): void { edges.forEach((edge) => this.addEdge(edge)); } /** * 获取边 * * Get an edge */ getEdge(id: ID): GraphEdge { return this.edges.get(id)!; } /** * 检查边是否存在 * * Check if an edge exists */ hasEdge(id: ID): boolean { return this.edges.has(id); } /** * 删除边 * * Remove an edge */ removeEdge(id: ID): void { const edge = this.edges.get(id); if (!edge) return; this.edges.delete(id); this.outEdges.get(edge.source)?.delete(id); this.inEdges.get(edge.target)?.delete(id); } /** * 获取所有边 * * Get all edges */ getAllEdges(): GraphEdge[] { return Array.from(this.edges.values()); } /** * 更新边数据 * * Update edge data */ updateEdgeData(id: ID, data: Partial): void { const edge = this.edges.get(id); if (edge) { Object.assign(edge.data, data); } } /** * 更新节点数据 * * Update node data */ updateNodeData(id: ID, data: Partial): void { const node = this.nodes.get(id); if (node) { Object.assign(node.data, data); } } /** * 获取相关边 * * Get related edges */ getRelatedEdges( nodeId: ID, direction: 'in' | 'out' | 'both' = 'both', ): GraphEdge[] { const result: GraphEdge[] = []; if (direction === 'in' || direction === 'both') { const inEdgeIds = this.inEdges.get(nodeId); if (inEdgeIds) { inEdgeIds.forEach((edgeId) => { const edge = this.edges.get(edgeId); if (edge) result.push(edge); }); } } if (direction === 'out' || direction === 'both') { const outEdgeIds = this.outEdges.get(nodeId); if (outEdgeIds) { outEdgeIds.forEach((edgeId) => { const edge = this.edges.get(edgeId); if (edge) result.push(edge); }); } } return result; } /** * 获取后继节点 * * Get successor nodes */ getSuccessors(nodeId: ID): GraphNode[] { const outEdgeIds = this.outEdges.get(nodeId); if (!outEdgeIds || outEdgeIds.size === 0) return []; const successors: GraphNode[] = []; outEdgeIds.forEach((edgeId) => { const edge = this.edges.get(edgeId); if (edge) { const targetNode = this.nodes.get(edge.target); if (targetNode) successors.push(targetNode); } }); return successors.length > 0 ? successors : []; } /** * 获取前驱节点 * * Get predecessor nodes */ getPredecessors(nodeId: ID): GraphNode[] { const inEdgeIds = this.inEdges.get(nodeId); if (!inEdgeIds || inEdgeIds.size === 0) return []; const predecessors: GraphNode[] = []; inEdgeIds.forEach((edgeId) => { const edge = this.edges.get(edgeId); if (edge) { const sourceNode = this.nodes.get(edge.source); if (sourceNode) predecessors.push(sourceNode); } }); return predecessors.length > 0 ? predecessors : []; } /** * 获取邻居节点 * * Get neighbor nodes */ getNeighbors(nodeId: ID): GraphNode[] { const successors = this.getSuccessors(nodeId) || []; const predecessors = this.getPredecessors(nodeId) || []; const neighbors = [...successors, ...predecessors]; // Remove duplicates const uniqueNeighbors = Array.from( new Map(neighbors.map((n) => [n.id, n])).values(), ); return uniqueNeighbors.length > 0 ? uniqueNeighbors : []; } /** * 附加树结构 * * Attach tree structure */ attachTreeStructure(treeName: string): void { if (!this.parentMap.has(treeName)) { this.parentMap.set(treeName, new Map()); this.childrenMap.set(treeName, new Map()); } } /** * 添加树结构(递归添加节点及其子节点) * * Add tree structure (recursively add nodes and their children) */ addTree( tree: | (GraphNode & { children?: GraphNode[] }) | (GraphNode & { children?: GraphNode[] })[], treeName?: string, ): void { const actualTreeName = treeName || ((this.options.tree?.[0] ?? 'default') as string); if (!this.hasTreeStructure(actualTreeName)) { this.attachTreeStructure(actualTreeName); } const trees = Array.isArray(tree) ? tree : [tree]; const addTreeNode = ( node: GraphNode & { children?: GraphNode[] }, parentId?: ID, ) => { // Add the node itself this.addNode({ id: node.id, data: node.data }); // Set parent relationship if parent exists if (parentId !== undefined) { this.setParent(node.id, parentId, actualTreeName); } // Recursively add children if (node.children && node.children.length > 0) { node.children.forEach((child) => { addTreeNode(child as any, node.id); }); } }; trees.forEach((t) => addTreeNode(t)); } /** * 检查是否有树结构 * * Check if has tree structure */ hasTreeStructure(treeName: string): boolean { return this.parentMap.has(treeName); } /** * 设置父节点 * * Set parent node */ setParent(childId: ID, parentId: ID, treeName?: string): void { const actualTreeName = treeName || ((this.options.tree?.[0] ?? 'default') as string); if (!this.parentMap.has(actualTreeName)) { this.attachTreeStructure(actualTreeName); } const treeParentMap = this.parentMap.get(actualTreeName)!; const treeChildrenMap = this.childrenMap.get(actualTreeName)!; // Remove from old parent const oldParent = treeParentMap.get(childId); if (oldParent !== undefined) { treeChildrenMap.get(oldParent)?.delete(childId); } // Set new parent treeParentMap.set(childId, parentId); // Add to new parent's children if (!treeChildrenMap.has(parentId)) { treeChildrenMap.set(parentId, new Set()); } treeChildrenMap.get(parentId)!.add(childId); } /** * 获取父节点 * * Get parent node */ getParent(nodeId: ID, treeName?: string): GraphNode | null | undefined { const actualTreeName = treeName || ((this.options.tree?.[0] ?? 'default') as string); // Ensure tree structure exists if (!this.parentMap.has(actualTreeName)) { this.attachTreeStructure(actualTreeName); } const treeParentMap = this.parentMap.get(actualTreeName); if (!treeParentMap) return undefined; const parentId = treeParentMap.get(nodeId); if (parentId === undefined) { // Node has no parent set return null; } return this.nodes.get(parentId); } /** * 获取子节点 * * Get children nodes */ getChildren(nodeId: ID, treeName?: string): GraphNode[] { const actualTreeName = treeName || ((this.options.tree?.[0] ?? 'default') as string); const treeChildrenMap = this.childrenMap.get(actualTreeName); if (!treeChildrenMap) return []; const childIds = treeChildrenMap.get(nodeId); if (!childIds) return []; return Array.from(childIds) .map((id) => this.nodes.get(id)) .filter((node): node is GraphNode => node !== undefined); } /** * 获取根节点(没有父节点的节点) * * Get root nodes (nodes without parents) */ getRoots(treeName?: string): GraphNode[] { const actualTreeName = treeName || ((this.options.tree?.[0] ?? 'default') as string); const treeParentMap = this.parentMap.get(actualTreeName); const roots: GraphNode[] = []; this.nodes.forEach((node) => { const hasParent = treeParentMap && treeParentMap.get(node.id) !== undefined; if (!hasParent) { roots.push(node); } }); return roots; } dfsTree(startId: ID, visit: (node: GraphNode) => boolean | void) { const stack: ID[] = [startId]; const visited: Set = new Set(); while (stack.length > 0) { const nodeId = stack.pop()!; if (visited.has(nodeId)) continue; const node = this.getNode(nodeId); if (node) { visited.add(nodeId); const shouldSkipChildren = visit(node); if (shouldSkipChildren === true) continue; const children = this.getChildren(nodeId); // Push children in reverse order so they are processed in the correct order for (let i = children.length - 1; i >= 0; i--) { if (!visited.has(children[i].id)) { stack.push(children[i].id); } } } } } }