From 74b69bca81090c2a876cd4dffaf21a568d1f5b2e Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Thu, 15 Aug 2024 12:28:58 +0200 Subject: [PATCH 01/20] Make sure that the order of incoming ports matches the order given by the previous layer. Updated WEST side handling to fix this and fixed typo as well and makes port model order non constraining for input ports. --- .../ModelOrderPortComparator.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index 20241c4a4..db003b5e6 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -90,7 +90,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { LPort p1 = originalP1; LPort p2 = originalP2; - if (portModelOrder && p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST) { + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST) { // Both nodes have the same port side. // Hence I need to determine their ordering based on the side. // WEST sort descending @@ -134,19 +134,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { // Sort incoming edges by sorting their ports by the order of the nodes they connect to. if (!p1.getIncomingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { - // If port order is used instead of edge order, consult it to make decisions. - // If not both ports have a model order fall back to the edge order. - if (portModelOrder) { - int result = checkPortModelOrder(p1, p2); - if (result != 0) { - if (result == -1) { - updateBiggerAndSmallerAssociations(p2, p1); - } else if (result == 1) { - updateBiggerAndSmallerAssociations(p1, p2); - } - return result; - } - } + LNode p1Node = p1.getIncomingEdges().get(0).getSource().getNode(); LNode p2Node = p2.getIncomingEdges().get(0).getSource().getNode(); if (p1Node.equals(p2Node)) { @@ -160,6 +148,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { // In this case both incoming edges must have a model order set. Check it. return Integer.compare(p1MO, p2MO); } + // Check which of the nodes connects first to the previous layer. for (LNode previousNode : previousLayer) { if (previousNode.equals(p1Node)) { updateBiggerAndSmallerAssociations(p1, p2); @@ -169,6 +158,20 @@ public int compare(final LPort originalP1, final LPort originalP2) { return -1; } } + // If both ports do not connect to the previous layer, use the port model order. + // If port order is used instead of edge order, consult it to make decisions. + // If not both ports have a model order fall back to the edge order. + if (portModelOrder) { + int result = checkPortModelOrder(p1, p2); + if (result != 0) { + if (result == -1) { + updateBiggerAndSmallerAssociations(p2, p1); + } else if (result == 1) { + updateBiggerAndSmallerAssociations(p1, p2); + } + return result; + } + } } // Sort outgoing edges by sorting their ports based on the model order of their edges. @@ -210,10 +213,10 @@ public int compare(final LPort originalP1, final LPort originalP2) { p1Order = p1.getOutgoingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); } if (p2.getOutgoingEdges().get(0).hasProperty(InternalProperties.MODEL_ORDER)) { - p2Order = p1.getOutgoingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); + p2Order = p2.getOutgoingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); } - // Same target node + // If both ports have the same target nodes, make sure that the backward edge is below the normal edge. if (p1TargetNode != null && p1TargetNode.equals(p2TargetNode)) { // Backward edges below if (p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) @@ -225,6 +228,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { updateBiggerAndSmallerAssociations(p2, p1); return -1; } + // If both edges are reversed or not reversed, just use their model order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); } else { @@ -232,6 +236,8 @@ public int compare(final LPort originalP1, final LPort originalP2) { } return Integer.compare(p1Order, p2Order); } + // Use precomputed ordering value if possible. + // FIXME why do I need this? if (targetNodeModelOrder != null) { if (targetNodeModelOrder.containsKey(p1TargetNode)) { p1Order = targetNodeModelOrder.get(p1TargetNode); @@ -240,6 +246,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { p2Order = targetNodeModelOrder.get(p2TargetNode); } } + // If the nodes have different targets just use their order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); } else { From a366479f74f443d0f982a34ba136de7a374af125 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 10:49:48 +0200 Subject: [PATCH 02/20] Fixed all but one case for port model order and feedback nodes. --- .../NorthSouthPortPreprocessor.java | 28 +++- .../SortByInputModelProcessor.java | 3 + .../ModelOrderNodeComparator.java | 141 ++++++++++++++++-- .../ModelOrderPortComparator.java | 119 +++++++++++---- 4 files changed, 248 insertions(+), 43 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/NorthSouthPortPreprocessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/NorthSouthPortPreprocessor.java index 19259c0a2..374666e7d 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/NorthSouthPortPreprocessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/NorthSouthPortPreprocessor.java @@ -23,6 +23,7 @@ import org.eclipse.elk.alg.layered.graph.Layer; import org.eclipse.elk.alg.layered.options.InternalProperties; import org.eclipse.elk.alg.layered.options.LayeredOptions; +import org.eclipse.elk.alg.layered.options.OrderingStrategy; import org.eclipse.elk.core.alg.ILayoutProcessor; import org.eclipse.elk.core.options.PortConstraints; import org.eclipse.elk.core.options.PortSide; @@ -169,7 +170,8 @@ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor } // Sort the port list if we have control over the port order - if (!node.getProperty(LayeredOptions.PORT_CONSTRAINTS).isOrderFixed()) { + if (!node.getProperty(LayeredOptions.PORT_CONSTRAINTS).isOrderFixed() + && node.getGraph().getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) == OrderingStrategy.NONE) { sortPortList(node); } @@ -188,6 +190,10 @@ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor LinkedList portList = Lists.newLinkedList(); Iterables.addAll(portList, node.getPorts(PortSide.NORTH)); + if (node.getGraph().getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) { + portList = modelOrderNorthSouthInputReversing(portList, node); + } + createDummyNodes(layeredGraph, portList, northDummyNodes, southDummyNodes, barycenterAssociates); @@ -241,6 +247,10 @@ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor portList.addFirst(port); } + if (node.getGraph().getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) { + portList = modelOrderNorthSouthInputReversing(portList, node); + } + createDummyNodes(layeredGraph, portList, southDummyNodes, null, barycenterAssociates); @@ -358,6 +368,22 @@ private void sortPortList(final LNode node) { } }); } + + private LinkedList modelOrderNorthSouthInputReversing(LinkedList portList, LNode node) { + // Reverse port list if edges are incoming edges. + LinkedList incoming = new LinkedList(); + LinkedList outgoing = new LinkedList(); + for (LPort port : portList) { + if (!port.getIncomingEdges().isEmpty()) { + // Incoming edge + incoming.add(port); + } else { + outgoing.add(port); + } + } + Lists.reverse(incoming).addAll(outgoing); + return incoming; + } // ///////////////////////////////////////////////////////////////////////////// // DUMMY NODE CREATION diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index 5ee21df8b..cfcf22fb5 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -55,6 +55,9 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), 1); int layerIndex = 0; for (Layer layer : graph) { + // The layer.id is necessary to check whether nodes really connect to the previous layer. + // Feedback long edge dummies do might have a long-edge dummy in the same layer. + layer.id = layerIndex; final int previousLayerIndex = layerIndex == 0 ? 0 : layerIndex - 1; Layer previousLayer = graph.getLayers().get(previousLayerIndex); for (LNode node : layer.getNodes()) { diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index bd429dc2a..fda116ba3 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -15,6 +15,7 @@ import org.eclipse.elk.alg.layered.graph.LEdge; import org.eclipse.elk.alg.layered.graph.LNode; +import org.eclipse.elk.alg.layered.graph.LNode.NodeType; import org.eclipse.elk.alg.layered.graph.LPort; import org.eclipse.elk.alg.layered.graph.Layer; import org.eclipse.elk.alg.layered.options.InternalProperties; @@ -175,23 +176,43 @@ public int compare(final LNode n1, final LNode n2) { } // One node has no source port - if (!n1.hasProperty(InternalProperties.MODEL_ORDER) || !n2.hasProperty(InternalProperties.MODEL_ORDER)) { - int n1ModelOrder = getModelOrderFromConnectedEdges(n1); - int n2ModelOrder = getModelOrderFromConnectedEdges(n2); - if (n1ModelOrder > n2ModelOrder) { - updateBiggerAndSmallerAssociations(n1, n2); - } else { - updateBiggerAndSmallerAssociations(n2, n1); + if (p1SourcePort != null && p2SourcePort == null || p1SourcePort == null && p2SourcePort != null) { + // Check whether one of them is a helper dummy node. + int comparedWithLongEdgeFeedback = handleHelperDummyNodes(n1, n2); + if (comparedWithLongEdgeFeedback != 0) { + return comparedWithLongEdgeFeedback; + } + + // Otherwise use the model order of the connected edges if one of them is no dummy node. + if (!n1.hasProperty(InternalProperties.MODEL_ORDER) || !n2.hasProperty(InternalProperties.MODEL_ORDER)) { + int n1ModelOrder = getModelOrderFromConnectedEdges(n1); + int n2ModelOrder = getModelOrderFromConnectedEdges(n2); + if (n1ModelOrder > n2ModelOrder) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } + return Integer.compare( + n1ModelOrder, + n2ModelOrder); + } + } + + // Both nodes are not connected to the previous layer + if (p1SourcePort == null && p2SourcePort == null) { + // Check whether one of them is a helper dummy node. + int comparedWithLongEdgeFeedback = handleHelperDummyNodes(n1, n2); + if (comparedWithLongEdgeFeedback != 0) { + return comparedWithLongEdgeFeedback; } - return Integer.compare( - n1ModelOrder, - n2ModelOrder); } + // Fall through case. // Both nodes are not connected to the previous layer. Therefore, they must be normal nodes. // The model order shall be used to order them. } // Order nodes by their order in the model. + // This is also the fallback case if one of the nodes is not connected to the previous layer. int n1ModelOrder = n1.getProperty(InternalProperties.MODEL_ORDER); int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER); if (n1ModelOrder > n2ModelOrder) { @@ -247,4 +268,104 @@ private void updateBiggerAndSmallerAssociations(final LNode bigger, final LNode biggerThan.get(veryBig).addAll(smallerNodeBiggerThan); } } + + private int handleHelperDummyNodes(LNode n1, LNode n2) { + if (n1.getType() == NodeType.LONG_EDGE && n2.getType() == NodeType.NORMAL) { + // n1 is a long edge node feedback node. + + LPort dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n1); + LNode dummyNodeSourceNode = dummyNodeSourcePort.getNode(); + // Case the source of the dummy is the same node as n2, than the dummy node is routed below. + if (dummyNodeSourceNode.equals(n2)) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } else { + // Calculate whether the dummy node leads to the target node. + LPort dummyNodeTargetPort = getFirstOutgoingSourcePortOfNode(n1); + LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); + if (dummyNodeTargetNode.equals(n2)) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } + + // If the two nodes are not the same, order them based on the source model order. + return this.compare(dummyNodeSourceNode, n2); + } + } else if (n1.getType() == NodeType.NORMAL && n2.getType() == NodeType.LONG_EDGE) { + // n2 is a long edge node feedback node. + LPort dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n2); + LNode dummyNodeSourceNode = dummyNodeSourcePort.getNode(); + // Case the source of the dummy is the same node as n2, than the dummy node is routed below. + if (dummyNodeSourceNode.equals(n1)) { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } else { + // Calculate whether the dummy node leads to the target node. + LPort dummyNodeTargetPort = getFirstOutgoingSourcePortOfNode(n2); + LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); + if (dummyNodeTargetNode.equals(n1)) { + updateBiggerAndSmallerAssociations(n2, n1); + return 1; + } + + // If the two nodes are not the same, order them based on the source model order. + return this.compare(n1, dummyNodeSourceNode); + } + } else if (n1.getType() == NodeType.LONG_EDGE && n2.getType() == NodeType.LONG_EDGE) { + // both are long edge feedback nodes. + LPort n1dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n1); + LPort n2dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n2); + // Case both are on the same node, sort them in reverse order of their ports. + if (n1dummyNodeSourcePort.getNode().equals(n2dummyNodeSourcePort.getNode())) { + // Find the first port that occurs on the node. Since it has to be a WEST port (check this) reverse + // the order. + for (LPort port : n1dummyNodeSourcePort.getNode().getPorts()) { + if (n1dummyNodeSourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } else if (n2dummyNodeSourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } + } + } + + // Case both edges connect to separate nodes. + // In this case comapare the order of their nodes in their layer. + LNode n1dummyNodeSourceNode = n1dummyNodeSourcePort.getNode(); + LNode n2dummyNodeSourceNode = n2dummyNodeSourcePort.getNode(); + Layer dummySourceLayer = n1dummyNodeSourceNode.getLayer(); + // Find the first node that occurs in the layer and order the nodes in reverse. + for (LNode node : dummySourceLayer) { + if (n1dummyNodeSourceNode.equals(node)) { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } else if (n2dummyNodeSourceNode.equals(node)) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } + } + // This cannot occur. + return 0; + } else { + // These nodes are just two normal nodes + return 0; + } + } + + private LPort getFirstIncomingPortOfNode(LNode node) { + return node.getPorts().stream().filter(p -> !p.getIncomingEdges().isEmpty()).findFirst().orElse(null); + } + + private LPort getFirstIncomingSourcePortOfNode(LNode node) { + return getFirstIncomingPortOfNode(node).getIncomingEdges().get(0).getSource(); + } + + private LPort getFirstOutgoingPortOfNode(LNode node) { + return node.getPorts().stream().filter(p -> !p.getOutgoingEdges().isEmpty()).findFirst().orElse(null); + } + + private LPort getFirstOutgoingSourcePortOfNode(LNode node) { + return getFirstOutgoingPortOfNode(node).getOutgoingEdges().get(0).getTarget(); + } } diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index db003b5e6..4f2361db4 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -9,14 +9,17 @@ *******************************************************************************/ package org.eclipse.elk.alg.layered.intermediate.preserveorder; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.elk.alg.layered.graph.LNode; import org.eclipse.elk.alg.layered.graph.LPort; import org.eclipse.elk.alg.layered.graph.Layer; +import org.eclipse.elk.alg.layered.graph.LNode.NodeType; import org.eclipse.elk.alg.layered.options.InternalProperties; import org.eclipse.elk.alg.layered.options.OrderingStrategy; import org.eclipse.elk.core.options.PortSide; @@ -90,16 +93,6 @@ public int compare(final LPort originalP1, final LPort originalP2) { LPort p1 = originalP1; LPort p2 = originalP2; - if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST) { - // Both nodes have the same port side. - // Hence I need to determine their ordering based on the side. - // WEST sort descending - // EAST, NORTH, SOUTH sort ascending - // Hence I exchange p1 and p2 in the WEST case. - LPort temp = p1; - p1 = p2; - p2 = temp; - } if (!biggerThan.containsKey(p1)) { biggerThan.put(p1, new HashSet<>()); } else if (biggerThan.get(p1).contains(p2)) { @@ -135,9 +128,26 @@ public int compare(final LPort originalP1, final LPort originalP2) { // Sort incoming edges by sorting their ports by the order of the nodes they connect to. if (!p1.getIncomingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { - LNode p1Node = p1.getIncomingEdges().get(0).getSource().getNode(); - LNode p2Node = p2.getIncomingEdges().get(0).getSource().getNode(); + LPort p1SourcePort = p1.getIncomingEdges().get(0).getSource(); + LPort p2SourcePort = p2.getIncomingEdges().get(0).getSource(); + LNode p1Node = p1SourcePort.getNode(); + LNode p2Node = p2SourcePort.getNode(); + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST && p1SourcePort.getSide() != PortSide.SOUTH + && p2SourcePort.getSide() != PortSide.SOUTH + || p1.getSide() == PortSide.NORTH && p2.getSide() == PortSide.NORTH && p1SourcePort.getSide() != PortSide.SOUTH + && p2SourcePort.getSide() != PortSide.SOUTH + || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH && p1SourcePort.getSide() != PortSide.SOUTH + && p2SourcePort.getSide() != PortSide.SOUTH + || p1.getSide() == PortSide.EAST && p2.getSide() == PortSide.EAST && p1SourcePort.getSide() != PortSide.SOUTH + && p2SourcePort.getSide() != PortSide.SOUTH) { + // Some ports are ordered in the way around. + // Previously this did not matter, since the north south processor did override the ordering. + LPort temp = p1; + p1 = p2; + p2 = temp; + } if (p1Node.equals(p2Node)) { + int p1MO = p1.getIncomingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); int p2MO = p2.getIncomingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); if (p1MO > p2MO) { @@ -148,16 +158,30 @@ public int compare(final LPort originalP1, final LPort originalP2) { // In this case both incoming edges must have a model order set. Check it. return Integer.compare(p1MO, p2MO); } - // Check which of the nodes connects first to the previous layer. - for (LNode previousNode : previousLayer) { - if (previousNode.equals(p1Node)) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; - } else if (previousNode.equals(p2Node)) { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + // If both ports connect to long edges in the same layer, reverse the order. + if (p1SourcePort.getNode().getType() == NodeType.LONG_EDGE + && p2SourcePort.getNode().getType() == NodeType.LONG_EDGE + && p1Node.getLayer().id == p2Node.getLayer().id && p1Node.getLayer().id == p1.getNode().getLayer().id) { + // _ + // n1 n2_| + // || || + // |__2__|| + // ___1___| + // + // The difference to the other cases above is that a EAST port uses dummy nodes, while a + // NORTH or SOUTH port uses none since they are later created by the NorthSouthProcessor. + Layer previousLayer = p1Node.getLayer(); + int inPreviousLayer = checkReferenceLayer(previousLayer, p1Node, p2Node, p1, p2); + if (inPreviousLayer != 0) { + return inPreviousLayer; } } + + // Check which of the nodes connects first to the previous layer. + int inPreviousLayer = checkReferenceLayer(Arrays.stream(previousLayer).collect(Collectors.toList()), p2Node, p1Node, p2, p1); + if (inPreviousLayer != 0) { + return inPreviousLayer; + } // If both ports do not connect to the previous layer, use the port model order. // If port order is used instead of edge order, consult it to make decisions. // If not both ports have a model order fall back to the edge order. @@ -176,6 +200,14 @@ public int compare(final LPort originalP1, final LPort originalP2) { // Sort outgoing edges by sorting their ports based on the model order of their edges. if (!p1.getOutgoingEdges().isEmpty() && !p2.getOutgoingEdges().isEmpty()) { + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST + || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { + // Some ports are ordered in the way around. + // Previously this did not matter, since the north south processor did override the ordering. + LPort temp = p1; + p1 = p2; + p2 = temp; + } LNode p1TargetNode = p1.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); LNode p2TargetNode = p2.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); @@ -218,16 +250,16 @@ public int compare(final LPort originalP1, final LPort originalP2) { // If both ports have the same target nodes, make sure that the backward edge is below the normal edge. if (p1TargetNode != null && p1TargetNode.equals(p2TargetNode)) { - // Backward edges below - if (p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) - && !p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; - } else if (!p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) - && p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; - } +// // Backward edges below +// if (p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) +// && !p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { +// updateBiggerAndSmallerAssociations(p1, p2); +// return 1; +// } else if (!p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) +// && p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { +// updateBiggerAndSmallerAssociations(p2, p1); +// return -1; +// } // If both edges are reversed or not reversed, just use their model order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); @@ -236,8 +268,8 @@ public int compare(final LPort originalP1, final LPort originalP2) { } return Integer.compare(p1Order, p2Order); } - // Use precomputed ordering value if possible. - // FIXME why do I need this? + // Use precomputed ordering value if possible to utilize order inheritence of edges connected to a node. + // This allows to bundle edges leading to the same node, disregarding their model order. if (targetNodeModelOrder != null) { if (targetNodeModelOrder.containsKey(p1TargetNode)) { p1Order = targetNodeModelOrder.get(p1TargetNode); @@ -319,6 +351,29 @@ private void updateBiggerAndSmallerAssociations(final LPort bigger, final LPort } } + /** + * Given a previous layer, check which of the two reference nodes of a port is the first in it. + * Updates the bigger/smaller association. + * @param layer The layer to check + * @param p1Node The reference node of port p1 + * @param p2Node The reference node of port p2 + * @param p1 The first port + * @param p2 The second port + * @return A comparator value showing which port should be first. + */ + private int checkReferenceLayer(Iterable layer, LNode p1Node, LNode p2Node, LPort p1, LPort p2) { + for (LNode node : layer) { + if (node.equals(p1Node)) { + updateBiggerAndSmallerAssociations(p2, p1); + return -1; + } else if (node.equals(p2Node)) { + updateBiggerAndSmallerAssociations(p1, p2); + return 1; + } + } + return 0; // Would never happen. + } + private class PortSideComparator implements Comparator { /* (non-Javadoc) From e4b1d04298aad8bcc501edb331d0a25155da7bc2 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 11:19:16 +0200 Subject: [PATCH 03/20] Check whether model order exists and do not sort in this case. --- .../ModelOrderNodeComparator.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index fda116ba3..a818f3105 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -213,16 +213,20 @@ public int compare(final LNode n1, final LNode n2) { } // Order nodes by their order in the model. // This is also the fallback case if one of the nodes is not connected to the previous layer. - int n1ModelOrder = n1.getProperty(InternalProperties.MODEL_ORDER); - int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER); - if (n1ModelOrder > n2ModelOrder) { - updateBiggerAndSmallerAssociations(n1, n2); + if (n1.hasProperty(InternalProperties.MODEL_ORDER) && n2.hasProperty(InternalProperties.MODEL_ORDER)) { + int n1ModelOrder = n1.getProperty(InternalProperties.MODEL_ORDER); + int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER); + if (n1ModelOrder > n2ModelOrder) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } + return Integer.compare( + n1ModelOrder, + n2ModelOrder); } else { - updateBiggerAndSmallerAssociations(n2, n1); + return 0; } - return Integer.compare( - n1ModelOrder, - n2ModelOrder); } /** From cf3911ef7d2301a0166e11ffd2769d26d7b96863 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 11:24:36 +0200 Subject: [PATCH 04/20] Removed comment for unnecessary backward edges below. --- .../preserveorder/ModelOrderPortComparator.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index 4f2361db4..72b228edc 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -250,16 +250,6 @@ public int compare(final LPort originalP1, final LPort originalP2) { // If both ports have the same target nodes, make sure that the backward edge is below the normal edge. if (p1TargetNode != null && p1TargetNode.equals(p2TargetNode)) { -// // Backward edges below -// if (p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) -// && !p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { -// updateBiggerAndSmallerAssociations(p1, p2); -// return 1; -// } else if (!p1.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED) -// && p2.getOutgoingEdges().get(0).getProperty(InternalProperties.REVERSED)) { -// updateBiggerAndSmallerAssociations(p2, p1); -// return -1; -// } // If both edges are reversed or not reversed, just use their model order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); From 3b8cc6f15d01bf49a508d7fc3fec60e4e38f8fa5 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 12:26:23 +0200 Subject: [PATCH 05/20] Do insertionsort to handle comparator errors. --- .../SortByInputModelProcessor.java | 26 ++++++++++++++++--- .../ModelOrderNodeComparator.java | 23 +++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index cfcf22fb5..47dc57114 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -10,7 +10,9 @@ package org.eclipse.elk.alg.layered.intermediate; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.elk.alg.layered.graph.LEdge; @@ -78,10 +80,11 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito } } // Sort nodes. - Collections.sort(layer.getNodes(), - new ModelOrderNodeComparator(previousLayer, - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY))); + ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); + SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); + progressMonitor.log("Layer " + layerIndex + ": " + layer); layerIndex++; } @@ -141,5 +144,20 @@ public static LNode getTargetNode(final LPort port) { } while (node != null && node.getType() != NodeType.NORMAL); return node; } + + public static void insertionSort(final List layer, + final ModelOrderNodeComparator comparator) { + LNode temp; + for (int i = 1; i < layer.size(); i++) { + temp = layer.get(i); + int j = i; + while (j > 0 && comparator.compare(layer.get(j - 1), temp) > 0) { + layer.set(j, layer.get(j - 1)); + j--; + } + layer.set(j, temp); + } + comparator.clearTransitiveOrdering(); + } } diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index a818f3105..e820d4cde 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -180,6 +180,11 @@ public int compare(final LNode n1, final LNode n2) { // Check whether one of them is a helper dummy node. int comparedWithLongEdgeFeedback = handleHelperDummyNodes(n1, n2); if (comparedWithLongEdgeFeedback != 0) { + if (comparedWithLongEdgeFeedback > 0) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } return comparedWithLongEdgeFeedback; } @@ -203,6 +208,11 @@ public int compare(final LNode n1, final LNode n2) { // Check whether one of them is a helper dummy node. int comparedWithLongEdgeFeedback = handleHelperDummyNodes(n1, n2); if (comparedWithLongEdgeFeedback != 0) { + if (comparedWithLongEdgeFeedback > 0) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } return comparedWithLongEdgeFeedback; } } @@ -218,12 +228,11 @@ public int compare(final LNode n1, final LNode n2) { int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER); if (n1ModelOrder > n2ModelOrder) { updateBiggerAndSmallerAssociations(n1, n2); + return 1; } else { updateBiggerAndSmallerAssociations(n2, n1); + return -1; } - return Integer.compare( - n1ModelOrder, - n2ModelOrder); } else { return 0; } @@ -372,4 +381,12 @@ private LPort getFirstOutgoingPortOfNode(LNode node) { private LPort getFirstOutgoingSourcePortOfNode(LNode node) { return getFirstOutgoingPortOfNode(node).getOutgoingEdges().get(0).getTarget(); } + + /** + * Clears the transitive ordering. + */ + public void clearTransitiveOrdering() { + this.biggerThan = new HashMap<>(); + this.smallerThan = new HashMap<>(); + } } From f6b1dbc250073f118e002db3acdf484cb7c07706 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 13:22:35 +0200 Subject: [PATCH 06/20] Fixed typos in node comparator. --- .../preserveorder/ModelOrderNodeComparator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index e820d4cde..268bbb604 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -117,7 +117,7 @@ public int compare(final LNode n1, final LNode n2) { for (LPort p : n1.getPorts()) { // Get the first port that actually connects to a previous layer. if (!p.getIncomingEdges().isEmpty()) { - if (p.getIncomingEdges().get(0).getSource().getNode().getLayer() != n1.getLayer()) { + if (p.getIncomingEdges().get(0).getSource().getNode().getLayer().id == (n1.getLayer().id - 1)) { p1SourcePort = p.getIncomingEdges().get(0).getSource(); } } @@ -127,7 +127,7 @@ public int compare(final LNode n1, final LNode n2) { for (LPort p : n2.getPorts()) { // Get the first port that actually connects to a previous layer. if (!p.getIncomingEdges().isEmpty()) { - if (p.getIncomingEdges().get(0).getSource().getNode().getLayer() != n2.getLayer()) { + if (p.getIncomingEdges().get(0).getSource().getNode().getLayer().id == (n2.getLayer().id - 1)) { p2SourcePort = p.getIncomingEdges().get(0).getSource(); } } @@ -318,7 +318,7 @@ private int handleHelperDummyNodes(LNode n1, LNode n2) { LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); if (dummyNodeTargetNode.equals(n1)) { updateBiggerAndSmallerAssociations(n2, n1); - return 1; + return -1; } // If the two nodes are not the same, order them based on the source model order. From 68691d8592ac4744e33a4ea8404e67d9ef1e9a72 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Fri, 16 Aug 2024 13:34:48 +0200 Subject: [PATCH 07/20] Always use insertion sort and more small fixes. --- .../SortByInputModelProcessor.java | 18 +++++- .../ModelOrderPortComparator.java | 61 +++++++++++-------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index 47dc57114..6f76ae8f2 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -70,8 +70,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito // Therefore all ports that connect to the same node should have the same // (their minimal) model order. // Get minimal model order for target node - - Collections.sort(node.getPorts(), + insertionSortPorts(node.getPorts(), new ModelOrderPortComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), longEdgeTargetNodePreprocessing(node), @@ -159,5 +158,20 @@ public static void insertionSort(final List layer, } comparator.clearTransitiveOrdering(); } + + public static void insertionSortPorts(final List ports, + final ModelOrderPortComparator comparator) { + LPort temp; + for (int i = 1; i < ports.size(); i++) { + temp = ports.get(i); + int j = i; + while (j > 0 && comparator.compare(ports.get(j - 1), temp) > 0) { + ports.set(j, ports.get(j - 1)); + j--; + } + ports.set(j, temp); + } + comparator.clearTransitiveOrdering(); + } } diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index 72b228edc..70974120d 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -127,36 +127,32 @@ public int compare(final LPort originalP1, final LPort originalP2) { // Sort incoming edges by sorting their ports by the order of the nodes they connect to. if (!p1.getIncomingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { - - LPort p1SourcePort = p1.getIncomingEdges().get(0).getSource(); - LPort p2SourcePort = p2.getIncomingEdges().get(0).getSource(); - LNode p1Node = p1SourcePort.getNode(); - LNode p2Node = p2SourcePort.getNode(); - if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST && p1SourcePort.getSide() != PortSide.SOUTH - && p2SourcePort.getSide() != PortSide.SOUTH - || p1.getSide() == PortSide.NORTH && p2.getSide() == PortSide.NORTH && p1SourcePort.getSide() != PortSide.SOUTH - && p2SourcePort.getSide() != PortSide.SOUTH - || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH && p1SourcePort.getSide() != PortSide.SOUTH - && p2SourcePort.getSide() != PortSide.SOUTH - || p1.getSide() == PortSide.EAST && p2.getSide() == PortSide.EAST && p1SourcePort.getSide() != PortSide.SOUTH - && p2SourcePort.getSide() != PortSide.SOUTH) { + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST + || p1.getSide() == PortSide.NORTH && p2.getSide() == PortSide.NORTH + || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { // Some ports are ordered in the way around. // Previously this did not matter, since the north south processor did override the ordering. LPort temp = p1; p1 = p2; p2 = temp; } + + LPort p1SourcePort = p1.getIncomingEdges().get(0).getSource(); + LPort p2SourcePort = p2.getIncomingEdges().get(0).getSource(); + LNode p1Node = p1SourcePort.getNode(); + LNode p2Node = p2SourcePort.getNode(); if (p1Node.equals(p2Node)) { - - int p1MO = p1.getIncomingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); - int p2MO = p2.getIncomingEdges().get(0).getProperty(InternalProperties.MODEL_ORDER); - if (p1MO > p2MO) { - updateBiggerAndSmallerAssociations(p1, p2); - } else { - updateBiggerAndSmallerAssociations(p2, p1); + // If both connect to the same node, check their occurrence order since these ports have to be handled + // first and should hence be sorted. + for (LPort port : p1Node.getPorts()) { + if (p1SourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(p2, p1); + return -1; + } else if (p2SourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(p1, p2); + return 1; + } } - // In this case both incoming edges must have a model order set. Check it. - return Integer.compare(p1MO, p2MO); } // If both ports connect to long edges in the same layer, reverse the order. if (p1SourcePort.getNode().getType() == NodeType.LONG_EDGE @@ -168,17 +164,20 @@ public int compare(final LPort originalP1, final LPort originalP2) { // |__2__|| // ___1___| // - // The difference to the other cases above is that a EAST port uses dummy nodes, while a - // NORTH or SOUTH port uses none since they are later created by the NorthSouthProcessor. Layer previousLayer = p1Node.getLayer(); int inPreviousLayer = checkReferenceLayer(previousLayer, p1Node, p2Node, p1, p2); if (inPreviousLayer != 0) { + if (inPreviousLayer > 0) { + updateBiggerAndSmallerAssociations(p1, p2); + } else { + updateBiggerAndSmallerAssociations(p1, p2); + } return inPreviousLayer; } } // Check which of the nodes connects first to the previous layer. - int inPreviousLayer = checkReferenceLayer(Arrays.stream(previousLayer).collect(Collectors.toList()), p2Node, p1Node, p2, p1); + int inPreviousLayer = checkReferenceLayer(Arrays.stream(previousLayer).collect(Collectors.toList()), p1Node, p2Node, p1, p2); if (inPreviousLayer != 0) { return inPreviousLayer; } @@ -188,9 +187,9 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (portModelOrder) { int result = checkPortModelOrder(p1, p2); if (result != 0) { - if (result == -1) { + if (result < 0) { updateBiggerAndSmallerAssociations(p2, p1); - } else if (result == 1) { + } else if (result > 0) { updateBiggerAndSmallerAssociations(p1, p2); } return result; @@ -375,4 +374,12 @@ public int compare(final PortSide ps1, final PortSide ps2) { } } + + /** + * Clears the transitive ordering. + */ + public void clearTransitiveOrdering() { + this.biggerThan = new HashMap<>(); + this.smallerThan = new HashMap<>(); + } } From 291247c88134e3eeb736f917cb008b59da2a99aa Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 12:25:52 +0200 Subject: [PATCH 08/20] Sort nodes before sorting ports since in-layer feedback dummies exist. --- .../SortByInputModelProcessor.java | 7 +- .../ModelOrderPortComparator.java | 67 ++++++++++++------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index 6f76ae8f2..8a8b234ca 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -62,6 +62,11 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito layer.id = layerIndex; final int previousLayerIndex = layerIndex == 0 ? 0 : layerIndex - 1; Layer previousLayer = graph.getLayers().get(previousLayerIndex); + // Sort nodes before port sorting to have sorted nodes for in-layer feedback edge dummies. + ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); + SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); for (LNode node : layer.getNodes()) { if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER && node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_POS) { @@ -79,7 +84,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito } } // Sort nodes. - ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, + comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index 70974120d..16457bd58 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -116,18 +116,17 @@ public int compare(final LPort originalP1, final LPort originalP2) { // Sort nodes by their ports sides NORTH < EAST < SOUTH < WEST. if (p1.getSide() != p2.getSide()) { int result = new PortSideComparator().compare(p1.getSide(), p2.getSide()); - - if (result == -1) { - updateBiggerAndSmallerAssociations(p2, p1); - } else { + if (result > 0) { updateBiggerAndSmallerAssociations(p1, p2); + } else { + updateBiggerAndSmallerAssociations(p2, p1); } return result; } // Sort incoming edges by sorting their ports by the order of the nodes they connect to. if (!p1.getIncomingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { - if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST || p1.getSide() == PortSide.NORTH && p2.getSide() == PortSide.NORTH || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { // Some ports are ordered in the way around. @@ -165,21 +164,35 @@ public int compare(final LPort originalP1, final LPort originalP2) { // ___1___| // Layer previousLayer = p1Node.getLayer(); + // if it is a SOUTH or EAST port reverse the order. + // checkReferenceLayer already updates the transitive ordering association. int inPreviousLayer = checkReferenceLayer(previousLayer, p1Node, p2Node, p1, p2); if (inPreviousLayer != 0) { + if (p1.getSide() == PortSide.EAST && p2.getSide() == PortSide.EAST) { + // Some ports are ordered in the way around. + // Previously this did not matter, since the north south processor did override the ordering. + inPreviousLayer = -inPreviousLayer; + } if (inPreviousLayer > 0) { updateBiggerAndSmallerAssociations(p1, p2); } else { - updateBiggerAndSmallerAssociations(p1, p2); + updateBiggerAndSmallerAssociations(p2, p1); } return inPreviousLayer; } } // Check which of the nodes connects first to the previous layer. + // checkReferenceLayer already updates the transitive ordering association. int inPreviousLayer = checkReferenceLayer(Arrays.stream(previousLayer).collect(Collectors.toList()), p1Node, p2Node, p1, p2); if (inPreviousLayer != 0) { - return inPreviousLayer; + if (inPreviousLayer > 0) { + updateBiggerAndSmallerAssociations(p1, p2); + return 1; + } else { + updateBiggerAndSmallerAssociations(p2, p1); + return -1; + } } // If both ports do not connect to the previous layer, use the port model order. // If port order is used instead of edge order, consult it to make decisions. @@ -187,12 +200,13 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (portModelOrder) { int result = checkPortModelOrder(p1, p2); if (result != 0) { - if (result < 0) { - updateBiggerAndSmallerAssociations(p2, p1); - } else if (result > 0) { + if (result > 0) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; + } else { + updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return result; } } } @@ -218,10 +232,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int p2MO = p2TargetNode.getProperty(InternalProperties.MODEL_ORDER); if (p1MO > p2MO) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; } else { updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return Integer.compare(p1MO, p2MO); } // If port order is used instead of edge order, consult it to make decisions. @@ -229,13 +244,15 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (portModelOrder) { int result = checkPortModelOrder(p1, p2); if (result != 0) { - if (result == -1) { - updateBiggerAndSmallerAssociations(p2, p1); - } else if (result == 1) { + if (result > 0) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; + } else { + updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return result; } + // If one of the ports has no model order, find something else to compare them which is the edge order. } int p1Order = 0; @@ -252,10 +269,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { // If both edges are reversed or not reversed, just use their model order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; } else { updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return Integer.compare(p1Order, p2Order); } // Use precomputed ordering value if possible to utilize order inheritence of edges connected to a node. // This allows to bundle edges leading to the same node, disregarding their model order. @@ -270,10 +288,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { // If the nodes have different targets just use their order. if (p1Order > p2Order) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; } else { updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return Integer.compare(p1Order, p2Order); } // Sort outgoing ports before incoming ports. @@ -290,11 +309,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int p2MO = p2.getProperty(InternalProperties.MODEL_ORDER); if (p1MO > p2MO) { updateBiggerAndSmallerAssociations(p1, p2); + return 1; } else { updateBiggerAndSmallerAssociations(p2, p1); + return -1; } - return Integer.compare(p1MO, - p2MO); } else { updateBiggerAndSmallerAssociations(p2, p1); return -1; @@ -342,7 +361,7 @@ private void updateBiggerAndSmallerAssociations(final LPort bigger, final LPort /** * Given a previous layer, check which of the two reference nodes of a port is the first in it. - * Updates the bigger/smaller association. + * * @param layer The layer to check * @param p1Node The reference node of port p1 * @param p2Node The reference node of port p2 @@ -353,14 +372,14 @@ private void updateBiggerAndSmallerAssociations(final LPort bigger, final LPort private int checkReferenceLayer(Iterable layer, LNode p1Node, LNode p2Node, LPort p1, LPort p2) { for (LNode node : layer) { if (node.equals(p1Node)) { - updateBiggerAndSmallerAssociations(p2, p1); + // If the first node is found first, it has to be above the second one and does hence have a smaller + // Model order. return -1; } else if (node.equals(p2Node)) { - updateBiggerAndSmallerAssociations(p1, p2); return 1; } } - return 0; // Would never happen. + return 0; // Should never happen, the previous layer needs these nodes in it. } private class PortSideComparator implements Comparator { From 55dfd55ddbc70a16f2266cd2b941c7c9c178ce70 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 12:28:51 +0200 Subject: [PATCH 09/20] Found the last inconsistency in port ordering. --- .../intermediate/SortByInputModelProcessor.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index 8a8b234ca..cec6b4e30 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -75,7 +75,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito // Therefore all ports that connect to the same node should have the same // (their minimal) model order. // Get minimal model order for target node - insertionSortPorts(node.getPorts(), + Collections.sort(node.getPorts(), new ModelOrderPortComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), longEdgeTargetNodePreprocessing(node), @@ -163,20 +163,5 @@ public static void insertionSort(final List layer, } comparator.clearTransitiveOrdering(); } - - public static void insertionSortPorts(final List ports, - final ModelOrderPortComparator comparator) { - LPort temp; - for (int i = 1; i < ports.size(); i++) { - temp = ports.get(i); - int j = i; - while (j > 0 && comparator.compare(ports.get(j - 1), temp) > 0) { - ports.set(j, ports.get(j - 1)); - j--; - } - ports.set(j, temp); - } - comparator.clearTransitiveOrdering(); - } } From b2b1e82da6b3fb58d1697dd9026dc91e206ede05 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 13:03:17 +0200 Subject: [PATCH 10/20] Define all corner cases of node model order. --- .../ModelOrderNodeComparator.java | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index 268bbb604..0c84f07ab 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -142,7 +142,8 @@ public int compare(final LNode n1, final LNode n2) { // layer should be used to order them. if (p1Node != null && p1Node.equals(p2Node)) { // We are not allowed to look at the model order of the edges but we have to look at the actual - // port ordering. + // port ordering since the edge order might be broken here. + // If we have long edges the edge model order might not respect the ordering. for (LPort port : p1Node.getPorts()) { if (port.equals(p1SourcePort)) { // Case the port is the one connecting to n1, therefore, n1 has a smaller model order @@ -156,13 +157,21 @@ public int compare(final LNode n1, final LNode n2) { } assert (false); // Cannot happen, since both nodes have a connection to the previous layer. - return Integer.compare( - getModelOrderFromConnectedEdges(n1), - getModelOrderFromConnectedEdges(n2)); + // Nevertheless, if it does happen provide well defined behavior and use the model order of the edges. + int n1EdgeOrder = getModelOrderFromConnectedEdges(n1); + int n2EdgeOrder = getModelOrderFromConnectedEdges(n2); + if (n1EdgeOrder > n2EdgeOrder) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } else { + // I assume that equal is not an alternative here. + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } } - // Else the nodes are ordered by the nodes they connect to. - // One can disregard the model order here + // If the nodes do not connect to the same node, the nodes are ordered by the nodes they connect to. + // One can disregard the model order here // since the ordering in the previous layer does already reflect it. for (LNode previousNode : previousLayer) { if (previousNode.equals(p1Node)) { @@ -173,6 +182,7 @@ public int compare(final LNode n1, final LNode n2) { return 1; } } + // Again, I assume that such a node must exist, and I should never get here. } // One node has no source port @@ -194,12 +204,11 @@ public int compare(final LNode n1, final LNode n2) { int n2ModelOrder = getModelOrderFromConnectedEdges(n2); if (n1ModelOrder > n2ModelOrder) { updateBiggerAndSmallerAssociations(n1, n2); + return -1; } else { updateBiggerAndSmallerAssociations(n2, n1); + return 1; } - return Integer.compare( - n1ModelOrder, - n2ModelOrder); } } @@ -218,8 +227,8 @@ public int compare(final LNode n1, final LNode n2) { } // Fall through case. - // Both nodes are not connected to the previous layer. Therefore, they must be normal nodes. - // The model order shall be used to order them. + // Both nodes are not connected to the previous layer and are normal nodes. + // The node model order shall be used to order them. } // Order nodes by their order in the model. // This is also the fallback case if one of the nodes is not connected to the previous layer. @@ -234,7 +243,10 @@ public int compare(final LNode n1, final LNode n2) { return -1; } } else { - return 0; + // If they have no model order, I should still make an ordering decision that I save somehow. + // This decision is somehow random I just sort the first one before the second one. + updateBiggerAndSmallerAssociations(n2, n1); + return -1; } } @@ -344,7 +356,7 @@ private int handleHelperDummyNodes(LNode n1, LNode n2) { } // Case both edges connect to separate nodes. - // In this case comapare the order of their nodes in their layer. + // In this case compare the order of their nodes in their layer. LNode n1dummyNodeSourceNode = n1dummyNodeSourcePort.getNode(); LNode n2dummyNodeSourceNode = n2dummyNodeSourcePort.getNode(); Layer dummySourceLayer = n1dummyNodeSourceNode.getLayer(); @@ -361,23 +373,48 @@ private int handleHelperDummyNodes(LNode n1, LNode n2) { // This cannot occur. return 0; } else { - // These nodes are just two normal nodes + // These nodes are just two normal nodes and need to be handled by node model order. return 0; } } + /** + * Helper method to get the first incoming port of a node. + * + * @param node The node + * @return The first incoming port. + */ private LPort getFirstIncomingPortOfNode(LNode node) { return node.getPorts().stream().filter(p -> !p.getIncomingEdges().isEmpty()).findFirst().orElse(null); } + + /** + * Helper method to get the source port of the first incoming port of a node. + * + * @param node The node + * @return The source port of the first incoming port. + */ private LPort getFirstIncomingSourcePortOfNode(LNode node) { return getFirstIncomingPortOfNode(node).getIncomingEdges().get(0).getSource(); } - + + /** + * Helper method to get the first outgoing port of a node. + * + * @param node The node + * @return The first outgoing port. + */ private LPort getFirstOutgoingPortOfNode(LNode node) { return node.getPorts().stream().filter(p -> !p.getOutgoingEdges().isEmpty()).findFirst().orElse(null); } - + + /** + * Helper method to get the target port of the first outgoing port of a node. + * + * @param node The node + * @return The target port of the first outgoing port. + */ private LPort getFirstOutgoingSourcePortOfNode(LNode node) { return getFirstOutgoingPortOfNode(node).getOutgoingEdges().get(0).getTarget(); } From 1af5370f068eef32b58ad24528e4589a4b8c1bdb Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 13:03:59 +0200 Subject: [PATCH 11/20] Use normal sorting instead of insertion sort but keep insertion sort. --- .../SortByInputModelProcessor.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index cec6b4e30..ccaefbe2d 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -10,7 +10,6 @@ package org.eclipse.elk.alg.layered.intermediate; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -66,7 +65,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); - SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); + Collections.sort(layer.getNodes(), comparator); for (LNode node : layer.getNodes()) { if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER && node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_POS) { @@ -83,11 +82,6 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito progressMonitor.log("Node " + node + " ports: " + node.getPorts()); } } - // Sort nodes. - comparator = new ModelOrderNodeComparator(previousLayer, - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); - SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); progressMonitor.log("Layer " + layerIndex + ": " + layer); layerIndex++; @@ -163,5 +157,20 @@ public static void insertionSort(final List layer, } comparator.clearTransitiveOrdering(); } + + public static void insertionSortPort(final List layer, + final ModelOrderPortComparator comparator) { + LPort temp; + for (int i = 1; i < layer.size(); i++) { + temp = layer.get(i); + int j = i; + while (j > 0 && comparator.compare(layer.get(j - 1), temp) > 0) { + layer.set(j, layer.get(j - 1)); + j--; + } + layer.set(j, temp); + } + comparator.clearTransitiveOrdering(); + } } From 72b658b3c598e14cb79e211b29a9e661ad487126 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 13:05:47 +0200 Subject: [PATCH 12/20] Make sure that exchanging ports does not lead to bugs by using a scalar. --- .../ModelOrderPortComparator.java | 98 ++++++++++--------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index 16457bd58..e904a1215 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -117,12 +117,13 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (p1.getSide() != p2.getSide()) { int result = new PortSideComparator().compare(p1.getSide(), p2.getSide()); if (result > 0) { - updateBiggerAndSmallerAssociations(p1, p2); + updateBiggerAndSmallerAssociations(p1, p2, 1); } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, 1); } return result; } + int reverseOrder = 1; // Sort incoming edges by sorting their ports by the order of the nodes they connect to. if (!p1.getIncomingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { @@ -131,9 +132,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { // Some ports are ordered in the way around. // Previously this did not matter, since the north south processor did override the ordering. - LPort temp = p1; - p1 = p2; - p2 = temp; + reverseOrder = -reverseOrder; } LPort p1SourcePort = p1.getIncomingEdges().get(0).getSource(); @@ -145,11 +144,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { // first and should hence be sorted. for (LPort port : p1Node.getPorts()) { if (p1SourcePort.equals(port)) { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } else if (p2SourcePort.equals(port)) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } } } @@ -171,14 +170,15 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (p1.getSide() == PortSide.EAST && p2.getSide() == PortSide.EAST) { // Some ports are ordered in the way around. // Previously this did not matter, since the north south processor did override the ordering. - inPreviousLayer = -inPreviousLayer; + reverseOrder = -reverseOrder; } if (inPreviousLayer > 0) { - updateBiggerAndSmallerAssociations(p1, p2); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - return inPreviousLayer; } } @@ -187,11 +187,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int inPreviousLayer = checkReferenceLayer(Arrays.stream(previousLayer).collect(Collectors.toList()), p1Node, p2Node, p1, p2); if (inPreviousLayer != 0) { if (inPreviousLayer > 0) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } // If both ports do not connect to the previous layer, use the port model order. @@ -201,11 +201,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int result = checkPortModelOrder(p1, p2); if (result != 0) { if (result > 0) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } } @@ -231,11 +231,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int p1MO = p1TargetNode.getProperty(InternalProperties.MODEL_ORDER); int p2MO = p2TargetNode.getProperty(InternalProperties.MODEL_ORDER); if (p1MO > p2MO) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } @@ -245,11 +245,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { int result = checkPortModelOrder(p1, p2); if (result != 0) { if (result > 0) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } // If one of the ports has no model order, find something else to compare them which is the edge order. @@ -268,11 +268,11 @@ public int compare(final LPort originalP1, final LPort originalP2) { if (p1TargetNode != null && p1TargetNode.equals(p2TargetNode)) { // If both edges are reversed or not reversed, just use their model order. if (p1Order > p2Order) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } // Use precomputed ordering value if possible to utilize order inheritence of edges connected to a node. @@ -287,20 +287,20 @@ public int compare(final LPort originalP1, final LPort originalP2) { } // If the nodes have different targets just use their order. if (p1Order > p2Order) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } // Sort outgoing ports before incoming ports. if (!p1.getIncomingEdges().isEmpty() && !p2.getOutgoingEdges().isEmpty()) { - updateBiggerAndSmallerAssociations(p1, p2); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); return 1; } else if (!p1.getOutgoingEdges().isEmpty() && !p2.getIncomingEdges().isEmpty()) { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); return -1; } else if (p1.hasProperty(InternalProperties.MODEL_ORDER) && p2.hasProperty(InternalProperties.MODEL_ORDER)) { // The ports have no edges. @@ -308,15 +308,15 @@ public int compare(final LPort originalP1, final LPort originalP2) { int p1MO = p1.getProperty(InternalProperties.MODEL_ORDER); int p2MO = p2.getProperty(InternalProperties.MODEL_ORDER); if (p1MO > p2MO) { - updateBiggerAndSmallerAssociations(p1, p2); - return 1; + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } @@ -338,7 +338,13 @@ public int checkPortModelOrder(final LPort p1, final LPort p2) { return 0; } - private void updateBiggerAndSmallerAssociations(final LPort bigger, final LPort smaller) { + private void updateBiggerAndSmallerAssociations(final LPort biggerOri, final LPort smallerOri, int reverseOrder) { + LPort bigger = biggerOri; + LPort smaller = smallerOri; + if (reverseOrder < 0) { + bigger = smallerOri; + smaller = biggerOri; + } HashSet biggerPortBiggerThan = biggerThan.get(bigger); HashSet smallerPortBiggerThan = biggerThan.get(smaller); HashSet biggerPortSmallerThan = smallerThan.get(bigger); From 4ef8eb59c717d8286e43c0bfc25dc1576d8c64b0 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 13:09:31 +0200 Subject: [PATCH 13/20] Readded second node sorting. I could solve this maybe by doing the inversePortProcessor after this. --- .../alg/layered/intermediate/SortByInputModelProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index ccaefbe2d..e4ef49bc3 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -82,6 +82,11 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito progressMonitor.log("Node " + node + " ports: " + node.getPorts()); } } + // Sort nodes after port sorting to also sort dummy feedback nodes from the current layer correctly. + comparator = new ModelOrderNodeComparator(previousLayer, + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); + Collections.sort(layer.getNodes(), comparator); progressMonitor.log("Layer " + layerIndex + ": " + layer); layerIndex++; From 976c8691f6885fc548b8b15ab6419e692ae0309d Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 14:43:53 +0200 Subject: [PATCH 14/20] Added first patch of sortbyinputmodel correctness examples. --- .../ConcreteSortByInputModelTest.java | 892 ++++++++++++++++++ 1 file changed, 892 insertions(+) create mode 100644 test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java diff --git a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java new file mode 100644 index 000000000..b7a1f3f51 --- /dev/null +++ b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java @@ -0,0 +1,892 @@ +/******************************************************************************* + * Copyright (c) 2024 Kiel University. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.elk.alg.layered.intermediate; + +import static org.junit.Assert.assertTrue; + +import org.eclipse.elk.alg.layered.LayeredLayoutProvider; +import org.eclipse.elk.alg.layered.options.CrossingMinimizationStrategy; +import org.eclipse.elk.alg.layered.options.CycleBreakingStrategy; +import org.eclipse.elk.alg.layered.options.GreedySwitchType; +import org.eclipse.elk.alg.layered.options.LayeredOptions; +import org.eclipse.elk.alg.layered.options.OrderingStrategy; +import org.eclipse.elk.alg.test.PlainJavaInitialization; +import org.eclipse.elk.core.math.ElkPadding; +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.core.options.Direction; +import org.eclipse.elk.core.options.PortConstraints; +import org.eclipse.elk.core.options.PortSide; +import org.eclipse.elk.core.util.BasicProgressMonitor; +import org.eclipse.elk.core.util.NullElkProgressMonitor; +import org.eclipse.elk.graph.ElkNode; +import org.eclipse.elk.graph.ElkPort; +import org.eclipse.elk.graph.util.ElkGraphUtil; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test whether these concrete example did not break while using the consider model order strategy. + * + */ +public class ConcreteSortByInputModelTest { + + @BeforeClass + public static void init() { + PlainJavaInitialization.initializePlainJavaLayout(); + } + + @Test + public void testOutgoingEastIncomingWest() { + // p1--->p4 + // n1 n2 + // p2<---p3 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingEastIncomingNorth() { + // p1--------- + // n1 | + // p2<---- | + // | v + // p3 p4 + // n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingEastIncomingSouth() { + // n2 + // p4 p3 + // A | + // p1----- | + // n1 | + // p2<-------- + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingEastIncomingEast() { + // p3<---- + // n2 | + // p4<- | + // | | + // | | + // p1------ | + // n1 | + // p2<-------- + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingNorthIncomingWest() { + // ------>p4 + // | n2 + // | |----p3 + // | v + //p1 p2 + // n1 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingNorthIncomingNorth() { + // ---------- + // | | + // | |----| | + // | v | v + //p1 p2 p3 p4 + // n1 n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingNorthIncomingSouth() { + // n2 + // p4 p3 + // ____A | + // | ____| + // | | + // | v + //p1 p2 + // n1 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingNorthIncomingEast() { + // p3---- + // n2 | + // p4<- | + // | | + // ------ | + // | ----- + // | | + // | v + //p1 p2 + // n1 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p3", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingSouthIncomingWest() { + // n1 + //p1 p2 + // | A + // | |---p3 + // | n2 + // |----->p4 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingSouthIncomingNorth() { + // n1 + //p1 p2 + // | A + // | |---| + // | | + // |---| | + // v | + // p4 p3 + // n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new NullElkProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingSouthIncomingSouth() { + // n1 n2 + //p1 p2 p3 p4 + // | A | A + // | |---| | + // | | + // |---------| + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingSouthIncomingEast() { + // + // p4<-| + // n1 n2 | + //p1 p2 p3 | + // | A | | + // | |------| | + // | | + // |------------| + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 abive p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingWestIncomingWest() { + // |-----p1 + // | n1 + // | |->p2 + // | | + // | |------p3 + // | n2 + // |-------->p4 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingWestIncomingNorth() { + // |-----p1 + // | n1 + // | |->p2 + // | | + // | |----------| + // | | + // |---------v | + // p4 p3 + // n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingWestIncomingSouth() { + // |-----p1 + // | n1 + // | |->p2 n2 + // | | p3 p4 + // | |-------| A + // | | + // |-------------| + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingWestIncomingEast() { + // |-----p1 p4<----| + // | n1 n2 | + // | |->p2 p3--| | + // | | | | + // | |-------------| | + // | | + // |-------------------| + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } +} \ No newline at end of file From b0335941fd36953381d362e4f6d3e109e190236d Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 15:40:49 +0200 Subject: [PATCH 15/20] Added correctness tests with multiple nodes. --- .../ConcreteSortByInputModelTest.java | 1235 ++++++++++++++--- 1 file changed, 1077 insertions(+), 158 deletions(-) diff --git a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java index b7a1f3f51..03408a7de 100644 --- a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java +++ b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java @@ -91,6 +91,59 @@ public void testOutgoingEastIncomingWest() { assertTrue("p4 above p3", p4.getY() < p3.getY()); } + @Test + public void testOutgoingEastIncomingWestMultipleNodes() { + // n1:p1--->p4 + // n2 + // |--p3 + // ni:p2<- + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + @Test public void testOutgoingEastIncomingNorth() { // p1--------- @@ -143,6 +196,61 @@ public void testOutgoingEastIncomingNorth() { assertTrue("p3 before p4", p3.getX() < p4.getX()); } + @Test + public void testOutgoingEastIncomingNorthMultipleNodes() { + //n1:p1--------- + // | + //ni:p2<---- | + // | v + // p3 p4 + // n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + @Test public void testOutgoingEastIncomingSouth() { // n2 @@ -195,6 +303,61 @@ public void testOutgoingEastIncomingSouth() { assertTrue("p4 before p3", p4.getX() < p3.getX()); } + @Test + public void testOutgoingEastIncomingSouthMultipleNodes() { + // n2 + // p4 p3 + // A | + //n1:p1----- | + // | + //ni:p2<-------- + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + @Test public void testOutgoingEastIncomingEast() { // p3<---- @@ -249,6 +412,63 @@ public void testOutgoingEastIncomingEast() { assertTrue("p3 above p4", p3.getY() < p4.getY()); } + @Test + public void testOutgoingEastIncomingEastMultipleNodes() { + // p3<---- + // n2 | + // p4<- | + // | | + // | | + //n1:p1------ | + // | + //ni:p2<-------- + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + @Test public void testOutgoingNorthIncomingWest() { // ------>p4 @@ -282,13 +502,687 @@ public void testOutgoingNorthIncomingWest() { parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingNorthIncomingWestMultipleNodes() { + // |--| + // | | + // p1 | + // n1 | + // |--->p4 + // n2 + // |-------p3 + // v + // p2 + // ni + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingNorthIncomingNorth() { + // ---------- + // | | + // | |----| | + // | v | v + //p1 p2 p3 p4 + // n1 n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingNorthIncomingNorthMultipleNodes() { + // |--| + // | | + // p1 | + // n1 | + // |------- + // | + // |------| | + // | p3 p4 + // v n2 + // p2 + // ni + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingNorthIncomingSouth() { + // n2 + // p4 p3 + // ____A | + // | ____| + // | | + // | v + //p1 p2 + // n1 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingNorthIncomingSouthMultipleNodes() { + // n2 + // p4 p3 + // |------| | + // p1 | + // n1 | + // |---------| + // v + // p2 + // ni + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingNorthIncomingEast() { + // p3---- + // n2 | + // p4<- | + // | | + // ------ | + // | ----- + // | | + // | v + //p1 p2 + // n1 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingNorthIncomingEastMultipleNodes() { + // p3----| + // n1 | + // p4<| | + // |------| | + // p1 | + // n1 | + // |---------| + // v + // p2 + // ni + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingSouthIncomingWest() { + // n1 + //p1 p2 + // | A + // | |---p3 + // | n2 + // |----->p4 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); + } + + @Test + public void testOutgoingSouthIncomingWestMultipleNodes() { + // n1 + // p1 + // |----->p4 + // ni n2 + // p2 |--p3 + // A---| + // + // + // + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } + + @Test + public void testOutgoingSouthIncomingNorth() { + // n1 + //p1 p2 + // | A + // | |---| + // | | + // |---| | + // v | + // p4 p3 + // n2 + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(n1); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new NullElkProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); + } + + @Test + public void testOutgoingSouthIncomingNorthMultipleNodes() { + // n1 + // p1 + // |----------- + // | + // ni | + // p2 | + // A--------| | + // p3 p4 + // n2 + // + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + ni.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p3 = ElkGraphUtil.createPort(n2); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + ElkPort p4 = ElkGraphUtil.createPort(n2); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + + ElkGraphUtil.createSimpleEdge(p1, p4); + ElkGraphUtil.createSimpleEdge(p2, p3); + + LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); + layoutProvider.layout(parent, new NullElkProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); + } + + @Test + public void testOutgoingSouthIncomingSouth() { + // n1 n2 + //p1 p2 p3 p4 + // | A | A + // | |---| | + // | | + // |---------| + + ElkNode parent = ElkGraphUtil.createGraph(); + ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode n2 = ElkGraphUtil.createNode(parent); + n1.setDimensions(20, 20); + n2.setDimensions(20, 20); + + parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Model order configuration. + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES); + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, false); + // No crossing minimization, hence only model order is used. + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + parent.setProperty(LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + // For presentation. + parent.setProperty(CoreOptions.DIRECTION, Direction.RIGHT); + parent.setProperty(CoreOptions.PADDING, new ElkPadding(0.0)); + parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); + parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); + n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Make sure that the order is correct. + parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + + ElkPort p1 = ElkGraphUtil.createPort(n1); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); @@ -298,22 +1192,27 @@ public void testOutgoingNorthIncomingWest() { // Assert the ordering of p1 and p2. assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. - assertTrue("p4 above p3", p4.getY() < p3.getY()); + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test - public void testOutgoingNorthIncomingNorth() { - // ---------- - // | | - // | |----| | - // | v | v - //p1 p2 p3 p4 - // n1 n2 - + public void testOutgoingSouthIncomingSouthMultipleNodes() { + //n1 n2 + //p1 p4 p3 + // | | A + // |------| | + // | + //ni | + //p2 | + //A | + //-----------| + ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -329,40 +1228,41 @@ public void testOutgoingNorthIncomingNorth() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); - ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. - assertTrue("p3 before p4", p3.getX() < p4.getX()); + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test - public void testOutgoingNorthIncomingSouth() { - // n2 - // p4 p3 - // ____A | - // | ____| - // | | - // | v - //p1 p2 - // n1 + public void testOutgoingSouthIncomingEast() { + // + // p4<-| + // n1 n2 | + //p1 p2 p3 | + // | A | | + // | |------| | + // | | + // |------------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); @@ -388,13 +1288,13 @@ public void testOutgoingNorthIncomingSouth() { parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); @@ -404,26 +1304,28 @@ public void testOutgoingNorthIncomingSouth() { // Assert the ordering of p1 and p2. assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. - assertTrue("p4 before p3", p4.getX() < p3.getX()); + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test - public void testOutgoingNorthIncomingEast() { - // p3---- - // n2 | - // p4<- | - // | | - // ------ | - // | ----- - // | | - // | v - //p1 p2 - // n1 + public void testOutgoingSouthIncomingEastMultipleNodes() { + // p3--| + //n1 n2 | + //p1 p4< | + // | | | + // |-------- | + // | + //ni | + //p2 | + //A | + //-----------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -439,14 +1341,15 @@ public void testOutgoingNorthIncomingEast() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); - ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p3 = ElkGraphUtil.createPort(n2); p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); ElkPort p4 = ElkGraphUtil.createPort(n2); @@ -457,20 +1360,21 @@ public void testOutgoingNorthIncomingEast() { LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. - assertTrue("p3 above p3", p3.getY() < p4.getY()); + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test - public void testOutgoingSouthIncomingWest() { - // n1 - //p1 p2 - // | A - // | |---p3 - // | n2 - // |----->p4 + public void testOutgoingWestIncomingWest() { + // |-----p1 + // | n1 + // | |->p2 + // | | + // | |------p3 + // | n2 + // |-------->p4 ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); @@ -496,9 +1400,9 @@ public void testOutgoingSouthIncomingWest() { parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p4 = ElkGraphUtil.createPort(n2); @@ -510,27 +1414,27 @@ public void testOutgoingSouthIncomingWest() { LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test - public void testOutgoingSouthIncomingNorth() { - // n1 - //p1 p2 - // | A - // | |---| - // | | - // |---| | - // v | - // p4 p3 - // n2 + public void testOutgoingWestIncomingWestMultipleNodes() { + // |-p1:n1 + // | + // |-------->p4 + // n2 + // |>p2:ni |-p3 + // | | + // |-------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -546,38 +1450,42 @@ public void testOutgoingSouthIncomingNorth() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); - ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); - layoutProvider.layout(parent, new NullElkProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. - assertTrue("p4 before p3", p4.getX() < p3.getX()); + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test - public void testOutgoingSouthIncomingSouth() { - // n1 n2 - //p1 p2 p3 p4 - // | A | A - // | |---| | - // | | - // |---------| + public void testOutgoingWestIncomingNorth() { + // |-----p1 + // | n1 + // | |->p2 + // | | + // | |----------| + // | | + // |---------v | + // p4 p3 + // n2 ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); @@ -603,13 +1511,13 @@ public void testOutgoingSouthIncomingSouth() { parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); @@ -617,26 +1525,29 @@ public void testOutgoingSouthIncomingSouth() { LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. - assertTrue("p3 before p4", p3.getX() < p4.getX()); + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test - public void testOutgoingSouthIncomingEast() { - // - // p4<-| - // n1 n2 | - //p1 p2 p3 | - // | A | | - // | |------| | - // | | - // |------------| + public void testOutgoingWestIncomingNorthMultipleNodes() { + // |-p1:n1 + // | + // |-----------| + // | + // |>p2:ni | + // | | + // |--------| v + // p3 p4 + // n2 ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -652,39 +1563,40 @@ public void testOutgoingSouthIncomingEast() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); - p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); - ElkPort p2 = ElkGraphUtil.createPort(n1); - p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. - assertTrue("p4 abive p3", p4.getY() < p3.getY()); + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test - public void testOutgoingWestIncomingWest() { + public void testOutgoingWestIncomingSouth() { // |-----p1 // | n1 - // | |->p2 - // | | - // | |------p3 - // | n2 - // |-------->p4 + // | |->p2 n2 + // | | p3 p4 + // | |-------| A + // | | + // |-------------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); @@ -714,9 +1626,9 @@ public void testOutgoingWestIncomingWest() { ElkPort p2 = ElkGraphUtil.createPort(n1); p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); @@ -726,25 +1638,26 @@ public void testOutgoingWestIncomingWest() { // Assert the ordering of p1 and p2. assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. - assertTrue("p3 above p4", p3.getY() < p4.getY()); + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test - public void testOutgoingWestIncomingNorth() { - // |-----p1 - // | n1 - // | |->p2 - // | | - // | |----------| - // | | - // |---------v | - // p4 p3 - // n2 + public void testOutgoingWestIncomingSouthMultipleNodes() { + // |-----p1:n1 n2 + // | p4 p3 + // | A | + // |------------| | + // | + // |-----p2:ni | + // | | + // |----------------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -760,39 +1673,40 @@ public void testOutgoingWestIncomingNorth() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); - ElkPort p2 = ElkGraphUtil.createPort(n1); + ElkPort p2 = ElkGraphUtil.createPort(ni); p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.NORTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test - public void testOutgoingWestIncomingSouth() { - // |-----p1 - // | n1 - // | |->p2 n2 - // | | p3 p4 - // | |-------| A - // | | - // |-------------| + public void testOutgoingWestIncomingEast() { + // |-----p1 p4<----| + // | n1 n2 | + // | |->p2 p3--| | + // | | | | + // | |-------------| | + // | | + // |-------------------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); @@ -822,9 +1736,9 @@ public void testOutgoingWestIncomingSouth() { ElkPort p2 = ElkGraphUtil.createPort(n1); p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); - p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); ElkPort p4 = ElkGraphUtil.createPort(n2); - p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + p4.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); ElkGraphUtil.createSimpleEdge(p1, p4); ElkGraphUtil.createSimpleEdge(p2, p3); @@ -834,23 +1748,27 @@ public void testOutgoingWestIncomingSouth() { // Assert the ordering of p1 and p2. assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. - assertTrue("p3 before p4", p3.getX() < p4.getX()); + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test - public void testOutgoingWestIncomingEast() { - // |-----p1 p4<----| - // | n1 n2 | - // | |->p2 p3--| | - // | | | | - // | |-------------| | - // | | - // |-------------------| + public void testOutgoingWestIncomingEastMultipleNodes() { + // p3-----| + // n2 | + // |--p1:n1 p4<-| | + // | | | + // |----------------| | + // | + // |->p2:ni | + // | | + // |-------------------| ElkNode parent = ElkGraphUtil.createGraph(); ElkNode n1 = ElkGraphUtil.createNode(parent); + ElkNode ni = ElkGraphUtil.createNode(parent); ElkNode n2 = ElkGraphUtil.createNode(parent); n1.setDimensions(20, 20); + ni.setDimensions(20, 20); n2.setDimensions(20, 20); parent.setProperty(CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -866,13 +1784,14 @@ public void testOutgoingWestIncomingEast() { parent.setProperty(CoreOptions.SPACING_NODE_NODE, 10.0); parent.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 20.0); n1.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + ni.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); n2.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); // Make sure that the order is correct. parent.setProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); ElkPort p1 = ElkGraphUtil.createPort(n1); p1.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); - ElkPort p2 = ElkGraphUtil.createPort(n1); + ElkPort p2 = ElkGraphUtil.createPort(ni); p2.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); ElkPort p3 = ElkGraphUtil.createPort(n2); p3.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); @@ -884,9 +1803,9 @@ public void testOutgoingWestIncomingEast() { LayeredLayoutProvider layoutProvider = new LayeredLayoutProvider(); layoutProvider.layout(parent, new BasicProgressMonitor()); - // Assert the ordering of p1 and p2. - assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. - assertTrue("p4 above p3", p4.getY() < p3.getY()); + assertTrue("p3 above p4", p3.getY() < p4.getY()); } } \ No newline at end of file From b35b534104eba312bbd4349c4cdd4f0ddc2bdd5c Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 15:48:25 +0200 Subject: [PATCH 16/20] Port model order basic concrete tests. --- .../ConcreteSortByInputModelTest.java | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java index 03408a7de..fc5de20e5 100644 --- a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java +++ b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java @@ -89,6 +89,17 @@ public void testOutgoingEastIncomingWest() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); + } @Test @@ -142,6 +153,16 @@ public void testOutgoingEastIncomingWestMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -194,6 +215,16 @@ public void testOutgoingEastIncomingNorth() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -249,6 +280,16 @@ public void testOutgoingEastIncomingNorthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -301,6 +342,16 @@ public void testOutgoingEastIncomingSouth() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -356,6 +407,16 @@ public void testOutgoingEastIncomingSouthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -410,6 +471,16 @@ public void testOutgoingEastIncomingEast() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -467,6 +538,16 @@ public void testOutgoingEastIncomingEastMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -519,6 +600,16 @@ public void testOutgoingNorthIncomingWest() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -578,6 +669,16 @@ public void testOutgoingNorthIncomingWestMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -630,6 +731,16 @@ public void testOutgoingNorthIncomingNorth() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -690,6 +801,16 @@ public void testOutgoingNorthIncomingNorthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -744,6 +865,16 @@ public void testOutgoingNorthIncomingSouth() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -802,6 +933,16 @@ public void testOutgoingNorthIncomingSouthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -858,6 +999,16 @@ public void testOutgoingNorthIncomingEast() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -917,6 +1068,16 @@ public void testOutgoingNorthIncomingEastMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -969,6 +1130,16 @@ public void testOutgoingSouthIncomingWest() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -1027,6 +1198,16 @@ public void testOutgoingSouthIncomingWestMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -1082,6 +1263,16 @@ public void testOutgoingSouthIncomingNorth() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -1141,6 +1332,16 @@ public void testOutgoingSouthIncomingNorthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -1193,6 +1394,16 @@ public void testOutgoingSouthIncomingSouth() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -1251,6 +1462,16 @@ public void testOutgoingSouthIncomingSouthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -1305,6 +1526,16 @@ public void testOutgoingSouthIncomingEast() { assertTrue("p1 before p2", p1.getX() < p2.getX()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 before p2", p1.getX() < p2.getX()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -1364,6 +1595,16 @@ public void testOutgoingSouthIncomingEastMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -1417,6 +1658,16 @@ public void testOutgoingWestIncomingWest() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } @Test @@ -1473,6 +1724,16 @@ public void testOutgoingWestIncomingWestMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -1528,6 +1789,16 @@ public void testOutgoingWestIncomingNorth() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -1586,6 +1857,16 @@ public void testOutgoingWestIncomingNorthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -1639,6 +1920,16 @@ public void testOutgoingWestIncomingSouth() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 before p4", p3.getX() < p4.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 before p4", p3.getX() < p4.getX()); } @Test @@ -1696,6 +1987,16 @@ public void testOutgoingWestIncomingSouthMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 before p3", p4.getX() < p3.getX()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 before p3", p4.getX() < p3.getX()); } @Test @@ -1749,6 +2050,16 @@ public void testOutgoingWestIncomingEast() { assertTrue("p1 above p2", p1.getY() < p2.getY()); // Assert the ordering of p3 and p4. assertTrue("p4 above p3", p4.getY() < p3.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of p1 and p2. + assertTrue("p1 above p2", p1.getY() < p2.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p4 above p3", p4.getY() < p3.getY()); } @Test @@ -1807,5 +2118,15 @@ public void testOutgoingWestIncomingEastMultipleNodes() { assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); // Assert the ordering of p3 and p4. assertTrue("p3 above p4", p3.getY() < p4.getY()); + + // Check for port model order, which should result in the same graph. + + parent.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER, true); + + layoutProvider.layout(parent, new BasicProgressMonitor()); + // Assert the ordering of n1 and ni. + assertTrue("Node order of n1 and ni", n1.getY() < ni.getY()); + // Assert the ordering of p3 and p4. + assertTrue("p3 above p4", p3.getY() < p4.getY()); } } \ No newline at end of file From 78539e5457a0726cc0913ab407fe2f2db5e4a74d Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 15:52:40 +0200 Subject: [PATCH 17/20] Nodes have to be sorted with insertion sort. --- .../alg/layered/intermediate/SortByInputModelProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index e4ef49bc3..d40e04f94 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -65,7 +65,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); - Collections.sort(layer.getNodes(), comparator); + SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); for (LNode node : layer.getNodes()) { if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER && node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_POS) { @@ -86,7 +86,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); - Collections.sort(layer.getNodes(), comparator); + SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); progressMonitor.log("Layer " + layerIndex + ": " + layer); layerIndex++; From 2316c9e81c019f0d7cd167bc83894f501dc8f40e Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 19 Aug 2024 16:43:53 +0200 Subject: [PATCH 18/20] Added optimization fixme to model order port comparator. --- .../intermediate/preserveorder/ModelOrderPortComparator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index e904a1215..c6fac5edd 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -30,6 +30,8 @@ * This takes into account that ports that connect to the same (long edge) target should be ordered next to each other. * Outgoing ports are ordered before incoming ports. * Incoming ports choose a position that does not create conflicts with the previous layer. + * FIXME optimize this by setting ids on all already handled nodes in the previous and current layer (e.g. after the + * node order was set) to avoid searching for the correct order and using the ids instead to compare. */ public class ModelOrderPortComparator implements Comparator { From af1d590c2aae00baf68320c17436db974d3f5b79 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Tue, 20 Aug 2024 11:10:21 +0200 Subject: [PATCH 19/20] long edge and feedback edge corner cases. --- .../SortByInputModelProcessor.java | 4 +- .../ModelOrderNodeComparator.java | 162 +++++++++++++----- .../options/LongEdgeOrderingStrategy.java | 2 +- .../p3order/LayerSweepCrossingMinimizer.java | 2 +- 4 files changed, 124 insertions(+), 46 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java index d40e04f94..969bf751b 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java @@ -64,7 +64,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito // Sort nodes before port sorting to have sorted nodes for in-layer feedback edge dummies. ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), true); SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); for (LNode node : layer.getNodes()) { if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER @@ -85,7 +85,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito // Sort nodes after port sorting to also sort dummy feedback nodes from the current layer correctly. comparator = new ModelOrderNodeComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), - graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY)); + graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), false); SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); progressMonitor.log("Layer " + layerIndex + ": " + layer); diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java index 0c84f07ab..d8750c605 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderNodeComparator.java @@ -52,6 +52,11 @@ public class ModelOrderNodeComparator implements Comparator { */ private LongEdgeOrderingStrategy longEdgeNodeOrder = LongEdgeOrderingStrategy.EQUAL; + /** + * Whether the node comparator was called before ports. + */ + private boolean beforePorts; + /** * Creates a comparator to compare {@link LNode}s in the same layer. * @@ -60,8 +65,8 @@ public class ModelOrderNodeComparator implements Comparator { * @param longEdgeOrderingStrategy The strategy to order dummy nodes and nodes with no connection the previous layer */ public ModelOrderNodeComparator(final Layer thePreviousLayer, final OrderingStrategy orderingStrategy, - final LongEdgeOrderingStrategy longEdgeOrderingStrategy) { - this(orderingStrategy, longEdgeOrderingStrategy); + final LongEdgeOrderingStrategy longEdgeOrderingStrategy, boolean beforePorts) { + this(orderingStrategy, longEdgeOrderingStrategy, beforePorts); this.previousLayer = new LNode[thePreviousLayer.getNodes().size()]; thePreviousLayer.getNodes().toArray(this.previousLayer); } @@ -74,15 +79,16 @@ public ModelOrderNodeComparator(final Layer thePreviousLayer, final OrderingStra * @param longEdgeOrderingStrategy The strategy to order dummy nodes and nodes with no connection the previous layer */ public ModelOrderNodeComparator(final LNode[] previousLayer, final OrderingStrategy orderingStrategy, - final LongEdgeOrderingStrategy longEdgeOrderingStrategy) { - this(orderingStrategy, longEdgeOrderingStrategy); + final LongEdgeOrderingStrategy longEdgeOrderingStrategy, boolean beforePorts) { + this(orderingStrategy, longEdgeOrderingStrategy, beforePorts); this.previousLayer = previousLayer; } private ModelOrderNodeComparator(final OrderingStrategy orderingStrategy, - final LongEdgeOrderingStrategy longEdgeOrderingStrategy) { + final LongEdgeOrderingStrategy longEdgeOrderingStrategy, boolean beforePorts) { this.orderingStrategy = orderingStrategy; this.longEdgeNodeOrder = longEdgeOrderingStrategy; + this.beforePorts = beforePorts; } @Override @@ -204,10 +210,10 @@ public int compare(final LNode n1, final LNode n2) { int n2ModelOrder = getModelOrderFromConnectedEdges(n2); if (n1ModelOrder > n2ModelOrder) { updateBiggerAndSmallerAssociations(n1, n2); - return -1; + return 1; } else { updateBiggerAndSmallerAssociations(n2, n1); - return 1; + return -1; } } } @@ -265,8 +271,8 @@ private int getModelOrderFromConnectedEdges(final LNode n) { return edge.getProperty(InternalProperties.MODEL_ORDER); } } - // Set to -1 to sort dummy nodes under nodes without a connection to the previous layer. // Set to MAX_INT to sort dummy nodes over nodes without a connection to the previous layer. + // Set to -MAX_INT to sort dummy nodes under nodes without a connection to the previous layer. // Set to 0 if you do not care about their order. // One of this has to be chosen, since dummy nodes are not comparable with nodes // that do not have a connection to the previous layer. @@ -296,18 +302,24 @@ private void updateBiggerAndSmallerAssociations(final LNode bigger, final LNode private int handleHelperDummyNodes(LNode n1, LNode n2) { if (n1.getType() == NodeType.LONG_EDGE && n2.getType() == NodeType.NORMAL) { - // n1 is a long edge node feedback node. + // n1 could be a long edge node feedback node. LPort dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n1); LNode dummyNodeSourceNode = dummyNodeSourcePort.getNode(); + LPort dummyNodeTargetPort = getFirstOutgoingTargetPortOfNode(n1); + LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); + int dummyLayerId = n1.getLayer().id; + + // Check whether the dummy node is feedback source or feedback target if not return. + if (dummyNodeSourceNode.getLayer().id != dummyLayerId && dummyNodeTargetNode.getLayer().id != dummyLayerId) { + return 0; + } // Case the source of the dummy is the same node as n2, than the dummy node is routed below. if (dummyNodeSourceNode.equals(n2)) { updateBiggerAndSmallerAssociations(n1, n2); return 1; } else { // Calculate whether the dummy node leads to the target node. - LPort dummyNodeTargetPort = getFirstOutgoingSourcePortOfNode(n1); - LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); if (dummyNodeTargetNode.equals(n2)) { updateBiggerAndSmallerAssociations(n1, n2); return 1; @@ -317,17 +329,23 @@ private int handleHelperDummyNodes(LNode n1, LNode n2) { return this.compare(dummyNodeSourceNode, n2); } } else if (n1.getType() == NodeType.NORMAL && n2.getType() == NodeType.LONG_EDGE) { - // n2 is a long edge node feedback node. + // n2 could be a long edge node feedback node. LPort dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n2); LNode dummyNodeSourceNode = dummyNodeSourcePort.getNode(); + LPort dummyNodeTargetPort = getFirstOutgoingTargetPortOfNode(n2); + LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); + int dummyLayerId = n1.getLayer().id; + + // Check whether the dummy node is feedback source or feedback target if not return. + if (dummyNodeSourceNode.getLayer().id != dummyLayerId && dummyNodeTargetNode.getLayer().id != dummyLayerId) { + return 0; + } // Case the source of the dummy is the same node as n2, than the dummy node is routed below. if (dummyNodeSourceNode.equals(n1)) { updateBiggerAndSmallerAssociations(n2, n1); return -1; } else { // Calculate whether the dummy node leads to the target node. - LPort dummyNodeTargetPort = getFirstOutgoingSourcePortOfNode(n2); - LNode dummyNodeTargetNode = dummyNodeTargetPort.getNode(); if (dummyNodeTargetNode.equals(n1)) { updateBiggerAndSmallerAssociations(n2, n1); return -1; @@ -337,41 +355,101 @@ private int handleHelperDummyNodes(LNode n1, LNode n2) { return this.compare(n1, dummyNodeSourceNode); } } else if (n1.getType() == NodeType.LONG_EDGE && n2.getType() == NodeType.LONG_EDGE) { - // both are long edge feedback nodes. + // One of these edges is a feedback edge. This has to be the case since at least one of them is not connected + // to the previous layer. + // If only one is a long edge feedback node, I have a problem since these are not comparable. + // I must find the reference node in the current layer of each edge and use it instead. LPort n1dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n1); + LPort n1dummyNodeTargetPort = getFirstOutgoingTargetPortOfNode(n1); + LNode n1dummySourceNode = n1dummyNodeSourcePort.getNode(); + LNode n1dummyTargetNode = n1dummyNodeTargetPort.getNode(); + int n1LayerId = n1.getLayer().id; + boolean n1SourceFeedbackNode = false; + boolean n1TargetFeedbackNode = false; + LPort n2dummyNodeSourcePort = getFirstIncomingSourcePortOfNode(n2); - // Case both are on the same node, sort them in reverse order of their ports. - if (n1dummyNodeSourcePort.getNode().equals(n2dummyNodeSourcePort.getNode())) { - // Find the first port that occurs on the node. Since it has to be a WEST port (check this) reverse - // the order. - for (LPort port : n1dummyNodeSourcePort.getNode().getPorts()) { - if (n1dummyNodeSourcePort.equals(port)) { + LPort n2dummyNodeTargetPort = getFirstOutgoingTargetPortOfNode(n2); + LNode n2dummySourceNode = n2dummyNodeSourcePort.getNode(); + LNode n2dummyTargetNode = n2dummyNodeTargetPort.getNode(); + int n2LayerId = n2.getLayer().id; + boolean n2SourceFeedbackNode = false; + boolean n2TargetFeedbackNode = false; + + LNode n1ReferenceNode = n1; + LNode n2ReferenceNode = n2; + if (n1dummySourceNode.getLayer().id == n1LayerId) { + // This means that n1dummySourceNode is the reference node that we need to consider for ordering n1; + n1SourceFeedbackNode = true; + n1ReferenceNode = n1dummySourceNode; + } else if (n1dummyTargetNode.getLayer().id == n1LayerId) { + // This means that n1dummyNodeTargetPort is the reference node that we need to consider for ordering n1; + n1TargetFeedbackNode = true; + n1ReferenceNode = n1dummyTargetNode; + } + if (n2dummySourceNode.getLayer().id == n2LayerId) { + // This means that n2dummySourceNode is the reference node that we need to consider for ordering n2; + n2SourceFeedbackNode = true; + n2ReferenceNode = n2dummySourceNode; + } else if (n2dummyTargetNode.getLayer().id == n2LayerId) { + // This means that n2dummyNodeTargetPort is the reference node that we need to consider for ordering n2; + n2TargetFeedbackNode = true; + n2ReferenceNode = n2dummyTargetNode; + } + + // After this each reference node should be a real node in this layer that I can use to compare n1 and n2. + + // Case both are on the same node. + if (n1ReferenceNode.equals(n2ReferenceNode)) { + // Find the first port that occurs on the node. + // Since we have a feedback node, we need to reverse the decision. + // If the side we connect to is WEST. we also have to reverse the decision. + // This may be a problem since here the node comparator depends on the port order in the same layer. + if (this.beforePorts) { + // The order on the reference node ports may just be wrong since the ports are not yet sorted. + // If both reference nodes are source feedback nodes, then the model order (considering reversing) does the trick. + // If one is source one is target, I will just order them but it will always create a crossing. + // If both are target, I should not have this problem and can never be here, since these nodes + // should have a previous layer node. + if (n1SourceFeedbackNode && n2SourceFeedbackNode) { + int returnValue = new ModelOrderPortComparator(previousLayer, orderingStrategy, null, n2TargetFeedbackNode) + .compare(n1dummyNodeSourcePort, n2dummyNodeSourcePort); + if (returnValue > 0) { + updateBiggerAndSmallerAssociations(n2, n1); + return 1; + } else { + updateBiggerAndSmallerAssociations(n1, n2); + return -1; + } + } else if (n1SourceFeedbackNode && n2TargetFeedbackNode) { updateBiggerAndSmallerAssociations(n2, n1); - return -1; - } else if (n2dummyNodeSourcePort.equals(port)) { - updateBiggerAndSmallerAssociations(n1, n2); return 1; + } else if (n1TargetFeedbackNode && n2SourceFeedbackNode) { + updateBiggerAndSmallerAssociations(n1, n2); + return -1; + } else if (n1TargetFeedbackNode && n2TargetFeedbackNode) { + // In this case, there must be incoming edges that can be used for ordering. + return 0; + } + } else { + // In this case, the order of the ports can just be used. + // Since the order of WEST ports is reversed and the order for feedback edges connecting to WEST + // ports is reversed, I may just do nothing here. + for (LPort port : n1ReferenceNode.getPorts()) { + if (n1dummyNodeSourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } else if (n2dummyNodeSourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(n1, n2); + return 1; + } } } } - // Case both edges connect to separate nodes. - // In this case compare the order of their nodes in their layer. - LNode n1dummyNodeSourceNode = n1dummyNodeSourcePort.getNode(); - LNode n2dummyNodeSourceNode = n2dummyNodeSourcePort.getNode(); - Layer dummySourceLayer = n1dummyNodeSourceNode.getLayer(); - // Find the first node that occurs in the layer and order the nodes in reverse. - for (LNode node : dummySourceLayer) { - if (n1dummyNodeSourceNode.equals(node)) { - updateBiggerAndSmallerAssociations(n2, n1); - return -1; - } else if (n2dummyNodeSourceNode.equals(node)) { - updateBiggerAndSmallerAssociations(n1, n2); - return 1; - } - } - // This cannot occur. - return 0; + // If the nodes are different, just compare them since one should be a normal non-feedback dummy now. + // Worst case would be that one is a dummy and one a normal dangling node, where this would just create a + // static ordering. + return this.compare(n1ReferenceNode, n2ReferenceNode); } else { // These nodes are just two normal nodes and need to be handled by node model order. return 0; @@ -415,7 +493,7 @@ private LPort getFirstOutgoingPortOfNode(LNode node) { * @param node The node * @return The target port of the first outgoing port. */ - private LPort getFirstOutgoingSourcePortOfNode(LNode node) { + private LPort getFirstOutgoingTargetPortOfNode(LNode node) { return getFirstOutgoingPortOfNode(node).getOutgoingEdges().get(0).getTarget(); } diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/options/LongEdgeOrderingStrategy.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/options/LongEdgeOrderingStrategy.java index 45e7ef06a..e2bcb5012 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/options/LongEdgeOrderingStrategy.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/options/LongEdgeOrderingStrategy.java @@ -38,7 +38,7 @@ public int returnValue() { case DUMMY_NODE_OVER: return Integer.MAX_VALUE; case DUMMY_NODE_UNDER: - return -1; + return Integer.MIN_VALUE; default: return 0; } diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p3order/LayerSweepCrossingMinimizer.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p3order/LayerSweepCrossingMinimizer.java index 07ed1d79a..d81f2a433 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p3order/LayerSweepCrossingMinimizer.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p3order/LayerSweepCrossingMinimizer.java @@ -328,7 +328,7 @@ private int countModelOrderNodeChanges(final LNode[][] layers, final OrderingStr int wrongModelOrder = 0; for (LNode[] layer : layers) { ModelOrderNodeComparator comp = new ModelOrderNodeComparator( - previousLayer == -1 ? layers[0] : layers[previousLayer], strategy, LongEdgeOrderingStrategy.EQUAL); + previousLayer == -1 ? layers[0] : layers[previousLayer], strategy, LongEdgeOrderingStrategy.EQUAL, false); for (int i = 0; i < layer.length; i++) { for (int j = i + 1; j < layer.length; j++) { if (layer[i].hasProperty(InternalProperties.MODEL_ORDER) From 56949cfaa178e143a2f7f002be17d001986774b3 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 26 Aug 2024 15:10:33 +0200 Subject: [PATCH 20/20] Handle unconnected ports. --- .../preserveorder/ModelOrderPortComparator.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java index c6fac5edd..d16d6277d 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/preserveorder/ModelOrderPortComparator.java @@ -219,9 +219,7 @@ public int compare(final LPort originalP1, final LPort originalP2) { || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { // Some ports are ordered in the way around. // Previously this did not matter, since the north south processor did override the ordering. - LPort temp = p1; - p1 = p2; - p2 = temp; + reverseOrder = -reverseOrder; } LNode p1TargetNode = p1.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); LNode p2TargetNode = p2.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); @@ -306,9 +304,19 @@ public int compare(final LPort originalP1, final LPort originalP2) { return -1; } else if (p1.hasProperty(InternalProperties.MODEL_ORDER) && p2.hasProperty(InternalProperties.MODEL_ORDER)) { // The ports have no edges. - // Use the port model order to compare them. + // Use the port model order to compare them. This can always be very bad since these unconnected ports + // can transitively order nodes that should be ordered differently. + // This can only be prevented, if one handles the sorting such that unconnected ports are handled last. int p1MO = p1.getProperty(InternalProperties.MODEL_ORDER); int p2MO = p2.getProperty(InternalProperties.MODEL_ORDER); + // Still check the side, since WEST and SOUTH must be the other way around. + if (p1.getSide() == PortSide.WEST && p2.getSide() == PortSide.WEST + || p1.getSide() == PortSide.SOUTH && p2.getSide() == PortSide.SOUTH) { + // Some ports are ordered in the way around. + // Previously this did not matter, since the north south processor did override the ordering. + reverseOrder = -reverseOrder; + } + if (p1MO > p2MO) { updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); return reverseOrder;