|
|
@@ -1,10 +1,9 @@
|
|
|
-import Edge from "../../graph/Edge.js";
|
|
|
+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.js";
|
|
|
+import Node from "../../graph/node/Node.js";
|
|
|
import AlgorithmStep from "../AlgorithmStep.js";
|
|
|
-import { SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
|
+import { FeedbackSet, SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
|
|
|
|
export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
|
|
|
private readonly _layerGap: number;
|
|
|
@@ -17,19 +16,33 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
|
|
|
}
|
|
|
|
|
|
public run(context: SiguiyamaContext): void {
|
|
|
- const { graph, layering } = context;
|
|
|
+ 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 reversedLayers = layering.getLayers().reverse();
|
|
|
|
|
|
- console.log(graph.getNodes());
|
|
|
this.assignXCoordinates(graph, reversedLayers);
|
|
|
- this.assignYCoordinates(graph, layering, reversedLayers);
|
|
|
- console.log(graph.getNodes());
|
|
|
+ this.assignYCoordinates(graph, reversedLayers);
|
|
|
+ }
|
|
|
+
|
|
|
+ private reverseEdgesInFeedback(graph: Graph<Node,Edge<Node>>, feedbackSet: FeedbackSet) : void {
|
|
|
+ const edges = graph.getEdges();
|
|
|
+
|
|
|
+ for(const edge of edges) {
|
|
|
+ if(!feedbackSet.includes(edge.id))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ const from = edge.to, to = edge.from;
|
|
|
+ graph.updateEdge(edge.id, { ...edge, from, to });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -48,7 +61,7 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
|
|
|
if(!node)
|
|
|
continue;
|
|
|
|
|
|
- node.x = currentX + (layerWidth - node.width) / 2;
|
|
|
+ node.x = currentX;
|
|
|
}
|
|
|
|
|
|
currentX += layerWidth + this._layerGap;
|
|
|
@@ -61,109 +74,18 @@ export default class CoordinateAssignmentStep extends AlgorithmStep<SiguiyamaCon
|
|
|
* @param layering Укладка графа
|
|
|
* @param layers Массив из слоев, по которым распределены вершины
|
|
|
*/
|
|
|
- private assignYCoordinates(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, layers: Layer<Node>[]) : void {
|
|
|
- if(layers.length === 0)
|
|
|
- return;
|
|
|
-
|
|
|
- this.distributeLayerEvenly(graph, layers[0]!);
|
|
|
-
|
|
|
- for(let i = 1; i < layers.length; i++) {
|
|
|
- const layer = layers[i]!;
|
|
|
- this.assignYByBarycenter(graph, layer);
|
|
|
- this.resolveNodeOverlaps(graph, layer);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Равномерное распределение вершин внутри слоя
|
|
|
- * @param graph Граф, содержащий информацию о вершинах и рёбрах
|
|
|
- * @param layer Слой, вершины которого нужно распределить равномерно
|
|
|
- */
|
|
|
- private distributeLayerEvenly(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
|
|
|
- let currentY = 0.0;
|
|
|
-
|
|
|
- for(const nodeId of layer.nodes) {
|
|
|
- const node = graph.getNode(nodeId);
|
|
|
- if(!node)
|
|
|
- continue;
|
|
|
-
|
|
|
- node.y = currentY;
|
|
|
- currentY += node.height + this._nodeGap;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Назначение Y-координаты для вершин внутрия слоя с выравниванием по barycenter соседних вершин
|
|
|
- * @param graph Граф, содержащий информацию о вершинах и рёбрах
|
|
|
- * @param layer Слой, вершины которого нужно распределить и выровнять
|
|
|
- */
|
|
|
- private assignYByBarycenter(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
|
|
|
- const assignments: { nodeId: Node["id"], y: number }[] = [];
|
|
|
-
|
|
|
- for(const nodeId of layer.nodes) {
|
|
|
- const node = graph.getNode(nodeId);
|
|
|
- if(!node)
|
|
|
- continue;
|
|
|
+ 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) {
|
|
|
+ const node = graph.getNode(nodeId);
|
|
|
+ if(!node)
|
|
|
+ continue;
|
|
|
|
|
|
- const predecessors = graph.getNodeInputs(nodeId);
|
|
|
- if(predecessors.length === 0) {
|
|
|
- assignments.push({ nodeId, y: node.y ?? 0 });
|
|
|
- continue;
|
|
|
+ node.y = currentY;
|
|
|
+ currentY += node.height + this._nodeGap;
|
|
|
}
|
|
|
-
|
|
|
- const averageY = predecessors.reduce((sum, pred) => sum + (pred.y ?? 0) + (pred.height ?? 0) / 2, 0) / predecessors.length;
|
|
|
- assignments.push({ nodeId, y: averageY - node.height / 2});
|
|
|
- }
|
|
|
-
|
|
|
- assignments.sort((a, b) => a.y - b.y);
|
|
|
-
|
|
|
- for(const { nodeId, y } of assignments) {
|
|
|
- const node = graph.getNode(nodeId);
|
|
|
- if(!node)
|
|
|
- continue;
|
|
|
-
|
|
|
- node.y = y;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Разрешение конфликтов, связанных с перекрытием вершин друг другом
|
|
|
- * @param graph Граф, содержащий информацию о вершинах и рёбрах
|
|
|
- * @param layer Слой, в котором необходимо разрешить конфликты перекрытия вершин
|
|
|
- */
|
|
|
- private resolveNodeOverlaps(graph: Graph<Node, Edge<Node>>, layer: Layer<Node>) : void {
|
|
|
- const nodes = layer.nodes.map((nodeId) => graph.getNode(nodeId)).filter((node) => node !== null).sort((a, b) => (a.y ?? 0) - (b.y ?? 0));
|
|
|
-
|
|
|
- for(let i = 1; i < nodes.length; i++) {
|
|
|
- const previous = nodes[i - 1], current = nodes[i];
|
|
|
- const minY = (previous?.y ?? 0) + (previous?.height ?? 0) + this._nodeGap;
|
|
|
-
|
|
|
- if(current && (current.y ?? 0) < minY)
|
|
|
- current.y = minY;
|
|
|
}
|
|
|
-
|
|
|
- this.centerNodesVertically(nodes);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * Центрирование вершин в слое по вертикали
|
|
|
- * @param nodes Вершины, которые надо отцентровать
|
|
|
- */
|
|
|
- private centerNodesVertically(nodes: Node[]) : void {
|
|
|
- if(!nodes || nodes.length == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- const totalHeight = nodes.reduce((sum, node) => sum + (node.height ?? 0), 0) + this._nodeGap * (nodes.length - 1);
|
|
|
-
|
|
|
- const firstY = nodes[0]?.y ?? 0;
|
|
|
- const offset = firstY - totalHeight / 2;
|
|
|
-
|
|
|
- const eps = 1.0;
|
|
|
- if(Math.abs(offset) < eps)
|
|
|
- return;
|
|
|
-
|
|
|
- for(const node of nodes)
|
|
|
- node.y = (node.y ?? 0) - offset;
|
|
|
}
|
|
|
}
|