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/Node.js"; import AlgorithmStep from "../AlgorithmStep.js"; import Grid from "../siguiyama/grid/Grid.js"; import { FeedbackSet, SiguiyamaContext } from "../siguiyama/SiguiyamaContext.js"; export default class CoordinateAssignmentStep extends AlgorithmStep { private readonly _layerGap: number; private readonly _nodeGap: number; public constructor(layerGap: number, nodeGap: number) { super(CoordinateAssignmentStep.name); this._layerGap = layerGap; this._nodeGap = nodeGap; } public run(context: SiguiyamaContext): void { 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 grid = this.buildGrid(layering); this.assignCoordinatesFromGrid(graph, grid); } private reverseEdgesInFeedback(graph: Graph>, feedbackSet: FeedbackSet) : void { const edges = graph.getEdges(); for(const edge of edges) { if(!feedbackSet.includes(edge)) continue; const from = edge.getFrom(), to = edge.getTo(); edge.setFrom(to).setTo(from); } } private buildGrid(layering: Layering>): Grid { const grid = new Grid(); const layers = layering.getLayers(); for(let col = layers.length - 1; col >= 0; col--) { const layer = layers[col]!; for(let row = 0; row < layer.nodes.length; row++) grid.set(row, col, layer.nodes[row]!); } return grid; } private assignCoordinatesFromGrid(graph: Graph>, grid: Grid): void { const PADDING = 60; const colWidths = this.computeColWidths(grid); const rowHeights = this.computeRowHeights(grid); const colX = this.computeOffsets(colWidths, this._nodeGap, PADDING); const rowY = this.computeOffsets(rowHeights, this._layerGap, PADDING); for(let col = 0; col < grid.cols; col++) { for(let row = 0; row < grid.rows; row++) { const node = grid.get(row, col); if(!node) continue; node.setX(colX[col]! + (colWidths[col]! - node.getWidth()) / 2); node.setY(rowY[row]! + (rowHeights[row]! - node.getHeight()) / 2); } } } private computeColWidths(grid: Grid): 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 node = grid.get(row, col); if(!node) continue; widths[col] = Math.max(widths[col]!, node.getWidth()); } return widths; } private computeRowHeights(grid: Grid): 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 node = grid.get(row, col); if(!node) continue; heights[row] = Math.max(heights[row]!, node.getHeight()); } 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; } }