export class GraphItem { protected _graph: Graph; readonly parent: Subgraph | null; readonly props: { [key: string]: any } = {}; constructor(graph: Graph, parent: Subgraph | null) { this._graph = graph; this.parent = parent; } } export class Subgraph extends GraphItem { readonly subgraphs: Array> = []; readonly vertices: Array> = []; readonly edges: Array> = []; readonly _?: S; constructor(graph: Graph, parent: Subgraph | null, _?: S) { super(graph, parent); if (parent) { // Only needed for dummy root parent._addSubgraph(this); } this._ = _; } remove(full: boolean = true): void { this._graph.removeSubgraph(this, full); } createSubgraph(_?: S): Subgraph { return this._graph.createSubgraph(this, _); } _addSubgraph(subgraph: Subgraph) { if (this.subgraphs.indexOf(subgraph) >= 0) { throw new Error("Subgraph already exists"); } this.subgraphs.push(subgraph); } _removeSubgraph(subgraph: Subgraph) { const idx = this.subgraphs.indexOf(subgraph); if (idx < 0) { throw new Error("Subgraph does not exist"); } this.subgraphs.splice(idx, 1); } removeAllSubgraphs() { for (let i = this.subgraphs.length - 1; i >= 0; --i) { this._graph.removeSubgraph(this.subgraphs[i], true); } } createVertex(_?: V): Vertex { return this._graph.createVertex(this, _); } _addVertex(vertex: Vertex) { if (this.vertices.indexOf(vertex) >= 0) { throw new Error("Vertex already exists"); } this.vertices.push(vertex); } _removeVertex(vertex: Vertex) { const idx = this.vertices.indexOf(vertex); if (idx < 0) { throw new Error("Vertex does not exist"); } this.vertices.splice(idx, 1); } removeAllVertices() { for (let i = this.vertices.length - 1; i >= 0; --i) { this._graph.removeVertex(this.vertices[i], true); } } createEdge(source: Vertex, target: Vertex, _?: E): Edge { return this._graph.createEdge(this, source, target, _); } _addEdge(edge: Edge) { if (this.edges.indexOf(edge) >= 0) { throw new Error("Edge already exists"); } this.edges.push(edge); } _removeEdge(edge: Edge) { const idx = this.edges.indexOf(edge); if (idx < 0) { throw new Error("Edge does not exist"); } this.edges.splice(idx, 1); } _add(item: Subgraph | Vertex | Edge) { if (item instanceof Subgraph) { this._addSubgraph(item); } else if (item instanceof Vertex) { this._addVertex(item); } else { this._addEdge(item); } } } export class Vertex extends GraphItem { readonly inEdges: Array> = []; readonly outEdges: Array> = []; get edges(): ReadonlyArray> { return [...this.inEdges, ...this.outEdges]; } readonly _?: V; constructor(graph: Graph, parent: Subgraph, _?: V) { super(graph, parent); parent._addVertex(this); this._ = _; } remove(full: boolean = true, _?: (source: V, target: V) => E) { return this._graph.removeVertex(this, full, _); } addInEdge(edge: Edge) { this.inEdges.push(edge); } removeInEdge(edge: Edge) { const idx = this.inEdges.indexOf(edge); if (idx < 0) { throw new Error("In edge does not exist"); } this.inEdges.splice(idx, 1); } addOutEdge(edge: Edge) { this.outEdges.push(edge); } removeOutEdge(edge: Edge) { const idx = this.outEdges.indexOf(edge); if (idx < 0) { throw new Error("Out edge does not exist"); } this.outEdges.splice(idx, 1); } } export class Edge extends GraphItem { readonly source: Vertex; readonly target: Vertex; readonly _?: E; constructor(graph: Graph, parent: Subgraph, source: Vertex, target: Vertex, _?: E) { super(graph, parent); if (!source) { throw new Error("Missing source vertex"); } if (!target) { throw new Error("Missing target vertex"); } parent._addEdge(this); this.source = source; this.source.addOutEdge(this); this.target = target; this.target.addInEdge(this); this._ = _; } remove(): void { this._graph.removeEdge(this); } } export class Graph { readonly root: Subgraph; private _allSubgraphs: Array> = []; private _allSubgraphsMap: { [id: string]: Subgraph } = {}; private _allVertices: Array> = []; private _allVerticesMap: { [id: string]: Vertex } = {}; private _allEdges: Array> = []; private _allEdgesMap: { [id: string]: Edge } = {}; idOf: (item: Subgraph | Vertex | Edge) => string; constructor(idOf: (item: Subgraph | Vertex | Edge) => string = item => "" + item._, _?: S) { this.root = new Subgraph(this, null, _); this.idOf = idOf; } createSubgraph(parent?: Subgraph, _?: S): Subgraph { const retVal = new Subgraph(this, parent || this.root, _); this._allSubgraphs.push(retVal); this._allSubgraphsMap[this.idOf(retVal)] = retVal; return retVal; } removeSubgraph(subgraph: Subgraph, full: boolean = true) { const idx = this._allSubgraphs.indexOf(subgraph); if (idx < 0) { throw new Error("Subgraph does not exist"); } this._allSubgraphs.splice(idx, 1); delete this._allSubgraphsMap[this.idOf(subgraph)]; if (subgraph.parent) { subgraph.parent._removeSubgraph(subgraph); } subgraph.edges.forEach(edge => full ? this.removeEdge(edge) : subgraph.parent!._addEdge(edge)); subgraph.vertices.forEach(vertex => full ? this.removeVertex(vertex, full) : subgraph.parent!._addVertex(vertex)); subgraph.subgraphs.forEach(childSubgraph => full ? this.removeSubgraph(childSubgraph, full) : subgraph.parent!._addSubgraph(childSubgraph) ); } get subgraphs(): ReadonlyArray> { return this._allSubgraphs; } subgraph(id: string): Subgraph { return this._allSubgraphsMap[id]; } createVertex(parent: Subgraph, _?: V): Vertex { const retVal = new Vertex(this, parent, _); this._allVertices.push(retVal); this._allVerticesMap[this.idOf(retVal)] = retVal; return retVal; } removeVertex(vertex: Vertex, full: boolean = true, _?: (source: V, target: V) => E) { const idx = this._allVertices.indexOf(vertex); if (idx < 0) { throw new Error("Vertex does not exist"); } this._allVertices.splice(idx, 1); delete this._allVerticesMap[this.idOf(vertex)]; if (vertex.parent) { vertex.parent._removeVertex(vertex); } if (!full) { vertex.inEdges.forEach(inEdge => { vertex.outEdges.forEach(outEdge => { this.createEdge(this.root, inEdge.source, outEdge.target, _ ? _(inEdge.source._!, outEdge.target._!) : undefined); }); }); } vertex.inEdges.forEach(edge => this.removeEdge(edge)); vertex.outEdges.forEach(edge => this.removeEdge(edge)); } get vertices(): ReadonlyArray> { return this._allVertices; } vertex(id: string): Vertex { return this._allVerticesMap[id]; } createEdge(parent: Subgraph, source: Vertex, target: Vertex, _?: E): Edge { const retVal = new Edge(this, parent, source, target, _); this._allEdges.push(retVal); this._allEdgesMap[this.idOf(retVal)] = retVal; return retVal; } removeEdge(edge: Edge) { const idx = this._allEdges.indexOf(edge); if (idx < 0) { throw new Error("Edge does not exist"); } this._allEdges.splice(idx, 1); delete this._allEdgesMap[this.idOf(edge)]; if (edge.parent) { edge.parent._removeEdge(edge); } edge.source.removeOutEdge(edge); edge.target.removeInEdge(edge); } get edges(): ReadonlyArray> { return this._allEdges; } edge(id: string): Edge { return this._allEdgesMap[id]; } private _walk(parent: Subgraph, visitor: (item: GraphItem) => "abort" | "stepover" | void): true | false | void { for (const subgraph of parent.subgraphs) { switch (visitor(subgraph)) { case "abort": return true; case "stepover": break; default: if (this._walk(subgraph, visitor)) return true; } } for (const vertex of parent.vertices) { if (visitor(vertex) === "abort") return true; } } walk(visitor: (visitor: GraphItem) => "abort" | "stepover" | void) { this._walk(this.root, visitor); for (const edge of this._allEdges) { if (visitor(edge) === "abort") return true; } } clone(): Graph { const ctor: new (idOf: (item: Subgraph | Vertex | Edge) => string, _?: S) => Graph = this.constructor as any; const retVal = new ctor(this.idOf, this.root._); const map = ObjMap(); map.put(this.root, retVal.root); this.walk(item => { const parent = map.get(item.parent); if (item instanceof Subgraph) { map.put(item, parent.createSubgraph(item._)); } else if (item instanceof Vertex) { map.put(item, parent.createVertex(item._)); } else if (item instanceof Edge) { const source = map.get(item.source); const target = map.get(item.target); parent.createEdge(source, target, item._); } }); return retVal; } } function ObjMap() { const keys: any[] = []; const values: any[] = []; return { put(key: any, value: any) { const index = keys.indexOf(key); if (index === -1) { keys.push(key); values.push(value); } else { values[index] = value; } }, get(key: any) { return values[keys.indexOf(key)]; } }; }