|
|
@@ -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;
|
|
|
+ }
|
|
|
}
|