From 746e9225fb13ec85fe849e5387d667b5d0a1293a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B6ren=20Domr=C3=B6s?=
Date: Wed, 6 Apr 2022 14:59:45 +0200
Subject: [PATCH] layered: Component model order: Port sides do not overrule
component order. (#819)
* layered: Component model order: Port sides do not overrule component
order.
* Layered: Added model order component order strategy.
* Revert unnecessary changes.
* Added all cases for compacting component groups with offsets.
---
.../META-INF/MANIFEST.MF | 2 +-
.../org/eclipse/elk/alg/layered/Layered.melk | 10 +-
.../layered/components/ComponentGroup.java | 21 +-
.../components/ComponentGroupGraphPlacer.java | 14 +-
.../ComponentGroupModelOrderGraphPlacer.java | 172 ++++++++++++
.../components/ComponentOrderingStrategy.java | 32 +++
.../components/ComponentsProcessor.java | 12 +-
.../components/ModelOrderComponentGroup.java | 265 ++++++++++++++++++
.../components/SimpleRowGraphPlacer.java | 2 +-
.../graph/transform/ElkGraphImporter.java | 9 +-
10 files changed, 515 insertions(+), 24 deletions(-)
create mode 100644 plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupModelOrderGraphPlacer.java
create mode 100644 plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentOrderingStrategy.java
create mode 100644 plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ModelOrderComponentGroup.java
diff --git a/plugins/org.eclipse.elk.alg.layered/META-INF/MANIFEST.MF b/plugins/org.eclipse.elk.alg.layered/META-INF/MANIFEST.MF
index f1f48fb651..eab0f0d186 100644
--- a/plugins/org.eclipse.elk.alg.layered/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.elk.alg.layered/META-INF/MANIFEST.MF
@@ -16,7 +16,7 @@ Export-Package: org.eclipse.elk.alg.layered,
org.eclipse.elk.alg.layered.compaction.oned;x-friends:="org.eclipse.elk.alg.layered.test",
org.eclipse.elk.alg.layered.compaction.oned.algs;x-friends:="org.eclipse.elk.alg.layered.test",
org.eclipse.elk.alg.layered.compaction.recthull;x-friends:="org.eclipse.elk.alg.layered.test",
- org.eclipse.elk.alg.layered.components;x-friends:="org.eclipse.elk.alg.layered.test",
+ org.eclipse.elk.alg.layered.components,
org.eclipse.elk.alg.layered.compound;x-friends:="org.eclipse.elk.alg.layered.test",
org.eclipse.elk.alg.layered.graph,
org.eclipse.elk.alg.layered.graph.transform;x-friends:="org.eclipse.elk.alg.layered.test",
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk
index 0d32a253b0..8079cb7b66 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk
@@ -11,6 +11,7 @@ package org.eclipse.elk.alg.layered
import java.util.List
import org.eclipse.elk.alg.layered.LayeredLayoutProvider
+import org.eclipse.elk.alg.layered.components.ComponentOrderingStrategy
import org.eclipse.elk.core.math.ElkPadding
import org.eclipse.elk.core.options.Direction
import org.eclipse.elk.core.options.EdgeRouting
@@ -948,12 +949,15 @@ group considerModelOrder {
default = false
targets nodes
}
- option components: boolean {
+ option components: ComponentOrderingStrategy {
label "Consider Model Order for Components"
description
- "If enabled orders components by their minimal node model order."
+ "If set to NONE the usual ordering strategy (by cumulative node priority and size of nodes) is used.
+ INSIDE_PORT_SIDES orders the components with external ports only inside the groups with the same port side.
+ FORCE_MODEL_ORDER enforces the mode order on components. This option might produce bad alignments and sub
+ optimal drawings in terms of used area since the ordering should be respected."
targets parents
- default = false
+ default = ComponentOrderingStrategy.NONE
requires org.eclipse.elk.separateConnectedComponents
}
option longEdgeStrategy: LongEdgeOrderingStrategy {
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroup.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroup.java
index 5d4b2e10d3..ff2b3a1319 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroup.java
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroup.java
@@ -71,7 +71,7 @@
*
* This class is not supposed to be public, but needs to be for JUnit tests to find it.
*/
-public final class ComponentGroup {
+public class ComponentGroup {
///////////////////////////////////////////////////////////////////////////////
// Constants
@@ -88,7 +88,7 @@ public final class ComponentGroup {
* port sides to a list of port side sets that must not already exist in this group for a
* component to be added.
*/
- private static final Multimap, Set> CONSTRAINTS = HashMultimap.create();
+ protected static final Multimap, Set> CONSTRAINTS = HashMultimap.create();
static {
// Setup constraints
@@ -174,7 +174,7 @@ public final class ComponentGroup {
/**
* A map mapping external port side combinations to components in this group.
*/
- private Multimap, LGraph> components = ArrayListMultimap.create();
+ protected Multimap, LGraph> components = ArrayListMultimap.create();
///////////////////////////////////////////////////////////////////////////////
@@ -212,8 +212,8 @@ public ComponentGroup(final LGraph component) {
public boolean add(final LGraph component) {
if (canAdd(component)) {
components.put(
- component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS),
- component);
+ component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS),
+ component);
return true;
} else {
return false;
@@ -227,7 +227,7 @@ public boolean add(final LGraph component) {
* @return {@code true} if the group has enough space left to add the component, {@code false}
* otherwise.
*/
- private boolean canAdd(final LGraph component) {
+ protected boolean canAdd(final LGraph component) {
// Check if we have a component with incompatible external port sides
Set candidateSides = component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS);
Collection> constraints = CONSTRAINTS.get(candidateSides);
@@ -243,6 +243,15 @@ private boolean canAdd(final LGraph component) {
return true;
}
+ /**
+ * Returns all port sides in this component group.
+ *
+ * @return all port sides in this component group.
+ */
+ public Collection> getPortSides() {
+ return components.keys();
+ }
+
/**
* Returns all components in this component group.
*
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupGraphPlacer.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupGraphPlacer.java
index 8b447f5b13..456e73f144 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupGraphPlacer.java
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupGraphPlacer.java
@@ -49,7 +49,7 @@
*
* The target graph must not be contained in the list of components.
*/
-final class ComponentGroupGraphPlacer extends AbstractGraphPlacer {
+class ComponentGroupGraphPlacer extends AbstractGraphPlacer {
///////////////////////////////////////////////////////////////////////////////
// Variables
@@ -57,7 +57,7 @@ final class ComponentGroupGraphPlacer extends AbstractGraphPlacer {
/**
* List of component groups holding the different components.
*/
- private final List componentGroups = Lists.newArrayList();
+ protected final List componentGroups = Lists.newArrayList();
///////////////////////////////////////////////////////////////////////////////
@@ -142,7 +142,7 @@ public void combine(final List components, final LGraph target) {
*
* @param component the component to be placed.
*/
- private void addComponent(final LGraph component) {
+ protected void addComponent(final LGraph component) {
// Check if one of the existing component groups has some place left
for (ComponentGroup group : componentGroups) {
if (group.add(component)) {
@@ -176,7 +176,7 @@ private void addComponent(final LGraph component) {
* @param spacing the amount of space to leave between two components.
* @return the group's size.
*/
- private KVector placeComponents(final ComponentGroup group, final double spacing) {
+ protected KVector placeComponents(final ComponentGroup group, final double spacing) {
// Determine the spacing between two components
// Place the different sector components and remember the amount of space their placement uses.
@@ -293,7 +293,7 @@ private KVector placeComponents(final ComponentGroup group, final double spacing
* @return the space used by the component placement, including spacing to the right and to the
* bottom of the components.
*/
- private KVector placeComponentsHorizontally(final Collection components,
+ protected KVector placeComponentsHorizontally(final Collection components,
final double spacing) {
KVector size = new KVector();
@@ -321,7 +321,7 @@ private KVector placeComponentsHorizontally(final Collection components,
* @param spacing the amount of space to leave between two components.
* @return the space used by the component placement.
*/
- private KVector placeComponentsVertically(final Collection components,
+ protected KVector placeComponentsVertically(final Collection components,
final double spacing) {
KVector size = new KVector();
@@ -349,7 +349,7 @@ private KVector placeComponentsVertically(final Collection components,
* @param spacing the amount of space to leave between two components.
* @return the space used by the component placement.
*/
- private KVector placeComponentsInRows(final Collection components,
+ protected KVector placeComponentsInRows(final Collection components,
final double spacing) {
/* This code is basically taken from the SimpleRowGraphPlacer. */
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupModelOrderGraphPlacer.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupModelOrderGraphPlacer.java
new file mode 100644
index 0000000000..49ba11cb87
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentGroupModelOrderGraphPlacer.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Kiel University and others.
+ *
+ * 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.components;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.elk.alg.layered.graph.LGraph;
+import org.eclipse.elk.alg.layered.options.LayeredOptions;
+import org.eclipse.elk.core.math.KVector;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.options.EdgeRouting;
+import org.eclipse.elk.core.options.PortSide;
+
+/**
+ * A graph placer that tries to place the components of a graph with taking the model order and the
+ * connections to external ports into account. This graph placer should only be used if the constraints applying to the
+ * external ports are either {@code FREE} or {@code FIXED_SIDES}.
+ */
+public class ComponentGroupModelOrderGraphPlacer extends ComponentGroupGraphPlacer {
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // AbstractGraphPlacer
+
+ @Override
+ public void combine(final List components, final LGraph target) {
+ componentGroups.clear();
+ assert !components.contains(target);
+ target.getLayerlessNodes().clear();
+
+ // Check if there are any components to be placed
+ if (components.isEmpty()) {
+ target.getSize().x = 0;
+ target.getSize().y = 0;
+ return;
+ }
+
+ // Set the graph properties
+ LGraph firstComponent = components.get(0);
+ target.copyProperties(firstComponent);
+
+ // Construct component groups
+ for (LGraph component : components) {
+ addComponent(component);
+ }
+
+ // Place components in each group
+ KVector offset = new KVector();
+ KVector maxSize = new KVector();
+ KVector maxGroupSize = new KVector();
+ double componentSpacing = firstComponent.getProperty(LayeredOptions.SPACING_COMPONENT_COMPONENT);
+
+ // The current free y has to be calculated based on that constraints the current graph has.
+ // The offset has to be readded if a NORTH edges is present.
+ // All previous component that might have different sizes have to be taken into account here.
+ for (ComponentGroup group : componentGroups) {
+ // Place the components
+ for (Set side : group.getPortSides()) {
+ if (target.getProperty(CoreOptions.DIRECTION).isHorizontal()) {
+ if (side.contains(PortSide.NORTH)) {
+ offset.x = maxGroupSize.x;
+ break;
+ }
+ } else if (target.getProperty(CoreOptions.DIRECTION).isVertical()) {
+ if (side.contains(PortSide.EAST)) {
+ offset.y = maxGroupSize.y;
+ break;
+ }
+ }
+ }
+ KVector groupSize = this.placeComponents((ModelOrderComponentGroup) group, componentSpacing);
+ offsetGraphs(group.getComponents(), offset.x, offset.y);
+ maxSize.x = Math.max(maxSize.x, groupSize.x + offset.x);
+ maxSize.y = Math.max(maxSize.y, groupSize.y + offset.y);
+ // Compute the new offset. The previous component might not be in vertical or horizontal conflict.
+ // Normally this cannot occur but here graphs with the same port sides may be in different groups.
+ maxGroupSize = new KVector(Math.max(maxGroupSize.x, groupSize.x + offset.x),
+ Math.max(maxGroupSize.y, groupSize.y + offset.y));
+ for (Set side : group.getPortSides()) {
+ if (target.getProperty(CoreOptions.DIRECTION).isHorizontal()) {
+ if (side.contains(PortSide.SOUTH)) {
+ offset.x += groupSize.x;
+ break;
+ }
+ } else if (target.getProperty(CoreOptions.DIRECTION).isVertical()) {
+ if (side.contains(PortSide.WEST)) {
+ offset.y += groupSize.y;
+ break;
+ }
+ }
+ }
+ for (Set side : group.getPortSides()) {
+ if (target.getProperty(CoreOptions.DIRECTION).isHorizontal()) {
+ if (side.contains(PortSide.WEST)) {
+ offset.y += groupSize.y;
+ break;
+ }
+ } else if (target.getProperty(CoreOptions.DIRECTION).isVertical()) {
+ if (side.contains(PortSide.NORTH)) {
+ offset.x += groupSize.x;
+ break;
+ }
+ }
+ }
+ }
+
+ // Set the graph's new size (the component group sizes include additional spacing
+ // on the right and bottom sides which we need to subtract at this point)
+ target.getSize().x = maxSize.x - componentSpacing;
+ target.getSize().y = maxSize.y - componentSpacing;
+
+ // if compaction is desired, do so!cing;
+ if (firstComponent.getProperty(LayeredOptions.COMPACTION_CONNECTED_COMPONENTS)
+ // the compaction only supports orthogonally routed edges
+ && firstComponent.getProperty(LayeredOptions.EDGE_ROUTING) == EdgeRouting.ORTHOGONAL) {
+
+ // apply graph offsets (which we reset later on)
+ // since the compaction works in a common coordinate system
+ for (LGraph h : components) {
+ offsetGraph(h, h.getOffset().x, h.getOffset().y);
+ }
+
+ ComponentsCompactor compactor = new ComponentsCompactor();
+ compactor.compact(components, target.getSize(), componentSpacing);
+
+ // the compaction algorithm places components absolutely,
+ // therefore we have to use the final drawing's offset
+ for (LGraph h : components) {
+ h.getOffset().reset().add(compactor.getOffset());
+ }
+
+ // set the new (compacted) graph size
+ target.getSize().reset().add(compactor.getGraphSize());
+ }
+
+ // finally move the components to the combined graph
+ for (ComponentGroup group : componentGroups) {
+ moveGraphs(target, group.getComponents(), 0, 0);
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Component Group Building
+
+ /**
+ * Adds the given component to the first component group that has place for it.
+ *
+ * @param component the component to be placed.
+ */
+ protected void addComponent(final LGraph component) {
+ // Check if one of the existing component groups has some place left
+ if (this.componentGroups.size() > 0) {
+ ModelOrderComponentGroup group =
+ (ModelOrderComponentGroup) this.componentGroups.get(componentGroups.size() - 1);
+ if (group.add(component)) {
+ return;
+ }
+ }
+
+ // Create a new component group for the component
+ componentGroups.add(new ModelOrderComponentGroup(component));
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentOrderingStrategy.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentOrderingStrategy.java
new file mode 100644
index 0000000000..22fae90358
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentOrderingStrategy.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Kiel University and others.
+ *
+ * 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.components;
+
+/**
+ * Strategy to order components by model order.
+ */
+public enum ComponentOrderingStrategy {
+ /**
+ * Components are ordered by priority or size.
+ */
+ NONE,
+ /**
+ * Components are ordered only inside their port groups by their minimal node model order
+ * to prevent ONO alignment cases.
+ * This usually results in a smaller drawing.
+ */
+ INSIDE_PORT_SIDE_GROUPS,
+ /**
+ * Components are ordered by their minimal node model order. This may still have some bugs and may create worse
+ * drawings in terms of size compared to INSIDE_PORT_SIDE_GROUPS.
+ */
+ FORCE_MODEL_ORDER;
+
+}
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentsProcessor.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentsProcessor.java
index 2524fe3817..35a6d63d75 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentsProcessor.java
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ComponentsProcessor.java
@@ -65,6 +65,9 @@ public final class ComponentsProcessor {
/** Cached instance of a {@link ComponentGroupGraphPlacer}. */
private final ComponentGroupGraphPlacer componentGroupGraphPlacer = new ComponentGroupGraphPlacer();
+ /** Cached instance of a {@link ComponentGroupGraphPlacer}. */
+ private final ComponentGroupModelOrderGraphPlacer componentGroupModelOrderGraphPlacer =
+ new ComponentGroupModelOrderGraphPlacer();
/** Cached instance of a {@link SimpleRowGraphPlacer}. */
private final SimpleRowGraphPlacer simpleRowGraphPlacer = new SimpleRowGraphPlacer();
/** Graph placer to be used to combine the different components back into a single graph. */
@@ -135,14 +138,19 @@ public List split(final LGraph graph) {
if (extPorts) {
// With external port connections, we want to use the more complex components
// placement algorithm
- graphPlacer = componentGroupGraphPlacer;
+ if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS)
+ == ComponentOrderingStrategy.FORCE_MODEL_ORDER) {
+ graphPlacer = componentGroupModelOrderGraphPlacer;
+ } else {
+ graphPlacer = componentGroupGraphPlacer;
+ }
}
} else {
result = Arrays.asList(graph);
}
// If model order should be preserved the connected components should be ordered by their elements.
// The component with the node with the smallest order should be first.
- if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS)) {
+ if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE) {
Collections.sort(result, (g1, g2) -> {
int g1Order = LGraphUtil.getMinimalModelOrder(g1);
int g2Order = LGraphUtil.getMinimalModelOrder(g2);
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ModelOrderComponentGroup.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ModelOrderComponentGroup.java
new file mode 100644
index 0000000000..0a6a9206a5
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/ModelOrderComponentGroup.java
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Kiel University and others.
+ *
+ * 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.components;
+
+import static org.eclipse.elk.core.options.PortSide.SIDES_EAST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_EAST_SOUTH;
+import static org.eclipse.elk.core.options.PortSide.SIDES_EAST_SOUTH_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_EAST_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NONE;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_EAST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_EAST_SOUTH;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_EAST_SOUTH_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_EAST_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_SOUTH;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_SOUTH_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_NORTH_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_SOUTH;
+import static org.eclipse.elk.core.options.PortSide.SIDES_SOUTH_WEST;
+import static org.eclipse.elk.core.options.PortSide.SIDES_WEST;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.elk.alg.layered.graph.LGraph;
+import org.eclipse.elk.alg.layered.options.InternalProperties;
+import org.eclipse.elk.core.options.PortSide;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Model order component group that saves additionally to the port side the order of the components.
+ *
+ */
+public class ModelOrderComponentGroup extends ComponentGroup {
+
+ /**
+ * Update constraints map with all constraints that occur if the key is inserted in a component group with the
+ * value already in it. This contains only additional sides to the CONSTRAINTS of the super class.
+ */
+ protected static final Multimap, Set> MODEL_ORDER_CONSTRAINTS = HashMultimap.create();
+
+ static {
+ // Key is inserted in component group with value
+ // E.g. NORTH can not be put in the same component group as NONE since it would be placed above NONE, which
+ // is against the model order.
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_NONE);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_NORTH);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_EAST); // XXX
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH_WEST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_SOUTH, SIDES_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_SOUTH);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_WEST);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_NORTH_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_NORTH_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_NORTH_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_NORTH_EAST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_NORTH_EAST);
+
+ // NW has nothing since it is in the first slot
+
+ // Only conflicts since it is in the last slot
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH_WEST, SIDES_EAST_SOUTH);
+// MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_SOUTH, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_EAST_SOUTH);
+// MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_SOUTH, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_EAST_SOUTH);
+// MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_SOUTH_WEST, SIDES_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_SOUTH_WEST, SIDES_EAST_SOUTH);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_SOUTH, SIDES_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_SOUTH_WEST, SIDES_SOUTH_WEST);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH_WEST, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_EAST_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH_WEST, SIDES_EAST_WEST);
+
+ // NEW no additional conflicts
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST_WEST, SIDES_EAST_SOUTH_WEST);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_NORTH_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST, SIDES_NORTH_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH, SIDES_NORTH_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_NORTH_SOUTH_WEST);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NONE, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_SOUTH, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_WEST, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_NORTH_EAST_SOUTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_NORTH_EAST_SOUTH);
+
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_WEST, SIDES_NORTH_EAST_SOUTH_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_EAST, SIDES_NORTH_EAST_SOUTH_WEST);
+
+ // Conflicts that seem solvable but that arise since the order of C, EW, W, E is fix
+ // EW is before C and W and E
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_WEST);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_EAST_WEST, SIDES_EAST);
+
+ // Conflicts that seem solvable but that arise since the order of C, NS, N, S is fix
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_NONE);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_NORTH);
+ MODEL_ORDER_CONSTRAINTS.put(SIDES_NORTH_SOUTH, SIDES_SOUTH);
+
+ }
+
+
+
+ private List componentOrder = Lists.newLinkedList();
+ ///////////////////////////////////////////////////////////////////////////////
+ // Constructors
+
+ /**
+ * Constructs a new, empty component group.
+ */
+ public ModelOrderComponentGroup() {
+
+ }
+
+ /**
+ * Constructs a new component group with the given initial component. This is equivalent to
+ * constructing an empty component group and then calling {@link #add(LGraph)}.
+ *
+ * @param component the component to be added to the group.
+ */
+ public ModelOrderComponentGroup(final LGraph component) {
+ add(component);
+ getComponentOrder().add(component);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Component Management
+
+ /**
+ * Tries to add the given component to the group. Before adding the component, a call to
+ * {@link #canAdd(LGraph)} determines if the component can actually be added to this
+ * group.
+ *
+ * @param component the component to be added to this group.
+ * @return {@code true} if the component was successfully added, {@code false} otherwise.
+ */
+ public boolean add(final LGraph component) {
+ if (canAdd(component)) {
+ components.put(
+ component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS),
+ component);
+ getComponentOrder().add(component);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether this group has enough space left to add a given component.
+ *
+ * @param component the component to be added to the group.
+ * @return {@code true} if the group has enough space left to add the component, {@code false}
+ * otherwise.
+ */
+ protected boolean canAdd(final LGraph component) {
+ // Check if we have a component with incompatible external port sides
+ Set candidateSides = component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS);
+ Collection> constraints = CONSTRAINTS.get(candidateSides);
+ Collection> modelOrderConstraints = MODEL_ORDER_CONSTRAINTS.get(candidateSides);
+
+ for (Set constraint : constraints) {
+ if (!components.get(constraint).isEmpty()) {
+ // A component with a conflicting external port side combination exists
+ return false;
+ }
+ }
+ for (Set constraint : modelOrderConstraints) {
+ if (!components.get(constraint).isEmpty()) {
+ // A component with a conflicting external port side combination exists considering model order
+ return false;
+ }
+ }
+
+ // We haven't found any conflicting components
+ return true;
+ }
+
+ /**
+ * @return the componentOrder
+ */
+ public List getComponentOrder() {
+ return componentOrder;
+ }
+}
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/SimpleRowGraphPlacer.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/SimpleRowGraphPlacer.java
index 02c02f531c..485054271f 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/SimpleRowGraphPlacer.java
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/components/SimpleRowGraphPlacer.java
@@ -56,7 +56,7 @@ public void combine(final List components, final LGraph target) {
}
assert !components.contains(target);
// Sort components
- if (!target.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS)) {
+ if (target.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) == ComponentOrderingStrategy.NONE) {
// assign priorities
for (LGraph graph : components) {
int priority = 0;
diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/graph/transform/ElkGraphImporter.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/graph/transform/ElkGraphImporter.java
index c5f9e27f0d..0428310ede 100644
--- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/graph/transform/ElkGraphImporter.java
+++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/graph/transform/ElkGraphImporter.java
@@ -16,6 +16,7 @@
import java.util.Set;
import org.eclipse.elk.alg.common.nodespacing.NodeLabelAndSizeCalculator;
+import org.eclipse.elk.alg.layered.components.ComponentOrderingStrategy;
import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.LGraphElement;
@@ -229,7 +230,7 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
- || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS))
+ || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE)
&& !child.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER)) {
child.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
@@ -246,7 +247,7 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
- || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS)) {
+ || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE) {
elkedge.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
}
@@ -311,7 +312,7 @@ private void importHierarchicalGraph(final ElkNode elkgraph, final LGraph lgraph
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
- || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS))
+ || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE)
&& !elknode.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER)) {
// Assign a model order to the nodes as they are read
elknode.setProperty(InternalProperties.MODEL_ORDER, index++);
@@ -383,7 +384,7 @@ private void importHierarchicalGraph(final ElkNode elkgraph, final LGraph lgraph
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
- || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS)) {
+ || elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE) {
// Assign a model order to the edges as they are read
elkedge.setProperty(InternalProperties.MODEL_ORDER, index++);
}