2 Angajamente 04abf7b12d ... a27515519a

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  Pavel Zhigalov a27515519a coordinate assignment v00 5 zile în urmă
  Pavel Zhigalov 04abf7b12d coordinate assignment v00 5 zile în urmă

+ 3 - 3
src/index.ts

@@ -1,8 +1,8 @@
 import { readFileSync, writeFileSync } from "node:fs";
 import BPMNError from "./v1/errors/BPMNError.js";
-import Edge from "./v1/graph/Edge.js";
+import Edge from "./v1/graph/edge/Edge.js";
 import Graph from "./v1/graph/Graph.js";
-import Node from "./v1/graph/Node.js";
+import Node from "./v1/graph/node/Node.js";
 import Deserializer from "./v1/io/deserialize/Deserializer.js";
 import DefaultJsonDeserializer from "./v1/io/deserialize/json/DefaultJsonDeserializer.js";
 import Serializer from "./v1/io/serialize/Serializer.js";
@@ -37,7 +37,7 @@ function getSerializer() : Serializer<Graph<Node, Edge<Node>>, string> {
 	return new DefaultJsonSerializer();
 }
 
-const algorithm = new SiguiyamaAlgorithm().addStep(new CycleRemoveStep()).addStep(new LayerAssignmentStep()).addStep(new NodeOrderingStep()).addStep(new CoordinateAssignmentStep(20, 20));
+const algorithm = new SiguiyamaAlgorithm().addStep(new CycleRemoveStep()).addStep(new LayerAssignmentStep()).addStep(new NodeOrderingStep()).addStep(new CoordinateAssignmentStep(100, 50));
 const optimizer = new BPMNOptimizer().setOptimizationAlgorithm(algorithm);
 
 const data = readFileSync("./data/graph.json").toString();

+ 2 - 2
src/v1/graph/Graph.ts

@@ -1,6 +1,6 @@
 import { array, forward, object, partialCheck, pipe } from "valibot";
