icestormikk 2 weeks ago
parent
commit
b7dd145cdb

+ 8 - 0
src/errors/graph/layering/grid/GridError.ts

@@ -0,0 +1,8 @@
+import BPMNError from "../../../BPMNError.js";
+
+export default class GridError extends BPMNError {
+	public constructor(message: string) {
+		super(message);
+		this.name = GridError.name;
+	}
+}

+ 89 - 0
src/graph/grid/Grid.ts

@@ -0,0 +1,89 @@
+import GridError from "../../errors/graph/layering/grid/GridError.js";
+
+/**
+ * Стека для укладка графа на двумерной плоскости
+ */
+export class Grid<TElement> {
+	/**
+     * Ячейки сетки, которые могут хранить в себе элементы
+     * @protected
+     */
+	protected readonly _cells: (TElement | undefined)[][];
+
+	/**
+	 * Количество элементов в каждой из строк сетки
+	 * @private
+	 */
+	private readonly width: number;
+	/**
+	 * Количество элементов в каждом из столбцов сетки
+	 * @private
+	 */
+	private readonly height: number;
+
+	public constructor(width: number, height: number) {
+		this.width = width;
+		this.height = height;
+		this._cells = Array.from({ length: height }, () => new Array(width));
+	}
+
+	public set(row: number, col: number, value: TElement) : this {
+		this.validate(row, col);
+
+		if(row >= this._cells.length)
+			this._cells.length = row + 1;
+
+		if(this._cells[row] === undefined)
+			this._cells[row] = [];
+
+		const r = this._cells[row]!;
+
+		if(col >= r.length)
+			r.length = col + 1;
+
+		r[col] = value;
+
+		return this;
+	}
+
+	public get(row: number, col: number) : TElement | undefined {
+		this.validate(row, col);
+		return this._cells[row]![col];
+	}
+
+	public delete(row: number, col: number) : this {
+		this.validate(row, col);
+
+		const r = this._cells[row];
+		if(!r)
+			return this;
+
+		delete r[col];
+
+		return this;
+	}
+
+	public isEmpty(row: number, col: number) : boolean {
+		this.validate(row, col);
+
+		const r = this._cells[row];
+		if(!r)
+			return true;
+
+		return r[col] === undefined;
+	}
+
+	public getSize(): { width: number; height: number } {
+		return { width: this.width, height: this.height };
+	}
+
+	protected validate(row: number, col: number) : void {
+		if(row < 0 || row >= this._cells.length)
+			throw new GridError(`Row index is out of bounds [0, ${this._cells.length - 1}]: ${row}`);
+
+		const r = this._cells[row]!;
+
+		if(col < 0 || col >= r.length)
+			throw new GridError(`Column index is out of bounds [0, ${r.length - 1}]: ${row}`);
+	}
+}

+ 36 - 0
src/graph/grid/NodeGrid.ts

@@ -0,0 +1,36 @@
+import {Grid} from "./Grid.js";
+import Node from "../node/Node.js";
+
+export default class NodeGrid<TNode extends Node> extends Grid<TNode> {
+	public getHeight(row: number, col: number) : number {
+		this.validate(row, col);
+
+		const r = this._cells[row];
+		if(!r)
+			return 0.0;
+
+		let height = 0.0;
+		for(const el of r)
+			height = Math.max(height, el?.getHeight() ?? 0.0);
+
+		return height;
+	}
+
+	public getWidth(row: number, col: number) : number {
+		this.validate(row, col);
+
+		let width = 0.0;
+		for(const r of this._cells) {
+			if(!r)
+				continue;
+
+			width = Math.max(width, r[col]?.getWidth() ?? 0.0);
+		}
+
+		return width;
+	}
+
+	public getCellSize(row: number, col: number) : { width: number, height: number } {
+		return { width: this.getWidth(row, col), height: this.getHeight(row, col) };
+	}
+}

+ 13 - 1
src/index.ts

@@ -3,6 +3,12 @@ import Graph from "./graph/Graph.js";
 import Node from "./graph/node/Node.js";
 import Edge from "./graph/edge/Edge.js";
 import BPMNError from "./errors/BPMNError.js";
