Pavel Zhigalov 1 月之前
父节点
当前提交
5f02dc9f6a

+ 3 - 1
src/index.ts

@@ -13,6 +13,7 @@ import LayerAssignmentStep from "./v1/optimizer/steps/LayerAssignmentStep.js";
 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";
 
 export class BPMNOptimizer {
 	private _optimizationAlgorithm?: SiguiyamaAlgorithm;
@@ -41,7 +42,8 @@ const algorithm = new SiguiyamaAlgorithm()
 	.addStep(new CycleRemoveStep())
 	.addStep(new LayerAssignmentStep())
 	.addStep(new NodeOrderingStep())
-	.addStep(new CoordinateAssignmentStep(100, 50));
+	.addStep(new CoordinateAssignmentStep(100, 50))
+	.addStep(new CleanupStep());
 const optimizer = new BPMNOptimizer().setOptimizationAlgorithm(algorithm);
 
 const data = readFileSync("./data/graph.json").toString();

+ 11 - 0
src/v1/errors/optimizer/CleanupStepError.ts

@@ -0,0 +1,11 @@
+import OptimizerError from "./OptimizerError.js";
+
+/**
+ * Ошибка, возникшая в процессе работы шага алгоритма {@link CleanupStep}
+ */
+export default class CleanupStepError extends OptimizerError {
+	public constructor(message: string) {
+		super(message);
+		this.name = CleanupStepError.name;
+	}
+}

+ 7 - 0
src/v1/graph/node/DummyNode.ts

@@ -4,7 +4,14 @@ import Node from "./Node.js";
  * Мнимая вершина графа, имеющая нулевые размеры
  */
 export default class DummyNode extends Node {
+	private readonly _originalEdgeId: string;
+
 	public constructor(x: number, y: number, originalEdgeId: string, id?: string) {
 		super(x, y, 0, 0, "dummy-node", id);
+		this._originalEdgeId = originalEdgeId;
+	}
+
+	public getOriginalEdgeId() : string {
+		return this._originalEdgeId;
 	}
 }

+ 8 - 6
src/v1/io/serialize/json/DefaultJsonSerializer.ts

@@ -2,6 +2,7 @@ import Edge from "../../../graph/edge/Edge.js";
 import Graph from "../../../graph/Graph.js";
 import Node from "../../../graph/node/Node.js";
 import { JsonSerializer } from "./JsonSerializer.js";
+import DummyNode from "../../../graph/node/DummyNode.js";
 
 type JsonNode = { x: number, y: number, width: number, height: number, type: string, id: string };
 
@@ -16,16 +17,17 @@ export default class DefaultJsonSerializer implements JsonSerializer<Graph<Node,
      * @param data Объект, который необходимо сериализовать
      */
 	serialize(data: Graph<Node, Edge<Node>>): string {
-		const nodes: JsonNode[] = [];
-		data.getNodes().forEach((node) => {
-			nodes.push({ x: node.getX(), y: node.getY(), width: node.getWidth(), height: node.getHeight(), type: node.getType(), id: node.getId() })
-		})
-
 		const edges: JsonEdge[] = [];
 		data.getEdges().forEach((edge) => {
 			edges.push({ from: edge.getFrom().getId(), to: edge.getTo().getId(), waypoints: edge.getWaypoints(), id: edge.getId() });
 		})
 
-		return JSON.stringify({ nodes, edges });
+		const nodes: JsonNode[] = [];
+		data.getNodes().forEach((node) => {
+			if(!(node instanceof DummyNode))
+				nodes.push({ x: node.getX(), y: node.getY(), width: node.getWidth(), height: node.getHeight(), type: node.getType(), id: node.getId() });
+		})
+
+		return JSON.stringify({ nodes, edges }, null, 2);
 	}
 }

