Skip to content

Commit

Permalink
layered: Component model order: Port sides do not overrule component …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
soerendomroes committed Apr 6, 2022
1 parent 2cb68a5 commit 746e922
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 24 deletions.
2 changes: 1 addition & 1 deletion plugins/org.eclipse.elk.alg.layered/META-INF/MANIFEST.MF
Expand Up @@ -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",
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Expand Up @@ -71,7 +71,7 @@
*
* <p>This class is not supposed to be public, but needs to be for JUnit tests to find it.</p>
*/
public final class ComponentGroup {
public class ComponentGroup {

///////////////////////////////////////////////////////////////////////////////
// Constants
Expand All @@ -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.</p>
*/
private static final Multimap<Set<PortSide>, Set<PortSide>> CONSTRAINTS = HashMultimap.create();
protected static final Multimap<Set<PortSide>, Set<PortSide>> CONSTRAINTS = HashMultimap.create();

static {
// Setup constraints
Expand Down Expand Up @@ -174,7 +174,7 @@ public final class ComponentGroup {
/**
* A map mapping external port side combinations to components in this group.
*/
private Multimap<Set<PortSide>, LGraph> components = ArrayListMultimap.create();
protected Multimap<Set<PortSide>, LGraph> components = ArrayListMultimap.create();


///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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;
Expand All @@ -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<PortSide> candidateSides = component.getProperty(InternalProperties.EXT_PORT_CONNECTIONS);
Collection<Set<PortSide>> constraints = CONSTRAINTS.get(candidateSides);
Expand All @@ -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<Set<PortSide>> getPortSides() {
return components.keys();
}

/**
* Returns all components in this component group.
*
Expand Down
Expand Up @@ -49,15 +49,15 @@
*
* <p>The target graph must not be contained in the list of components.</p>
*/
final class ComponentGroupGraphPlacer extends AbstractGraphPlacer {
class ComponentGroupGraphPlacer extends AbstractGraphPlacer {

///////////////////////////////////////////////////////////////////////////////
// Variables

/**
* List of component groups holding the different components.
*/
private final List<ComponentGroup> componentGroups = Lists.newArrayList();
protected final List<ComponentGroup> componentGroups = Lists.newArrayList();


///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -142,7 +142,7 @@ public void combine(final List<LGraph> 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)) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<LGraph> components,
protected KVector placeComponentsHorizontally(final Collection<LGraph> components,
final double spacing) {

KVector size = new KVector();
Expand Down Expand Up @@ -321,7 +321,7 @@ private KVector placeComponentsHorizontally(final Collection<LGraph> 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<LGraph> components,
protected KVector placeComponentsVertically(final Collection<LGraph> components,
final double spacing) {

KVector size = new KVector();
Expand Down Expand Up @@ -349,7 +349,7 @@ private KVector placeComponentsVertically(final Collection<LGraph> 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<LGraph> components,
protected KVector placeComponentsInRows(final Collection<LGraph> components,
final double spacing) {

/* This code is basically taken from the SimpleRowGraphPlacer. */
Expand Down
@@ -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<LGraph> 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<PortSide> 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<PortSide> 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<PortSide> 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));
}

}
@@ -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;

}
Expand Up @@ -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. */
Expand Down Expand Up @@ -135,14 +138,19 @@ public List<LGraph> 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);
Expand Down

0 comments on commit 746e922

Please sign in to comment.