-import Edge, { EdgeSchema } from "./Edge.js";
-import Node, { NodeSchema } from "./Node.js";
+import Edge, { EdgeSchema } from "./edge/Edge.js";
+import Node, { NodeSchema } from "./node/Node.js";
 import GraphError from "../errors/graph/GraphError.js";
 
 type GraphCache<TNode extends Node> = Partial<{

+ 3 - 3
src/v1/graph/Edge.ts → src/v1/graph/edge/Edge.ts

@@ -1,10 +1,10 @@
 import { object, string } from "valibot";
-import Node, { NodeSchema } from "./Node.js";
+import Node, { NodeSchema } from "../node/Node.js";
 
 export default class Edge<TNode extends Node> {
 	public readonly id: string;
-	public from: TNode["id"];
-	public to: TNode["id"];
+	public readonly from: TNode["id"];
+	public readonly to: TNode["id"];
 	
 	public constructor(from: TNode["id"], to: TNode["id"], id?: string) {
 		this.from = from;

+ 1 - 1
src/v1/graph/layering/Layer.ts

@@ -1,4 +1,4 @@
-import Node from "../Node.js";
+import Node from "../node/Node.js";
 
 export default class Layer<TNode extends Node> {
 	public readonly index: number;

+ 2 - 2
src/v1/graph/layering/Layering.ts

@@ -1,6 +1,6 @@
 import LayeringError from "../../errors/graph/layering/LayeringError.js";
-import Edge from "../Edge.js";
-import Node from "../Node.js";
+import Edge from "../edge/Edge.js";
+import Node from "../node/Node.js";
 import Layer from "./Layer.js";
 
 export default class Layering<TNode extends Node, TEdge extends Edge<TNode>> {

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

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

+ 0 - 0
src/v1/graph/Node.ts → src/v1/graph/node/Node.ts


+ 4 - 4
src/v1/io/deserialize/dzwf/DZWFDeserializer.ts

@@ -1,8 +1,8 @@
 import { InferOutput, parse } from "valibot";
 import DeserializerError from "../../../errors/DeserializerError.js";
-import Edge from "../../../graph/Edge.js";
+import Edge from "../../../graph/edge/Edge.js";
 import Graph, { GraphSchema } from "../../../graph/Graph.js";
-import Node from "../../../graph/Node.js";
+import Node from "../../../graph/node/Node.js";
 import { JsonDeserializer } from "../json/JsonDeserializer.js";
 import { DZWFData } from "../../dzwf/DZWFData.js";
 
@@ -20,8 +20,8 @@ export default class DZWFDeserializer implements JsonDeserializer<{ graph: Graph
 				return { x, y, type, id, width: 0, height: 0 }
 			})
 			const edges: InferOutput<typeof GraphSchema>["edges"] = parsed.links.map((link) => {
-				const { sourceId, targetId, id } = link;
-				return { id, from: sourceId, to: targetId };
+				const { sourceId, targetId, id, diagram } = link;
+				return { id, from: sourceId, to: targetId, waypoints: diagram?.vertices ?? [] };
 			})
 
 			graph = parse(GraphSchema, { nodes, edges });

+ 2 - 2
src/v1/io/deserialize/json/DefaultJsonDeserializer.ts

@@ -1,8 +1,8 @@
 import { InferOutput, parse } from "valibot";
 import DeserializerError from "../../../errors/DeserializerError.js";
-import Edge from "../../../graph/Edge.js";
+import Edge from "../../../graph/edge/Edge.js";
 import Graph, { GraphSchema } from "../../../graph/Graph.js";
-import Node from "../../../graph/Node.js";
+import Node from "../../../graph/node/Node.js";
 import { JsonDeserializer } from "./JsonDeserializer.js";
 
 export default class DefaultJsonDeserializer implements JsonDeserializer<Graph<Node, Edge<Node>>> {

+ 4 - 1
src/v1/io/dzwf/DZWFData.ts

@@ -14,6 +14,9 @@ export type DZWFData = {
 	links: {
 		sourceId: string,
 		targetId: string,
-		id: string
+		id: string,
+		diagram?: {
+			vertices: { x: number, y: number }[]
+		}
 	}[] 
 };

+ 2 - 2
src/v1/io/serialize/dzwf/JointJsonSerializer.ts

@@ -1,7 +1,7 @@
 import { writeFileSync } from "node:fs";
-import Edge from "../../../graph/Edge.js";
+import Edge from "../../../graph/edge/Edge.js";
 import Graph from "../../../graph/Graph.js";
-import Node from "../../../graph/Node.js";
+import Node from "../../../graph/node/Node.js";
 import Serializer from "../Serializer.js";
 import { DZWFData } from "../../dzwf/DZWFData.js";
 

+ 2 - 2
src/v1/io/serialize/json/DefaultJsonSerializer.ts

@@ -1,6 +1,6 @@
-import Edge from "../../../graph/Edge.js";
+import Edge from "../../../graph/edge/Edge.js";
 import Graph from "../../../graph/Graph.js";
-import Node from "../../../graph/Node.js";
+import Node from "../../../graph/node/Node.js";
 import { JsonSerializer } from "./JsonSerializer.js";
 
 export default class DefaultJsonSerializer implements JsonSerializer<Graph<Node, Edge<Node>>> {

+ 2 - 2
src/v1/optimizer/AlgorithmContext.ts

@@ -1,6 +1,6 @@
-import Edge from "../graph/Edge.js"
+import Edge from "../graph/edge/Edge.js"
 import Graph from "../graph/Graph.js"
-import Node from "../graph/Node.js"
+import Node from "../graph/node/Node.js"
 
 export type AlgorithmContext = {
 	graph: Graph<Node, Edge<Node>>,

+ 32 - 110
src/v1/optimizer/siguiyama/CoordinateAssignmentStep.ts

@@ -1,10 +1,9 @@
-import Edge from "../../graph/Edge.js";
+import Edge from "../../graph/edge/Edge.js";
 import Graph from "../../graph/Graph.js";
 import Layer from "../../graph/layering/Layer.js";
-import Layering from "../../graph/layering/Layering.js";
-import Node from "../../graph/Node.js";
+import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
-import { SiguiyamaContext } from "./SiguiyamaContext.js";
+import { FeedbackSet, SiguiyamaContext } from "./SiguiyamaContext.js";
 
 export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
 	private readonly _layerGap: number;
@@ -17,19 +16,33 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 	}
 
 	public run(context: SiguiyamaContext): void {
-		const { graph, layering } = context;
+		const { graph, layering, feedbackSet } = context;
 
 		if(!graph)
 			throw new Error("Source graph was not found!");
 		if(!layering)
 			throw new Error("Layering of graph was not found!");
+		if(!feedbackSet)
+			throw new Error("Feedback set was not found!");
+
+		this.reverseEdgesInFeedback(graph, feedbackSet);
 
 		const reversedLayers = layering.getLayers().reverse();
 
-		console.log(graph.getNodes());
 		this.assignXCoordinates(graph, reversedLayers);
-		this.assignYCoordinates(graph, layering, reversedLayers);
-		console.log(graph.getNodes());
+		this.assignYCoordinates(graph, reversedLayers);
+	}
+
+	private reverseEdgesInFeedback(graph: Graph<Node,Edge<Node>>, feedbackSet: FeedbackSet) : void {
+		const edges = graph.getEdges();
+
+		for(const edge of edges) {
+			if(!feedbackSet.includes(edge.id))
+				continue;
+
+			const from = edge.to, to = edge.from;
+			graph.updateEdge(edge.id, { ...edge, from, to });
+		}
 	}
 
 	/**
@@ -48,7 +61,7 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 				if(!node)
 					continue;
 
-				node.x = currentX + (layerWidth - node.width) / 2;
+				node.x = currentX;
 			}
 
 			currentX += layerWidth + this._layerGap;
@@ -61,109 +74,18 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 	 * @param layering Укладка графа
 	 * @param layers Массив из слоев, по которым распределены вершины
 	 */
-	private assignYCoordinates(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, layers: Layer<Node>[]) : void {
-		if(layers.length === 0)
-			return;
-
-		this.distributeLayerEvenly(graph, layers[0]!);
-
-		for(let i = 1; i < layers.length; i++) {
-			const layer = layers[i]!;
-			this.assignYByBarycenter(graph, layer);
-			this.resolveNodeOverlaps(graph, layer);
-		}
-	}
-
-	/**
-	 * Равномерное распределение вершин внутри слоя
-	 * @param graph Граф, содержащий информацию о вершинах и рёбрах
-	 * @param layer Слой, вершины которого нужно распределить равномерно
-	 */
-	private distributeLayerEvenly(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
-		let currentY = 0.0;
-
-		for(const nodeId of layer.nodes) {
-			const node = graph.getNode(nodeId);
-			if(!node)
-				continue;
-
-			node.y = currentY;
-			currentY += node.height + this._nodeGap;
-		}
-	}
-
-	/**
-	 * Назначение Y-координаты для вершин внутрия слоя с выравниванием по barycenter соседних вершин
-	 * @param graph Граф, содержащий информацию о вершинах и рёбрах
-	 * @param layer Слой, вершины которого нужно распределить и выровнять
-	 */
-	private assignYByBarycenter(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
-		const assignments: { nodeId: Node["id"], y: number }[] = [];
-
-		for(const nodeId of layer.nodes) {
-			const node = graph.getNode(nodeId);
-			if(!node)
-				continue;
+	private assignYCoordinates(graph: Graph<Node, Edge<Node>>, layers: Layer<Node>[]) : void {
+		for(const layer of layers) {
+			let currentY = 0.0;
+			
+			for(const nodeId of layer.nodes) {
+				const node = graph.getNode(nodeId);
+				if(!node)
+					continue;
 
-			const predecessors = graph.getNodeInputs(nodeId);
-			if(predecessors.length === 0) {
-				assignments.push({ nodeId, y: node.y ?? 0 });
-				continue;
+				node.y = currentY;
+				currentY += node.height + this._nodeGap;
 			}
-
-			const averageY = predecessors.reduce((sum, pred) => sum + (pred.y ?? 0) + (pred.height ?? 0) / 2, 0) / predecessors.length;
-			assignments.push({ nodeId, y: averageY - node.height / 2});
-		}
-
-		assignments.sort((a, b) => a.y - b.y);
-
-		for(const { nodeId, y } of assignments) {
-			const node = graph.getNode(nodeId);
-			if(!node)
-				continue;
-
-			node.y = y;
-		}
-	}
-
-	/**
-	 * Разрешение конфликтов, связанных с перекрытием вершин друг другом
-	 * @param graph Граф, содержащий информацию о вершинах и рёбрах
-	 * @param layer Слой, в котором необходимо разрешить конфликты перекрытия вершин
-	 */
-	private resolveNodeOverlaps(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
-		const nodes = layer.nodes.map((nodeId) => graph.getNode(nodeId)).filter((node) => node !== null).sort((a, b) => (a.y ?? 0) - (b.y ?? 0));
-
-		for(let i = 1; i < nodes.length; i++) {
-			const previous = nodes[i - 1], current = nodes[i];
-			const minY = (previous?.y ?? 0) + (previous?.height ?? 0) + this._nodeGap;
-			
-			if(current && (current.y ?? 0) < minY)
-				current.y = minY;
 		}
-
-		this.centerNodesVertically(nodes);
-	}
-
-
-	/**
-	 * Центрирование вершин в слое по вертикали
-	 * @param nodes Вершины, которые надо отцентровать
-	 */
-	private centerNodesVertically(nodes: Node[]) : void {
-		if(!nodes || nodes.length == 0)
-			return;
-
-		const totalHeight = nodes.reduce((sum, node) => sum + (node.height ?? 0), 0) + this._nodeGap * (nodes.length - 1);
-
-		const firstY = nodes[0]?.y ?? 0;
-		const offset = firstY - totalHeight / 2;
-
-		const eps = 1.0;
-		if(Math.abs(offset) < eps)
-			return;
-
-		for(const node of nodes)
-			node.y = (node.y ?? 0) - offset;
 	}
 }

+ 3 - 3
src/v1/optimizer/siguiyama/CycleRemoveStep.ts

@@ -1,7 +1,7 @@
 import CycleRemoveStepError from "../../errors/optimizer/CycleRemoveStepError.js";
-import Edge from "../../graph/Edge.js";
+import Edge from "../../graph/edge/Edge.js";
 import Graph from "../../graph/Graph.js";
-import Node from "../../graph/Node.js";
+import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
 import { SiguiyamaContext } from "./SiguiyamaContext.js";
 
@@ -63,7 +63,7 @@ export default class CycleRemoveStep extends AlgorithmStep<SiguiyamaContext> {
 
 			if(fromIndex > toIndex) {
 				graph.updateEdge(edge.id, { ...edge, from: edge.to, to: edge.from });
-				feedbackSet.push(edge);
+				feedbackSet.push(edge.id);
 			}
 		}
 

+ 56 - 4
src/v1/optimizer/siguiyama/LayerAssignmentStep.ts

@@ -1,10 +1,11 @@
 import LayerAssignmentStepError from "../../errors/optimizer/LayerAssignmentStepError.js";
-import Edge from "../../graph/Edge.js";
+import DummyNode from "../../graph/node/DummyNode.js";
+import Edge from "../../graph/edge/Edge.js";
 import Graph from "../../graph/Graph.js";
 import Layering from "../../graph/layering/Layering.js";
-import Node from "../../graph/Node.js";
+import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
-import { SiguiyamaContext } from "./SiguiyamaContext.js";
+import { FeedbackSet, SiguiyamaContext } from "./SiguiyamaContext.js";
 
 /**
  * Network simplex algorithm
@@ -22,10 +23,13 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 		if(!feedbackSet)
 			throw new LayerAssignmentStepError("Feedback set is undefined!");
 
+		console.log(graph)
 		const layering = this.longestPathAlgorithm(graph);
 
+		this.subdivideLongEdges(graph, layering, feedbackSet);
+		console.log(graph)
+
 		context.layering = layering;
-		console.log(layering.getLayers().map((l) => l.nodes));
 	}
 
 	private longestPathAlgorithm(dag: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["layering"]> {
@@ -63,4 +67,52 @@ export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext>
 
 		return layering;
 	}
+
+	private subdivideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet) : void {
+		const edges = graph.getEdges();
+
+		for(const edge of edges) {
+			const edgeSpan = layering.getEdgeSpan(edge);
+
+
+			if(edgeSpan <= 1)
+				continue;
+
+			this.subdivideLongEdge(graph, layering, edge, feedbackSet, edgeSpan);
+		}
+	}
+
+	private subdivideLongEdge(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, edge: Edge<Node>, feedbackSet: FeedbackSet, span: number) : void {
+		if(span <= 1)
+			return;
+
+		const edgeReversedIndex = feedbackSet.findIndex((e) => e == edge.id);
+		const isReversed = edgeReversedIndex !== -1;
+
+		if(isReversed)
+			feedbackSet.splice(edgeReversedIndex, 1);
+
+		graph.removeEdge(edge.id);
+
+		const fromLayer = layering.getLayerOf(edge.from)!;
+		let previousNodeId = edge.from;
+
+		for(let i = 1; i < span; i++) {
+			const dummyId = `dummy-${edge.id}-${i}`;
+			const node = new DummyNode(0, 0, dummyId);
+			const dummyLayerIndex = fromLayer - i;
+
+			graph.addNode(node);
+			layering.assign(node, dummyLayerIndex);
+
+			const newEdge = new Edge(previousNodeId, node.id, `${edge.id}_segment_${i}`);
+
+			graph.addEdge(newEdge);
+
+			previousNodeId = node.id;
+		}
+
+		const lastEdge = new Edge(previousNodeId, edge.to, `${edge.id}_segment_${span}`);
+		graph.addEdge(lastEdge);
+	}
 }

+ 2 - 2
src/v1/optimizer/siguiyama/NodeOrderingStep.ts

@@ -1,8 +1,8 @@
 import NodeOrderingStepError from "../../errors/optimizer/NodeOrderingStepError.js";
-import Edge from "../../graph/Edge.js";
+import Edge from "../../graph/edge/Edge.js";
 import Graph from "../../graph/Graph.js";
 import Layering from "../../graph/layering/Layering.js";
-import Node from "../../graph/Node.js";
+import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
 import { SiguiyamaContext } from "./SiguiyamaContext.js";
 

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

@@ -1,6 +1,6 @@
-import Edge from "../../graph/Edge.js";
+import Edge from "../../graph/edge/Edge.js";
 import Graph from "../../graph/Graph.js";
-import Node from "../../graph/Node.js";
+import Node from "../../graph/node/Node.js";
 import Step from "../AlgorithmStep.js";
 import { SiguiyamaContext } from "./SiguiyamaContext.js";
 

+ 3 - 3
src/v1/optimizer/siguiyama/SiguiyamaContext.ts

@@ -1,9 +1,9 @@
-import Edge from "../../graph/Edge.js"
+import Edge from "../../graph/edge/Edge.js"
 import Layering from "../../graph/layering/Layering.js";
-import Node from "../../graph/Node.js"
+import Node from "../../graph/node/Node.js"
 import { AlgorithmContext } from "../AlgorithmContext.js"
 
-export type FeedbackSet = Edge<Node>[];
+export type FeedbackSet = Edge<Node>["id"][];
 
 export type SiguiyamaContext = AlgorithmContext & Partial<{
 	feedbackSet: FeedbackSet,