+ 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, config: {} };
+		const context: SiguiyamaContext = { graph, edgeSubdivisions: [], config: {} };
 
 		this._steps.forEach((step) => {
 			step.onBeforeRun();

+ 7 - 0
src/v1/optimizer/siguiyama/SiguiyamaContext.ts

@@ -6,8 +6,15 @@ import Grid from "./grid/Grid.js";
 
 export type FeedbackSet = Edge<Node>[];
 
+export type EdgeSubdivision<TNode extends Node> = {
+    originalEdge: Edge<TNode>,
+    chainNodes: TNode[],
+    chainEdges: Edge<TNode>[]
+}
+
 export type SiguiyamaContext = AlgorithmContext & Partial<{
 	feedbackSet: FeedbackSet,
 	layering: Layering<Node, Edge<Node>>,
 	grid: Grid<Node>,
+    edgeSubdivisions: EdgeSubdivision<Node>[]
 }>

+ 45 - 0
src/v1/optimizer/steps/CleanupStep.ts

@@ -0,0 +1,45 @@
+import AlgorithmStep from "../AlgorithmStep.js";
+import {EdgeSubdivision, 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";
+import Edge from "../../graph/edge/Edge.js";
+
+export default class CleanupStep extends AlgorithmStep<SiguiyamaContext> {
+	public constructor() {
+		super(CleanupStep.name)
+	}
+
+	public run(context: SiguiyamaContext): void {
+		const { graph, edgeSubdivisions } = context;
+
+		if(!graph)
+			throw new CleanupStepError("Graph is undefined");
+		if(!edgeSubdivisions)
+			throw new CleanupStepError("Edge subdivision is undefined");
+
+		this.restoreEdges(graph, edgeSubdivisions);
+		this.removeSubdividedEdges(graph, edgeSubdivisions);
+	}
+
+	private restoreEdges(graph: Graph<Node, Edge<Node>>, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
+		const subdivisionsMap = new Map(
+			edgeSubdivisions.map((e) => [e.originalEdge.getId(), e])
+		);
+
+		for(const edge of graph.getEdges()) {
+			const edgeId = edge.getId();
+
+			const subdivision = subdivisionsMap.get(edgeId);
+			if(!subdivision)
+				continue;
+
+			edge.setWaypoints(subdivision.chainNodes.slice(1, -1).map((n) => ({ x: n.getX(), y: n.getY() })));
+		}
+	}
+
+	private removeSubdividedEdges(graph:Graph<Node, Edge<Node>>, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
+		for(const subdivision of edgeSubdivisions)
+			subdivision.chainEdges.forEach((ce) => graph.removeEdge(ce.getId()));
+	}
+}

+ 1 - 1
src/v1/optimizer/steps/CoordinateAssignmentStep.ts

@@ -47,7 +47,7 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 
 	private buildGrid(layering: Layering<Node, Edge<Node>>): Grid<Node> {
 		const grid = new Grid<Node>();
-		const layers = [...layering.getLayers()].reverse(); 
+		const layers = layering.getLayers();
 
 		for(let col = 0; col < layers.length; col++) {
 			const layer = layers[col]!;

+ 34 - 13
src/v1/optimizer/steps/LayerAssignmentStep.ts

@@ -5,10 +5,11 @@ import Graph from "../../graph/Graph.js";
 import Layering from "../../graph/layering/Layering.js";
 import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
-import { FeedbackSet, SiguiyamaContext } from "../siguiyama/SiguiyamaContext.js";
+import {EdgeSubdivision, FeedbackSet, SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
 
 /**
  * Network simplex algorithm
+ * TODO fix it
  */
 export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
 	public constructor() {
@@ -16,16 +17,18 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 	}
 
 	public run(context: SiguiyamaContext): void {
-		const { graph, feedbackSet } = context;
+		const { graph, feedbackSet, edgeSubdivisions } = context;
 
 		if(!graph)
 			throw new LayerAssignmentStepError("Graph was not found!");
 		if(!feedbackSet)
 			throw new LayerAssignmentStepError("Feedback set is undefined!");
+		if(!edgeSubdivisions)
+			throw new LayerAssignmentStepError("Edge subdivisions information is undefined!")
 
 		const layering = this.longestPathAlgorithm(graph);
 
-		this.subdivideLongEdges(graph, layering, feedbackSet);
+		this.subdivideLongEdges(graph, layering, feedbackSet, context.edgeSubdivisions!);
 
 		context.layering = layering;
 	}
@@ -63,10 +66,17 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 			}
 		}
 
+		const maxLayer = currentLayerIndex;
+
+		for(const node of nodes) {
+			const layer = layering.getLayerOf(node)!;
+			layering.assign(node, maxLayer - layer);
+		}
+
 		return layering;
 	}
 
-	private subdivideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet) : void {
+	private subdivideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet, edgeSubdivisions: EdgeSubdivision<Node>[]) : void {
 		const edges = graph.getEdges();
 
 		for(const edge of edges) {
@@ -75,11 +85,11 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 			if(edgeSpan <= 1)
 				continue;
 
-			this.subdivideLongEdge(graph, layering, edge, feedbackSet, edgeSpan);
+			this.subdivideLongEdge(graph, layering, edge, feedbackSet, edgeSubdivisions, edgeSpan);
 		}
 	}
 
-	private subdivideLongEdge(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, edge: Edge<Node>, feedbackSet: FeedbackSet, span: number) : void {
+	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;
 
@@ -88,25 +98,30 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 		const edgeReversedIndex = feedbackSet.findIndex((e) => e.getId() == edgeId);
 		const isReversed = edgeReversedIndex !== -1;
 
-		if(isReversed)
-			feedbackSet.splice(edgeReversedIndex, 1);
-
-		graph.removeEdge(edge.getId());
-
 		const fromLayer = layering.getLayerOf(edgeFrom)!;
+		const toLayer = layering.getLayerOf(edgeTo)!;
+		const direction = Math.sign(toLayer - fromLayer);
+		const subdivision: EdgeSubdivision<Node> = {
+			originalEdge: edge,
+			chainNodes: [edgeFrom],
+			chainEdges: []
+		};
 		let previousNode = edgeFrom;
 
 		for(let i = 1; i < span; i++) {
 			const dummyId = `dummy-${edgeId}-${i}`;
-			const node = new DummyNode(0, 0, dummyId);
-			const dummyLayerIndex = fromLayer - i;
+			const node = new DummyNode(0, 0, edgeId, dummyId);
+
+			const dummyLayerIndex = fromLayer + direction * i;
 
 			graph.addNode(node);
+			subdivision.chainNodes.push(node);
 			layering.assign(node, dummyLayerIndex);
 
 			const newEdge = new Edge(previousNode, node, [], `${edgeId}_segment_${i}`);
 
 			graph.addEdge(newEdge);
+			subdivision.chainEdges.push(newEdge);
 
 			if(isReversed)
 				feedbackSet.push(newEdge);
@@ -116,7 +131,13 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 
 		const lastEdge = new Edge(previousNode, edgeTo, [], `${edgeId}_segment_${span}`);
 		graph.addEdge(lastEdge);
+
+		subdivision.chainEdges.push(lastEdge);
+		subdivision.chainNodes.push(edgeTo);
+        
 		if(isReversed)
 			feedbackSet.push(lastEdge);
+
+		edgeSubdivisions.push(subdivision);
 	}
 }