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++); }