Bladeren bron

introduced grid system placing in Coordinate Assignment step

Pavel Zhigalov 10 uur geleden
bovenliggende
commit
93da32f8d5
2 gewijzigde bestanden met toevoegingen van 119 en 38 verwijderingen
  1. 90 38
      src/v1/optimizer/siguiyama/CoordinateAssignmentStep.ts
  2. 29 0
      src/v1/optimizer/siguiyama/grid/Grid.ts

+ 90 - 38
src/v1/optimizer/siguiyama/CoordinateAssignmentStep.ts

@@ -1,8 +1,9 @@
 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/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
+import Grid from "./grid/Grid.js";
 import { FeedbackSet, SiguiyamaContext } from "./SiguiyamaContext.js";
 
 export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
@@ -27,10 +28,8 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 
 		this.reverseEdgesInFeedback(graph, feedbackSet);
 
-		const reversedLayers = layering.getLayers().reverse();
-
-		this.assignXCoordinates(graph, reversedLayers);
-		this.assignYCoordinates(graph, reversedLayers);
+		const grid = this.buildGrid(layering);
+		this.assignCoordinatesFromGrid(graph, grid);
 	}
 
 	private reverseEdgesInFeedback(graph: Graph<Node,Edge<Node>>, feedbackSet: FeedbackSet) : void {
@@ -45,47 +44,100 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
 		}
 	}
 
-	/**
-	 * Назначение X-координаты для всех вершин 
-	 * @param graph Граф, содержащий информацию о вершинах и рёбрах
-	 * @param layers Массив из слоев, по которым распределены вершины
-	 */
-	private assignXCoordinates(graph: Graph<Node, Edge<Node>>, layers: Layer<Node>[]) : void {
-		let currentX = 0;
-
-		for(const layer of layers) {
-			const layerWidth = Math.max(...layer.nodes.map((nodeId) => graph.getNode(nodeId)?.width ?? 0));
-		
-			for(const nodeId of layer.nodes) {
-				const node = graph.getNode(nodeId);
-				if(!node)
-					continue;
+	private buildGrid(
+		layering: Layering<Node, Edge<Node>>
+	): Grid<Node> {
+		const grid = new Grid<Node>();
+		const layers = [...layering.getLayers()].reverse(); 
 
-				node.x = currentX;
-			}
+		for(let col = 0; col < layers.length; col++) {
+			const layer = layers[col]!;
 
-			currentX += layerWidth + this._layerGap;
+			for(let row = 0; row < layer.nodes.length; row++)
+				grid.set(row, col, layer.nodes[row]!);
 		}
+
+		return grid;
 	}
 
-	/**
-	 * Назначение Y-координаты для всех вершин 
-	 * @param graph Граф, содержащий информацию о вершинах и рёбрах
-	 * @param layering Укладка графа
-	 * @param layers Массив из слоев, по которым распределены вершины
-	 */
-	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) {
+	private assignCoordinatesFromGrid(
+		graph: Graph<Node, Edge<Node>>,
+		grid: Grid<Node>
+	): void {
+		const PADDING = 60;
+		const H_GAP = 80;  
+		const V_GAP = 50; 
+
+		const colWidths = this.computeColWidths(graph, grid);
+		const rowHeights = this.computeRowHeights(graph, grid);
+
+		const colX = this.computeOffsets(colWidths, H_GAP, PADDING);
+		const rowY = this.computeOffsets(rowHeights, V_GAP, PADDING);
+
+		for(let col = 0; col < grid.cols; col++) {
+			for(let row = 0; row < grid.rows; row++) {
+				const nodeId = grid.get(row, col);
+				if(!nodeId) continue;
+
 				const node = graph.getNode(nodeId);
-				if(!node)
-					continue;
+				if(!node) continue;
 
-				node.y = currentY;
-				currentY += node.height + this._nodeGap;
+				node.x = colX[col]! + (colWidths[col]! - node.width) / 2;
+				node.y = rowY[row]! + (rowHeights[row]! - node.height) / 2;
 			}
 		}
 	}
+
+	private computeColWidths(
+		graph: Graph<Node, Edge<Node>>,
+		grid: Grid<Node>
+	): number[] {
+		const widths: number[] = new Array(grid.cols).fill(0);
+
+		for(let col = 0; col < grid.cols; col++)
+			for(let row = 0; row < grid.rows; row++) {
+				const nodeId = grid.get(row, col);
+				if(!nodeId) continue;
+
+				const node = graph.getNode(nodeId);
+				if(!node) continue;
+
+				widths[col] = Math.max(widths[col]!, node.width);
+			}
+
+		return widths;
+	}
+
+	private computeRowHeights(
+		graph: Graph<Node, Edge<Node>>,
+		grid: Grid<Node>
+	): number[] {
+		const heights: number[] = new Array(grid.rows).fill(0);
+
+		for(let row = 0; row < grid.rows; row++)
+			for(let col = 0; col < grid.cols; col++) {
+				const nodeId = grid.get(row, col);
+				if(!nodeId) continue;
+
+				const node = graph.getNode(nodeId);
+				if(!node) continue;
+
+				heights[row] = Math.max(heights[row]!, node.height);
+			}
+
+		return heights;
+	}
+
+	private computeOffsets(
+		sizes: number[],
+		gap: number,
+		padding: number
+	): number[] {
+		const offsets: number[] = [padding];
+
+		for(let i = 1; i < sizes.length; i++)
+			offsets.push(offsets[i - 1]! + sizes[i - 1]! + gap);
+
+		return offsets;
+	}
 }

+ 29 - 0
src/v1/optimizer/siguiyama/grid/Grid.ts

@@ -0,0 +1,29 @@
+import Node from "../../../graph/node/Node.js";
+
+export default class Grid<TNode extends Node> {
+	private readonly _cells: Map<string, TNode["id"]> = new Map();
+	private _rows: number = 0;
+	private _cols: number = 0;
+
+	private static key(row: number, col: number): string {
+		return `${row}:${col}`;
+	}
+
+	public set(row: number, col: number, nodeId: TNode["id"]): void {
+		this._cells.set(Grid.key(row, col), nodeId);
+		this._rows = Math.max(this._rows, row + 1);
+		this._cols = Math.max(this._cols, col + 1);
+	}
+
+	public get(row: number, col: number): TNode["id"] | null {
+		return this._cells.get(Grid.key(row, col)) ?? null;
+	}
+
+	public get rows(): number { 
+		return this._rows; 
+	}
+
+	public get cols(): number { 
+		return this._cols; 
+	}
+}