Procházet zdrojové kódy

revert changes + styling

Pavel Zhigalov před 1 měsícem
rodič
revize
887be80c3e

+ 2 - 0
src/index.ts

@@ -14,6 +14,7 @@ import NodeOrderingStep from "./v1/optimizer/steps/NodeOrderingStep.js";
 import SiguiyamaAlgorithm from "./v1/optimizer/siguiyama/SiguiyamaAlgorithm.js";
 import { SiguiyamaContext } from "./v1/optimizer/siguiyama/SiguiyamaContext.js";
 import CleanupStep from "./v1/optimizer/steps/CleanupStep.js";
+import EdgeRoutingStep from "./v1/optimizer/steps/EdgeRoutingStep.js";
 
 export class BPMNOptimizer {
 	private _optimizationAlgorithm?: SiguiyamaAlgorithm;
@@ -43,6 +44,7 @@ const algorithm = new SiguiyamaAlgorithm()
 	.addStep(new LayerAssignmentStep())
 	.addStep(new NodeOrderingStep())
 	.addStep(new CoordinateAssignmentStep(100, 50))
+	.addStep(new EdgeRoutingStep())
 	.addStep(new CleanupStep());
 const optimizer = new BPMNOptimizer().setOptimizationAlgorithm(algorithm);
 

+ 1 - 1
src/v1/optimizer/siguiyama/SiguiyamaAlgorithm.ts

@@ -13,7 +13,7 @@ export default class SiguiyamaAlgorithm {
 	}
 
 	public run(graph: Graph<Node, Edge<Node>>): SiguiyamaContext {
-		const context: SiguiyamaContext = { graph, edgeSubdivisions: [], config: {} };
+		const context: SiguiyamaContext = { graph, edgeSubdivisions: new Map(), config: {} };
 
 		this._steps.forEach((step) => {
 			step.onBeforeRun();

+ 6 - 5
src/v1/optimizer/siguiyama/SiguiyamaContext.ts

@@ -3,6 +3,7 @@ import Layering from "../../graph/layering/Layering.js";
 import Node from "../../graph/node/Node.js"
 import { AlgorithmContext } from "../AlgorithmContext.js"
 import Grid from "./grid/Grid.js";
+import DummyNode from "../../graph/node/DummyNode.js";
 
 /**
  * Набор рёбер обратной связи - рёбра, которые были инвертированы на этапе удаления циклов и должны быть восстановлены на этапе назначения координат.
@@ -13,10 +14,10 @@ export type FeedbackSet = Edge<Node>[];
  * Информация о разбиении длинного ребра на сегменты с dummy-вершинами. Содержит ссылку на исходное ребро и, при наличии, ссылки на новые рёбра, образованные в результате разбиения.
  */
 export type EdgeSubdivision<TNode extends Node> = {
-    original: Edge<TNode>,
-    left?: Edge<TNode>,
-    right?: Edge<TNode>,
-}
+    originalEdge: Edge<TNode>;
+    segments: Edge<TNode>[];
+    dummies: DummyNode[];
+};
 
 /**
  * Контекст, передаваемый между шагами алгоритма Siguiyama. Содержит всю необходимую информацию о графе, его разбиении на слои, сетке и рёбрах обратной связи, которая может быть использована и модифицирована на каждом этапе оптимизации.
@@ -25,5 +26,5 @@ export type SiguiyamaContext = AlgorithmContext & Partial<{
 	feedbackSet: FeedbackSet,
 	layering: Layering<Node, Edge<Node>>,
 	grid: Grid<Node>,
-    edgeSubdivisions: EdgeSubdivision<Node>[]
+    edgeSubdivisions: Map<string, EdgeSubdivision<Node>>
 }>

+ 58 - 2
src/v1/optimizer/siguiyama/grid/Grid.ts

@@ -30,11 +30,67 @@ export default class Grid<TNode extends Node> {
 		return null;
 	}
 
-	public get rows(): number { 
+	public getColumnWidth(column: number) : number {
+		let width = 0;
+
+		for(let i = 0; i < this.getRows(); i++) {
+			const node = this._cells.get(Grid.key(i, column));
+			if(!node)
+				continue;
+
+			width = Math.max(node.getWidth(), width);
+		}
+
+		return width;
+	}
+
+	public getColumnHeight(column: number) : number {
+		let height = 0;
+
+		for(let i = 0; i < this.getRows(); i++) {
+			const node = this._cells.get(Grid.key(i, column));
+			if(!node)
+				continue;
+
+			height = Math.max(node.getHeight(), height);
+		}
+
+		return height;
+	}
+
+	public getRowWidth(row: number) : number {
+		let width = 0;
+
+		for(let i = 0; i < this.getCols(); i++) {
+			const node = this._cells.get(Grid.key(row, i));
+			if(!node)
+				continue;
+
+			width = Math.max(node.getWidth(), width);
+		}
+
+		return width;
+	}
+
+	public getRowHeight(row: number) : number {
+		let height = 0;
+
+		for(let i = 0; i < this.getCols(); i++) {
+			const node = this._cells.get(Grid.key(row, i));
+			if(!node)
+				continue;
+
+			height = Math.max(node.getHeight(), height);
+		}
+
+		return height;
+	}
+
+	public getRows(): number {
 		return this._rows; 
 	}
 
-	public get cols(): number { 
+	public getCols(): number {
 		return this._cols; 
 	}
 }

+ 17 - 43
src/v1/optimizer/steps/CleanupStep.ts

@@ -1,5 +1,5 @@
 import AlgorithmStep from "../AlgorithmStep.js";
-import {EdgeSubdivision, SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
+import {EdgeSubdivision, FeedbackSet, SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
 import CleanupStepError from "../../errors/optimizer/CleanupStepError.js";
 import Graph from "../../graph/Graph.js";
 import Node from "../../graph/node/Node.js";
@@ -11,61 +11,35 @@ export default class CleanupStep extends AlgorithmStep<SiguiyamaContext> {
 	}
 
 	public run(context: SiguiyamaContext): void {
-		const { graph, edgeSubdivisions } = context;
+		const { graph, feedbackSet, edgeSubdivisions } = context;
 
 		if(!graph)
 			throw new CleanupStepError("Graph is undefined");
 		if(!edgeSubdivisions)
 			throw new CleanupStepError("Edge subdivision is undefined");
+		if(!feedbackSet)
+			throw new CleanupStepError("Feedback is undefined")
 
-		this.restoreEdges(graph, edgeSubdivisions);
-		this.removeSubdividedEdges(graph, edgeSubdivisions);
-		this.removeDummyNodes(graph, edgeSubdivisions);
+		for(const subdivision of edgeSubdivisions.values())
+			this.restoreSubdivision(graph, subdivision, feedbackSet);
 	}
 
-	private restoreEdges(graph: Graph<Node, Edge<Node>>, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
-		const grouped = new Map<string, EdgeSubdivision<Node>[]>();
+	private restoreSubdivision(graph: Graph<Node, Edge<Node>>, subdivision: EdgeSubdivision<Node>, feedbackSet: FeedbackSet): void {
+		const { originalEdge, segments, dummies } = subdivision;
 
-		for(const subdivision of edgeSubdivisions) {
-			const edgeId = subdivision.original.getId();
-			const group = grouped.get(edgeId);
-			if(group)
-				group.push(subdivision);
-			else
-				grouped.set(edgeId, [subdivision]);
-		}
+		if(feedbackSet.includes(originalEdge))
+			dummies.reverse();
 
-		for(const subdivisions of grouped.values()) {
-			const originalEdge = subdivisions[0]!.original;
-			const waypoints = subdivisions
-				.slice()
-				.sort((a, b) => {
-					const getDummyIndex = (subdivision: EdgeSubdivision<Node>) => {
-						const dummy = subdivision.left?.getTo() ?? subdivision.right?.getFrom();
-						return Number(dummy!.getId().split("-").pop() ?? "0");
-					};
+		const waypoints = dummies.map((d) => ({ x: d.getX(), y: d.getY() }));
 
-					return getDummyIndex(a) - getDummyIndex(b);
-				})
-				.map((subdivision) => {
-					const dummy = subdivision.left?.getTo() ?? subdivision.right?.getFrom();
-					return { x: dummy!.getX(), y: dummy!.getY() };
-				});
+		originalEdge.setWaypoints(waypoints);
 
-			originalEdge.setWaypoints(waypoints);
-			graph.addEdge(originalEdge);
-		}
-	}
+		for(const segment of segments)
+			graph.removeEdge(segment.getId());
 
-	private removeSubdividedEdges(graph:Graph<Node, Edge<Node>>, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
-		for(const subdivision of edgeSubdivisions) {
-			if(subdivision.left)
-				graph.removeEdge(subdivision.left.getId());
-			if(subdivision.right)
-				graph.removeEdge(subdivision.right.getId());
-		}
-	}
+		for(const dummy of dummies)
+			graph.removeNode(dummy.getId());
 
-	private removeDummyNodes(graph: Graph<Node, Edge<Node>>, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
+		graph.addEdge(originalEdge);
 	}
 }

+ 13 - 16
src/v1/optimizer/steps/CoordinateAssignmentStep.ts

@@ -26,19 +26,16 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 		if(!feedbackSet)
 			throw new Error("Feedback set was not found!");
 
-
-
-		this.reverseEdgesInFeedback(graph, feedbackSet);
+		this.reverseEdgesInFeedback(feedbackSet);
 
 		const grid = this.buildGrid(layering);
-		
+		context.grid = grid;
+
 		this.assignCoordinatesFromGrid(graph, grid);
 	}
 
-	private reverseEdgesInFeedback(graph: Graph<Node,Edge<Node>>, feedbackSet: FeedbackSet) : void {
-		const edges = graph.getEdges();
-
-		for(const edge of edges) {
+	private reverseEdgesInFeedback(feedbackSet: FeedbackSet) : void {
+		for(const edge of feedbackSet) {
 			if(!feedbackSet.includes(edge))
 				continue;
 
@@ -70,8 +67,8 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 		const colX = this.computeOffsets(colWidths, this._nodeGap, PADDING);
 		const rowY = this.computeOffsets(rowHeights, this._layerGap, PADDING);
 
-		for(let col = 0; col < grid.cols; col++) {
-			for(let row = 0; row < grid.rows; row++) {
+		for(let col = 0; col < grid.getCols(); col++) {
+			for(let row = 0; row < grid.getRows(); row++) {
 				const node = grid.get(row, col);
 				if(!node)
 					continue;
@@ -83,10 +80,10 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 	}
 
 	private computeColWidths(grid: Grid<Node>): number[] {
-		const widths: number[] = new Array(grid.cols).fill(0);
+		const widths: number[] = new Array(grid.getCols()).fill(0);
 
-		for(let col = 0; col < grid.cols; col++)
-			for(let row = 0; row < grid.rows; row++) {
+		for(let col = 0; col < grid.getCols(); col++)
+			for(let row = 0; row < grid.getRows(); row++) {
 				const node = grid.get(row, col);
 				if(!node)
 					continue;
@@ -98,10 +95,10 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 	}
 
 	private computeRowHeights(grid: Grid<Node>): number[] {
-		const heights: number[] = new Array(grid.rows).fill(0);
+		const heights: number[] = new Array(grid.getRows()).fill(0);
 
-		for(let row = 0; row < grid.rows; row++)
-			for(let col = 0; col < grid.cols; col++) {
+		for(let row = 0; row < grid.getRows(); row++)
+			for(let col = 0; col < grid.getCols(); col++) {
 				const node = grid.get(row, col);
 				if(!node) continue;
 

+ 4 - 0
src/v1/optimizer/steps/EdgeRoutingStep.ts

@@ -3,6 +3,10 @@ import AlgorithmStep from "../AlgorithmStep.js";
 import {SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
 
 export default class EdgeRoutingStep extends AlgorithmStep<SiguiyamaContext> {
+	public constructor() {
+		super(EdgeRoutingStep.name);
+	}
+
 	public run(context: SiguiyamaContext): void {
 		const { graph, layering, grid } = context;
 

+ 44 - 55
src/v1/optimizer/steps/LayerAssignmentStep.ts

@@ -56,11 +56,11 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 		const cache = new Map<string, number>();
 
 		const getLongestPath = (node: Node): number => {
-			if (cache.has(node.getId())) 
+			if(cache.has(node.getId())) 
 				return cache.get(node.getId())!;
 
 			const successors = dag.getNodeOutputs(node);
-			if (successors.length === 0) {
+			if(successors.length === 0) {
 				cache.set(node.getId(), 0);
 				return 0;
 			}
@@ -71,7 +71,7 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 			return dist;
 		};
 
-		for (const node of dag.getNodes()) {
+		for(const node of dag.getNodes()) {
 			const layer = getLongestPath(node);
 			layering.assign(node, layer);
 		}
@@ -87,7 +87,7 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 	 * @param edgeSubdivisions Массив для хранения информации о разбиениях рёбер.
 	 * @private
 	 */
-	private subdivideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
+	private subdivideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet, edgeSubdivisions: NonNullable<SiguiyamaContext["edgeSubdivisions"]>) : void {
 		const edges = graph.getEdges();
 
 		for(const edge of edges) {
@@ -96,78 +96,67 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 			if(edgeSpan <= 1)
 				continue;
 
-			this.subdivideLongEdge(graph, layering, edge, feedbackSet, edgeSubdivisions, edgeSpan);
+			this.subdivideLongEdge(graph, layering, edge, feedbackSet, edgeSpan, edgeSubdivisions);
 		}
 	}
 
 	/**
-	 * Разбивает одно длинное ребро на сегменты с dummy-вершинами.
-	 * @param graph Граф, в котором разбивается ребро.
-	 * @param layering Текущая структура слоёв.
-	 * @param edge Ребро для разбиения.
-	 * @param feedbackSet Набор рёбер обратной связи.
-	 * @param edgeSubdivisions Массив для хранения информации о разбиениях рёбер.
-	 * @param span Число слоёв, которые пересекает ребро.
-	 * @private
-	 */
-	private subdivideLongEdge(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, edge: Edge<Node>, feedbackSet: FeedbackSet, edgeSubdivisions: EdgeSubdivision<Node>[], span: number) : void {
-		if(span <= 1)
-			return;
-
+     * Разбивает одно длинное ребро на сегменты с dummy-вершинами.
+     * @param graph Граф, в котором разбивается ребро.
+     * @param layering Текущая структура слоёв.
+     * @param edge Ребро для разбиения.
+     * @param feedbackSet Набор рёбер обратной связи.
+     * @param span Число слоёв, которые пересекает ребро.
+     * @param subdivisions Массив для хранения информации о разбиениях рёбер.
+     * @private
+     */
+	private subdivideLongEdge(
+		graph: Graph<Node, Edge<Node>>,
+		layering: Layering<Node, Edge<Node>>,
+		edge: Edge<Node>,
+		feedbackSet: FeedbackSet,
+		span: number,
+		subdivisions: Map<string, EdgeSubdivision<Node>>
+	): void {
 		const edgeId = edge.getId();
 		const edgeFrom = edge.getFrom();
 		const edgeTo = edge.getTo();
 
-		const edgeReversedIndex = feedbackSet.findIndex((e) => e.getId() == edgeId);
+		const edgeReversedIndex = feedbackSet.findIndex((e) => e.getId() === edgeId);
 		const isReversed = edgeReversedIndex !== -1;
 
-		if(isReversed)
-			feedbackSet.splice(edgeReversedIndex, 1);
-
 		graph.removeEdge(edgeId);
 
 		const fromLayer = layering.getLayerOf(edgeFrom)!;
-		const toLayer = layering.getLayerOf(edgeTo)!;
-		const direction = Math.sign(toLayer - fromLayer);
-		let splitTarget = edgeTo;
-		let previousLeftEdge: Edge<Node> | null = null;
+		let previousNode = edgeFrom;
+
+		const segments: Edge<Node>[] = [];
+		const dummies: DummyNode[] = [];
 
 		for(let i = 1; i < span; i++) {
-			if(previousLeftEdge) {
-				graph.removeEdge(previousLeftEdge.getId());
-				if(isReversed) {
-					const removedIndex = feedbackSet.findIndex((e) => e.getId() === previousLeftEdge!.getId());
-					if(removedIndex !== -1)
-						feedbackSet.splice(removedIndex, 1);
-				}
-			}
+			const dummy = new DummyNode(0, 0, edgeId, `dummy-${edgeId}-${i}`);
 
-			const dummyId = `dummy-${edgeId}-${i}`;
-			const node = new DummyNode(0, 0, edgeId, dummyId);
+			graph.addNode(dummy);
+			layering.assign(dummy, fromLayer - i);
+			dummies.push(dummy);
 
-			const dummyLayerIndex = fromLayer + direction * i;
-			graph.addNode(node);
-			layering.assign(node, dummyLayerIndex);
+			const segment = new Edge(previousNode, dummy, [], `${edgeId}_segment_${i}`);
+			graph.addEdge(segment);
+			segments.push(segment);
 
-			const leftEdge = new Edge(edgeFrom, node, [], `${edgeId}_segment_${i}_left`);
-			const rightEdge = new Edge(node, splitTarget, [], `${edgeId}_segment_${i}_right`);
+			if(isReversed)
+				feedbackSet.push(segment);
 
-			graph.addEdge(leftEdge);
-			graph.addEdge(rightEdge);
+			previousNode = dummy;
+		}
 
-			if(isReversed) {
-				feedbackSet.push(leftEdge);
-				feedbackSet.push(rightEdge);
-			}
+		const lastSegment = new Edge(previousNode, edgeTo, [], `${edgeId}_segment_${span}`);
+		graph.addEdge(lastSegment);
+		segments.push(lastSegment);
 
-			edgeSubdivisions.push({
-				original: edge,
-				left: leftEdge,
-				right: rightEdge
-			});
+		if(isReversed)
+			feedbackSet.push(lastSegment);
 
-			previousLeftEdge = leftEdge;
-			splitTarget = node;
-		}
+		subdivisions.set(edgeId, { originalEdge: edge, segments, dummies });
 	}
 }