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..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 @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.elk.alg.layered.graph.LEdge; @@ -55,8 +56,16 @@ 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); + // 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), true); + 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) { @@ -65,7 +74,6 @@ 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(), new ModelOrderPortComparator(previousLayer, graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY), @@ -74,11 +82,12 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito progressMonitor.log("Node " + node + " ports: " + node.getPorts()); } } - // 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))); + // 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), false); + SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator); + progressMonitor.log("Layer " + layerIndex + ": " + layer); layerIndex++; } @@ -138,5 +147,35 @@ 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(); + } + + 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(); + } } 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..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 @@ -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; @@ -51,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. * @@ -59,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); } @@ -73,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 @@ -116,7 +123,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(); } } @@ -126,7 +133,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(); } } @@ -141,7 +148,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 @@ -155,13 +163,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)) { @@ -172,36 +188,72 @@ 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 - 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) { + if (comparedWithLongEdgeFeedback > 0) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } + 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); + return 1; + } else { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } } - 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) { + if (comparedWithLongEdgeFeedback > 0) { + updateBiggerAndSmallerAssociations(n1, n2); + } else { + updateBiggerAndSmallerAssociations(n2, n1); + } + return comparedWithLongEdgeFeedback; + } + } + // 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. - int n1ModelOrder = n1.getProperty(InternalProperties.MODEL_ORDER); - int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER); - if (n1ModelOrder > n2ModelOrder) { - updateBiggerAndSmallerAssociations(n1, n2); + // This is also the fallback case if one of the nodes is not connected to the previous layer. + 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); + return 1; + } else { + updateBiggerAndSmallerAssociations(n2, n1); + return -1; + } } else { + // 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; } - return Integer.compare( - n1ModelOrder, - n2ModelOrder); } /** @@ -219,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. @@ -247,4 +299,209 @@ 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 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. + 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 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. + 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) { + // 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); + 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 (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; + } + } + } + } + + // 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; + } + } + + /** + * 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 getFirstOutgoingTargetPortOfNode(LNode node) { + return getFirstOutgoingPortOfNode(node).getOutgoingEdges().get(0).getTarget(); + } + + /** + * Clears the transitive ordering. + */ + public void clearTransitiveOrdering() { + this.biggerThan = new HashMap<>(); + this.smallerThan = new HashMap<>(); + } } 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..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 @@ -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; @@ -27,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 { @@ -90,16 +95,6 @@ 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) { - // 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)) { @@ -123,56 +118,109 @@ 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); + if (result > 0) { + updateBiggerAndSmallerAssociations(p1, p2, 1); } else { - updateBiggerAndSmallerAssociations(p1, p2); + 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()) { - // 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); + 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. + reverseOrder = -reverseOrder; + } + + 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)) { + // 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, reverseOrder); + return -reverseOrder; + } else if (p2SourcePort.equals(port)) { + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } - return result; } } - LNode p1Node = p1.getIncomingEdges().get(0).getSource().getNode(); - LNode p2Node = p2.getIncomingEdges().get(0).getSource().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); + // 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___| + // + 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. + reverseOrder = -reverseOrder; + } + if (inPreviousLayer > 0) { + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; + } else { + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; + } + } + } + + // 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) { + if (inPreviousLayer > 0) { + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - // In this case both incoming edges must have a model order set. Check it. - return Integer.compare(p1MO, p2MO); } - 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 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 > 0) { + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; + } else { + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; + } } } } // 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. + reverseOrder = -reverseOrder; + } LNode p1TargetNode = p1.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); LNode p2TargetNode = p2.getProperty(InternalProperties.LONG_EDGE_TARGET_NODE); @@ -183,11 +231,12 @@ 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); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - return Integer.compare(p1MO, p2MO); } // If port order is used instead of edge order, consult it to make decisions. @@ -195,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) { - updateBiggerAndSmallerAssociations(p1, p2); + if (result > 0) { + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; + } else { + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - 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; @@ -210,28 +261,22 @@ 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) - && !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); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - 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. if (targetNodeModelOrder != null) { if (targetNodeModelOrder.containsKey(p1TargetNode)) { p1Order = targetNodeModelOrder.get(p1TargetNode); @@ -240,36 +285,48 @@ 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); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - return Integer.compare(p1Order, p2Order); } // 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. - // 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); + updateBiggerAndSmallerAssociations(p1, p2, reverseOrder); + return reverseOrder; } else { - updateBiggerAndSmallerAssociations(p2, p1); + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } - return Integer.compare(p1MO, - p2MO); } else { - updateBiggerAndSmallerAssociations(p2, p1); - return -1; + updateBiggerAndSmallerAssociations(p2, p1, reverseOrder); + return -reverseOrder; } } @@ -291,7 +348,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); @@ -312,6 +375,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. + * + * @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)) { + // 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)) { + return 1; + } + } + return 0; // Should never happen, the previous layer needs these nodes in it. + } + private class PortSideComparator implements Comparator { /* (non-Javadoc) @@ -323,4 +409,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<>(); + } } 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) 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..fc5de20e5 --- /dev/null +++ b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/intermediate/ConcreteSortByInputModelTest.java @@ -0,0 +1,2132 @@ +/******************************************************************************* + * 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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()); + + // 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 + 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); + // 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.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()); + + // 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 + 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 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 + 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); + // 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.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()); + + // 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 + 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()); + + // 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 + 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); + // 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.WEST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + 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 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()); + + // 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 + 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()); + + // 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 + 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); + // 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.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); + 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()); + + // 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 + 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()); + + // 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 + 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); + // 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.WEST); + ElkPort p2 = ElkGraphUtil.createPort(ni); + 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 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()); + + // 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 + 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()); + + // 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 + 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); + // 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.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); + 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()); + + // 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