devexpress-diagram
Version:
DevExpress Diagram Control
220 lines (204 loc) • 9.81 kB
text/typescript
import { Edge, ConnectionMode } from "./Structures";
import { ItemKey } from "../Model/DiagramItem";
import { Shape } from "../Model/Shapes/Shape";
import { Connector } from "../Model/Connectors/Connector";
import { IKeyOwner } from "../Interfaces";
import { SearchUtils } from "@devexpress/utils/lib/utils/search";
import { HashSet, KeySet } from "../ListUtils";
export interface IEdge extends IKeyOwner {
from: ItemKey;
to: ItemKey;
}
abstract class GraphBase<TNode extends IKeyOwner, TEdge extends IEdge> {
private nodeMap: { [key: string]: TNode } = {};
private edgeMap: { [key: string]: TEdge } = {};
nodes: ItemKey[] = [];
get items(): TNode[] {
return this.nodes.map(this.getNode.bind(this));
}
edges: TEdge[] = [];
constructor(nodes: TNode[], edges: TEdge[]) {
this.onInit();
nodes.forEach(this.addNode.bind(this));
edges.forEach(this.addEdge.bind(this));
}
protected onInit() {}
addEdge(edge: TEdge) {
this.edgeMap[edge.key] = edge;
this.edges.push(edge);
}
addNode(node: TNode) {
this.nodeMap[node.key] = node;
this.nodes.push(node.key);
}
getNode(key: ItemKey): TNode {
return this.nodeMap[key];
}
getEdge(key: ItemKey): TEdge {
return this.edgeMap[key];
}
isEmpty(): boolean {
return !this.nodes.length && !this.edges.length;
}
getAdjacentEdges(nodeKey: ItemKey, connectionMode: ConnectionMode = ConnectionMode.OutgoingAndIncoming): TEdge[] {
return this.edges.filter(e =>
connectionMode & ConnectionMode.Incoming && e.to === nodeKey ||
connectionMode & ConnectionMode.Outgoing && e.from === nodeKey
);
}
abstract createIterator(connectionMode: ConnectionMode): GraphIterator<TNode, TEdge>;
}
export class Graph<T extends IKeyOwner> extends GraphBase<T, Edge> {
cast<TNew extends IKeyOwner>(castNode: (node: T) => TNew, castEdge?: (edge: Edge) => Edge): Graph<TNew> {
const newNodes = this.nodes.map(nk => castNode(this.getNode(nk)));
const newEdges = this.edges.map(e => castEdge ? castEdge(e) : e);
return new Graph<TNew>(newNodes, newEdges);
}
getConnectedComponents(): Graph<T>[] {
const iterator = this.createIterator(ConnectionMode.OutgoingAndIncoming);
iterator.visitEachEdgeOnce = true;
const components: Graph<T>[] = [];
for(let i = 0; i < this.nodes.length; i++) {
const nodes: T[] = [];
const edges: Edge[] = [];
iterator.onNode = n => nodes.push(n);
iterator.onEdge = e => edges.push(e);
iterator.iterate(this.nodes[i]);
if(nodes.length)
components.push(new Graph<T>(nodes, edges));
}
return components;
}
createIterator(connectionMode: ConnectionMode): GraphIterator<T, Edge> {
const iterator = new GraphIterator(this, connectionMode);
iterator.comparer = (a, b) => a.weight - b.weight;
return iterator;
}
getSpanningGraph(rootKey: ItemKey, connectionMode: ConnectionMode, edgeWeightFunc: (e: Edge) => number = undefined): Graph<T> {
if(!this.nodes.length)
return new Graph<T>([], []);
if(!edgeWeightFunc)
edgeWeightFunc = (e) => e.weight;
let sortedAdjacentEdges: Edge[] = [];
const spanningTreeNodesSet = new HashSet<ItemKey>();
const spanningTreeEdgesSet = new HashSet<Edge>([], e => e.getHashKey());
this.addNodeToSpanningGraph(rootKey, connectionMode, sortedAdjacentEdges, spanningTreeNodesSet, spanningTreeEdgesSet, edgeWeightFunc);
while(sortedAdjacentEdges.length && spanningTreeNodesSet.length !== this.nodes.length) {
const minWeighedEdge = sortedAdjacentEdges.shift();
spanningTreeEdgesSet.tryPush(minWeighedEdge);
const node = spanningTreeNodesSet.contains(minWeighedEdge.from) ? minWeighedEdge.to : minWeighedEdge.from;
this.addNodeToSpanningGraph(node, connectionMode, sortedAdjacentEdges, spanningTreeNodesSet, spanningTreeEdgesSet, edgeWeightFunc);
sortedAdjacentEdges = sortedAdjacentEdges.filter(e => !spanningTreeNodesSet.contains(e.from) || !spanningTreeNodesSet.contains(e.to));
}
return new Graph<T>(
spanningTreeNodesSet.list().map(nk => this.getNode(nk)),
spanningTreeEdgesSet.list()
);
}
private addNodeToSpanningGraph(nodeKey: ItemKey, connectionMode: ConnectionMode, adjacentEdges: Edge[],
spanningTreeNodesSet: HashSet<ItemKey>, spanningTreeEdgesSet: HashSet<Edge>, edgeWeightFunc: (e: Edge) => number) {
spanningTreeNodesSet.tryPush(nodeKey);
this.getAdjacentEdges(nodeKey, connectionMode)
.filter(e => !spanningTreeEdgesSet.contains(e))
.forEach(e => {
const weight = edgeWeightFunc(e);
let pos = SearchUtils.binaryIndexOf(adjacentEdges, a => a.weight - weight);
pos = pos < 0 ? ~pos : pos;
while(pos < adjacentEdges.length && edgeWeightFunc(adjacentEdges[pos]) === weight)
pos++;
adjacentEdges.splice(pos, 0, new Edge(e.key, e.from, e.to, weight));
});
}
static create(shapes: Shape[], connectors: Connector[]) {
const nodes = shapes;
const edges = connectors
.filter(i => i.beginItem && i.endItem instanceof Shape && i.endItem && i.endItem instanceof Shape && i.beginItem !== i.endItem)
.map(i => new Edge(i.key, i.beginItem && i.beginItem.key, i.endItem && i.endItem.key));
return new Graph<Shape>(nodes, edges);
}
}
export class FastGraph<TNode extends IKeyOwner, TEdge extends IEdge = IEdge> extends GraphBase<TNode, TEdge> {
private parentToChildren: {[parentKey: string]: ItemKey[]};
private childToParents: {[childKey: string]: ItemKey[]};
protected onInit() {
this.parentToChildren = {};
this.childToParents = {};
}
addEdge(edge: TEdge) {
super.addEdge(edge);
(this.parentToChildren[edge.from] || (this.parentToChildren[edge.from] = [])).push(edge.to);
(this.childToParents[edge.to] || (this.childToParents[edge.to] = [])).push(edge.from);
}
getChildren(parent: ItemKey): ItemKey[] {
return this.parentToChildren[parent] || [];
}
getParents(child: ItemKey): ItemKey[] {
return this.childToParents[child] || [];
}
createIterator(connectionMode: ConnectionMode): GraphIterator<TNode, TEdge> {
return new GraphIterator<TNode, TEdge>(this, connectionMode);
}
}
export class GraphIterator<TNode extends IKeyOwner, TEdge extends IEdge> {
onNode: (node: TNode) => void;
skipNode: (node: TNode) => boolean;
skipEdge: (edge: TEdge) => boolean;
onEdge: (edge: TEdge, outgoing: boolean) => void;
onAfterEdge: (edge: TEdge, outgoing: boolean) => void;
onAllEdges: (node: TNode, outgoing: boolean) => void;
comparer: (a: TEdge, b: TEdge) => number;
visitEachEdgeOnce: boolean = true;
visitEachNodeOnce: boolean = true;
private visitedNodes: KeySet = {};
private visitedEdges: KeySet = {};
constructor(private graph: GraphBase<TNode, TEdge>, private connectionMode: ConnectionMode = ConnectionMode.OutgoingAndIncoming) { }
iterate(nodeKey: ItemKey) {
if(!this.visitEachNodeOnce && !this.visitEachEdgeOnce && !this.skipNode)
throw "skipNode or visitEachNodeOnce or visitEachEdgeOnce must be set to avoid SOF";
this.iterateCore(nodeKey);
}
private iterateCore(nodeKey: ItemKey) {
const node = this.graph.getNode(nodeKey);
if(!node || (this.skipNode && this.skipNode(node)) || (this.visitEachNodeOnce && this.isNodeVisited(nodeKey)))
return;
this.visitedNodes[nodeKey] = true;
this.onNode && this.onNode(node);
let edges = this.graph.getAdjacentEdges(nodeKey, this.connectionMode);
if(this.skipEdge)
edges = edges.filter(e => !this.skipEdge(e));
if(this.connectionMode & ConnectionMode.Outgoing) {
const outgoing = edges.filter(e => e.from === nodeKey);
if(this.comparer)
outgoing.sort(this.comparer);
outgoing.forEach(e => {
if(this.visitEachEdgeOnce && this.visitedEdges[e.key])
return;
this.visitedEdges[e.key] = true;
this.onEdge && this.onEdge(e, true);
this.iterateCore(e.to);
this.onAfterEdge && this.onAfterEdge(e, true);
});
}
this.onAllEdges && this.onAllEdges(node, true);
if(this.connectionMode & ConnectionMode.Incoming) {
const incoming = edges.filter(e => e.to === nodeKey);
if(this.comparer)
incoming.sort(this.comparer);
incoming.forEach(e => {
if(this.visitEachEdgeOnce && this.visitedEdges[e.key])
return;
this.visitedEdges[e.key] = true;
this.onEdge && this.onEdge(e, false);
this.iterateCore(e.from);
this.onAfterEdge && this.onAfterEdge(e, false);
});
}
this.onAllEdges && this.onAllEdges(node, false);
}
isNodeVisited(nodeKey: ItemKey): boolean {
return !!this.visitedNodes[nodeKey];
}
isEdgeVisited(edgeKey: ItemKey): boolean {
return !!this.visitedEdges[edgeKey];
}
}