+import DzwfJsonDeserializer from "./io/deserialize/json/DzwfJsonDeserializer.js";
+import {readFileSync} from "node:fs";
+import CycleRemoveStep from "./optimizer/steps/CycleRemoveStep.js";
+import LayerAssignmentStep from "./optimizer/steps/LayerAssignmentStep.js";
+import NodeOrderingStep from "./optimizer/steps/NodeOrderingStep.js";
+import CoordinateAssignmentStep from "./optimizer/steps/CoordinateAssignmentStep.js";
 
 export class BPMNOptimizer {
 	private _optimizationAlgorithm?: SiguiyamaAlgorithm;
@@ -26,4 +32,10 @@ export class BPMNOptimizer {
 
 		this._optimizationAlgorithm.run(this._graph);
 	}
-}
+}
+
+const deserializer = new DzwfJsonDeserializer();
+const data = deserializer.deserialize(readFileSync("./data/bpmn-graph.json").toString());
+const algorithm = new SiguiyamaAlgorithm().addStep(new CycleRemoveStep()).addStep(new LayerAssignmentStep()).addStep(new NodeOrderingStep()).addStep(new CoordinateAssignmentStep());
+const optimizer = new BPMNOptimizer().setOptimizationAlgorithm(algorithm).setGraph(data.graph);
+optimizer.run();

+ 2 - 0
src/optimizer/siguiyama/SiguiyamaContext.ts

@@ -2,6 +2,7 @@ import Edge from "../../graph/edge/Edge.js"
 import Layering from "../../graph/layering/Layering.js";
 import Node from "../../graph/node/Node.js"
 import { AlgorithmContext } from "../AlgorithmContext.js"
+import NodeGrid from "../../graph/grid/NodeGrid.js";
 
 /**
  * Набор рёбер обратной связи - рёбра, которые были инвертированы на этапе удаления циклов и должны быть восстановлены на этапе назначения координат.
@@ -17,4 +18,5 @@ export type SiguiyamaContext = AlgorithmContext & Partial<{
 	feedbackSet: FeedbackSet,
 	edgeSubdivisions: EdgeSubdivisions<Node, Edge<Node>>
 	layering: Layering<Node, Edge<Node>>,
+	grid: NodeGrid<Node>
 }>

+ 36 - 0
src/optimizer/steps/CoordinateAssignmentStep.ts

@@ -4,6 +4,7 @@ import EdgeRoutingStepError from "../../errors/optimizer/EdgeRoutingStepError.js
 import Edge from "../../graph/edge/Edge.js";
 import Layering from "../../graph/layering/Layering.js";
 import Node from "../../graph/node/Node.js";
+import NodeGrid from "../../graph/grid/NodeGrid.js";
 
 /**
  * Шаг назначения координат вершинам
@@ -44,6 +45,7 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 		if(!layering)
 			throw new Error("Layering of graph was not found!");
 
+		const grid = this.buildGrid(layering);
 		this.assignCoordinates(layering);
 	}
 
@@ -79,4 +81,38 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 			xOffset += width + this._padding;
 		}
 	}
+
+	private buildGrid(layering: Layering<Node, Edge<Node>>) : NodeGrid<Node> {
+		const grid = new NodeGrid(0,0);
+		const layers = layering.getLayers().toReversed();
+
+		for(let i = 0; i < layers.length; i++) {
+			const layer = layers[i]!, nodes = layer.getNodes();
+
+			for(let j = 0; j < nodes.length; j++) {
+				grid.set(i, j, nodes[j]!);
+			}
+		}
+
+		return grid;
+	}
+
+	private assignCoordinatesByGrid(grid: NodeGrid<Node>) : void {
+		const { width, height } = grid.getSize();
+
+		const xOffset = 0.0;
+		for(let i = 0; i < height; i++) {
+			const yOffset = 0.0;
+
+			for(let j = 0; j < width; j++) {
+				const node = grid.get(i, j);
+				const cellSize = grid.getCellSize(j, j);
+
+				if(node) {
+					const nodeWidth = node.getWidth(), nodeHeight = node.getHeight();
+					node.setX(xOffset + );
+				}
+			}
+		}
+	}
 }