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);
}
}
}
}
}
}