CoordinateAssignmentStep.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import Edge from "../../graph/edge/Edge.js";
  2. import Graph from "../../graph/Graph.js";
  3. import Layering from "../../graph/layering/Layering.js";
  4. import Node from "../../graph/node/Node.js";
  5. import AlgorithmStep from "../AlgorithmStep.js";
  6. import Grid from "../siguiyama/grid/Grid.js";
  7. import { FeedbackSet, SiguiyamaContext } from "../siguiyama/SiguiyamaContext.js";
  8. export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
  9. private readonly _layerGap: number;
  10. private readonly _nodeGap: number;
  11. public constructor(layerGap: number, nodeGap: number) {
  12. super(CoordinateAssignmentStep.name);
  13. this._layerGap = layerGap;
  14. this._nodeGap = nodeGap;
  15. }
  16. public run(context: SiguiyamaContext): void {
  17. const { graph, layering, feedbackSet } = context;
  18. if(!graph)
  19. throw new Error("Source graph was not found!");
  20. if(!layering)
  21. throw new Error("Layering of graph was not found!");
  22. if(!feedbackSet)
  23. throw new Error("Feedback set was not found!");
  24. this.reverseEdgesInFeedback(graph, feedbackSet);
  25. const grid = this.buildGrid(layering);
  26. this.assignCoordinatesFromGrid(graph, grid);
  27. }
  28. private reverseEdgesInFeedback(graph: Graph<Node,Edge<Node>>, feedbackSet: FeedbackSet) : void {
  29. const edges = graph.getEdges();
  30. for(const edge of edges) {
  31. if(!feedbackSet.includes(edge))
  32. continue;
  33. const from = edge.getFrom(), to = edge.getTo();
  34. edge.setFrom(to).setTo(from);
  35. }
  36. }
  37. private buildGrid(layering: Layering<Node, Edge<Node>>): Grid<Node> {
  38. const grid = new Grid<Node>();
  39. const layers = layering.getLayers();
  40. for(let col = layers.length - 1; col >= 0; col--) {
  41. const layer = layers[col]!;
  42. for(let row = 0; row < layer.nodes.length; row++)
  43. grid.set(row, col, layer.nodes[row]!);
  44. }
  45. return grid;
  46. }
  47. private assignCoordinatesFromGrid(graph: Graph<Node, Edge<Node>>, grid: Grid<Node>): void {
  48. const PADDING = 60;
  49. const colWidths = this.computeColWidths(grid);
  50. const rowHeights = this.computeRowHeights(grid);
  51. const colX = this.computeOffsets(colWidths, this._nodeGap, PADDING);
  52. const rowY = this.computeOffsets(rowHeights, this._layerGap, PADDING);
  53. for(let col = 0; col < grid.cols; col++) {
  54. for(let row = 0; row < grid.rows; row++) {
  55. const node = grid.get(row, col);
  56. if(!node)
  57. continue;
  58. node.setX(colX[col]! + (colWidths[col]! - node.getWidth()) / 2);
  59. node.setY(rowY[row]! + (rowHeights[row]! - node.getHeight()) / 2);
  60. }
  61. }
  62. }
  63. private computeColWidths(grid: Grid<Node>): number[] {
  64. const widths: number[] = new Array(grid.cols).fill(0);
  65. for(let col = 0; col < grid.cols; col++)
  66. for(let row = 0; row < grid.rows; row++) {
  67. const node = grid.get(row, col);
  68. if(!node)
  69. continue;
  70. widths[col] = Math.max(widths[col]!, node.getWidth());
  71. }
  72. return widths;
  73. }
  74. private computeRowHeights(grid: Grid<Node>): number[] {
  75. const heights: number[] = new Array(grid.rows).fill(0);
  76. for(let row = 0; row < grid.rows; row++)
  77. for(let col = 0; col < grid.cols; col++) {
  78. const node = grid.get(row, col);
  79. if(!node) continue;
  80. heights[row] = Math.max(heights[row]!, node.getHeight());
  81. }
  82. return heights;
  83. }
  84. private computeOffsets(sizes: number[], gap: number, padding: number): number[] {
  85. const offsets: number[] = [padding];
  86. for(let i = 1; i < sizes.length; i++)
  87. offsets.push(offsets[i - 1]! + sizes[i - 1]! + gap);
  88. return offsets;
  89. }
  90. }