Skip to content

Commit 95417f0

Browse files
authored
Layer Splitting Improvements (#1094)
* Layer Splitter Update rename property properties can now be set on any node in a layer lifted unnecessary restriction in the combination of certain properties * add blogpost explaining layer splitting
1 parent 7ba490b commit 95417f0

File tree

8 files changed

+118
-33
lines changed

8 files changed

+118
-33
lines changed

docs/content/blog/2024.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: "2024"
3+
menu:
4+
main:
5+
identifier: "2024"
6+
parent: "Blog"
7+
weight: 20
8+
---
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
title: "Layer Unzipping"
3+
menu:
4+
main:
5+
identifier: "24-12-06-layer-unzipping"
6+
parent: "2024"
7+
weight: 10
8+
---
9+
10+
_By Maximilian Kasperowski, December 6, 2024_
11+
12+
The coming update (ELK 0.9.2) introduces a new feature to control the positioning of nodes in a layer for further compaction.
13+
Layer Unzipping can split up the nodes of a single layer into multiple layers.
14+
This reduces the total height and can be helpful in certain situations.
15+
16+
The update introduces three new properties:
17+
- `org.eclipse.elk.layerUnzipping.strategy`
18+
- `org.eclipse.elk.layerUnzipping.layerSplit`
19+
- `org.eclipse.elk.layerUnizpping.resetOnLongEdges`
20+
21+
This [elklive example](https://rtsys.informatik.uni-kiel.de/elklive/elkgraph.html?compressedContent=MYewdgzglgJgpgJwLIngGwPIPggdBAFwQEMC4BzATwC4ACABQCUBRAMWcYH1mARAcWYBlAFBpilRAFUwALygAHeVDDl8RUhRq0AggBkAKhwBy2-QEkjfYcLCo4tMAEZa1uDHL2ntALQA+BwBMLm4eDs5+DgDMwiGeQRFgACwx7p4ArD7+Sa6pYZkOybF5CWkpoV4JAGzWAPQ1tABmUAiEtGIIoWISCDZ2gbQA3sK0I23iUrIKSiq4CHAQcAQYYLrg5MypEHQNxGgLtHW0MFAQxABGaPbwOwCuaAS0Z3AAFsQAblAgNwi0xGAwtCIcFIbTWtFiEF+kLm5DuxB+tngEGEAF9evAooNhqMuhM5IplKpcQhBPI0FACHQAqj0Z5Ei5EekGX0wJUXMJDgtQP8xh17MTaYFnENRmNutJ8dMieMSWSKXRomjGVFnIKkqrlWA0hqWZVVWU6fl1cFcqyjXqcuUAuEsgB2E3lSI2hz2g0FZ1gV1FLUer2mvVG11q21AA) demonstrates the properties and the exact behaviour of the properties is explained in the sections below.
22+
23+
## Layer Unzipping Strategy
24+
For now there is only the strategy `LayerUnzippingStrategy.ALTERNATING`.
25+
It evenly distributes the nodes into several sub-layers.
26+
The first node goes into the first sub-layer, the second goes into the second and so on.
27+
The default configuration is to use two sub-layers.
28+
29+
## Configuring the Number of Sub-layers
30+
The number of sub-layers is two by default and can be changed with the `layerSplit` property.
31+
The property applies to an entire layer.
32+
To use it, it must be set on any node of that layer.
33+
If multiple values are set, then the lowest value is used.
34+
35+
## Long Edge Treatment
36+
Under the hood long edges are implemented using invisible dummy nodes.
37+
The default behaviour is begin the alternatinon anew after a long edge, but this behaviour can be disabled for a layer by setting `resetOnLongEdges` to `false` for any node in the layer.

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/GraphConfigurator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ private LayoutProcessorConfiguration<LayeredPhases, LGraph> getPhaseIndependentL
299299
}
300300

301301
switch (lgraph.getProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY)) {
302-
case N_LAYERS:
303-
configuration.addBefore(LayeredPhases.P4_NODE_PLACEMENT, IntermediateProcessorStrategy.LAYER_UNZIPPER);
302+
case ALTERNATING:
303+
configuration.addBefore(LayeredPhases.P4_NODE_PLACEMENT, IntermediateProcessorStrategy.ALTERNATING_LAYER_UNZIPPER);
304304
break;
305305
default:
306306
break;

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -960,25 +960,26 @@ group layerUnzipping {
960960
advanced option layerSplit: Integer {
961961
label "Unzipping Layer Split"
962962
description
963-
"Defines the number of sublayers to split a layer into when using the N_LAYERS strategy.
964-
The property can be set to the first node in a layer, which then applies the property
965-
for the layer the node belongs to."
963+
"Defines the number of sublayers to split a layer into. The property can be set to the nodes in a layer,
964+
which then applies the property for the layer. If multiple nodes set the value to different values,
965+
then the lowest value is chosen."
966966
default = 2
967967
targets nodes
968968
lowerBound = 1
969-
requires layerUnzipping.strategy == LayerUnzippingStrategy.N_LAYERS
970969
}
971970

972971
option resetOnLongEdges: Boolean {
973972
label "Reset Alternation on Long Edges"
974973
description
975-
"If set to true, nodes will always be placed in the first sublayer after a long edge.
974+
"If set to true, nodes will always be placed in the first sublayer after a long edge when using the
975+
ALTERNATING strategy.
976976
Otherwise long edge dummies are treated the same as regular nodes. The default value is true.
977-
The property can be set to the first node in a layer, which then applies the property
978-
for the layer the node belongs to."
977+
The property can be set to the nodes in a layer, which then applies the property
978+
for the layer. If any node sets the value to false, then the value is set to false for the entire
979+
layer."
979980
default = true
980981
targets nodes
981-
requires layerUnzipping.strategy == LayerUnzippingStrategy.N_LAYERS
982+
requires layerUnzipping.strategy == LayerUnzippingStrategy.ALTERNATING
982983
}
983984
}
984985

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/IntermediateProcessorStrategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import org.eclipse.elk.alg.layered.graph.LGraph;
1313
import org.eclipse.elk.alg.layered.intermediate.compaction.HorizontalGraphCompactor;
14-
import org.eclipse.elk.alg.layered.intermediate.unzipping.GeneralLayerUnzipper;
14+
import org.eclipse.elk.alg.layered.intermediate.unzipping.AlternatingLayerUnzipper;
1515
import org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointInserter;
1616
import org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointProcessor;
1717
import org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointRemover;
@@ -97,7 +97,7 @@ public enum IntermediateProcessorStrategy implements ILayoutProcessorFactory<LGr
9797
/** Position self loops after phase 3. */
9898
SELF_LOOP_PORT_RESTORER,
9999
/** Unzips layers for compaction. */
100-
LAYER_UNZIPPER,
100+
ALTERNATING_LAYER_UNZIPPER,
101101
/** Wraps graphs such that they better fit a given drawing area, allowing only a single edge per cut. */
102102
SINGLE_EDGE_GRAPH_WRAPPER,
103103
/** Makes sure that in-layer constraints are handled. */
@@ -331,8 +331,8 @@ public ILayoutProcessor<LGraph> create() {
331331
case SELF_LOOP_PORT_RESTORER:
332332
return new SelfLoopPortRestorer();
333333

334-
case LAYER_UNZIPPER:
335-
return new GeneralLayerUnzipper();
334+
case ALTERNATING_LAYER_UNZIPPER:
335+
return new AlternatingLayerUnzipper();
336336

337337
case SELF_LOOP_POSTPROCESSOR:
338338
return new SelfLoopPostProcessor();
Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
* </dl>
4848
*
4949
*/
50-
public class GeneralLayerUnzipper implements ILayoutProcessor<LGraph> {
50+
public class AlternatingLayerUnzipper implements ILayoutProcessor<LGraph> {
5151

5252
@Override
5353
public void process (LGraph graph, IElkProgressMonitor progressMonitor) {
@@ -58,8 +58,8 @@ public void process (LGraph graph, IElkProgressMonitor progressMonitor) {
5858
List<Pair<Layer, Integer>> newLayers = new ArrayList<>();
5959
for (int i = 0; i < graph.getLayers().size(); i++) {
6060

61-
int N = graph.getLayers().get(i).getProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT);
62-
boolean resetOnLongEdges = graph.getLayers().get(i).getProperty(LayeredOptions.LAYER_UNZIPPING_RESET_ON_LONG_EDGES);
61+
int N = getLayerSplitProperty(graph.getLayers().get(i));
62+
boolean resetOnLongEdges = getResetOnLongEdgesProperty(graph.getLayers().get(i));
6363

6464
// only split if there are more nodes than the resulting sub-layers
6565
// an alternative would be to reduce N for this layer, this may or may
@@ -110,6 +110,45 @@ public void process (LGraph graph, IElkProgressMonitor progressMonitor) {
110110

111111

112112
}
113+
114+
/**
115+
* Checks all nodes of a layer for the layerSplit property and returns the lowest set value.
116+
*
117+
* @param layer The layer to determine the layerSplit property for
118+
* @return the layerSplit value
119+
*/
120+
private int getLayerSplitProperty(Layer layer) {
121+
int layerSplit = Integer.MAX_VALUE;
122+
boolean propertyUnset = true;
123+
for (int i = 0; i < layer.getNodes().size(); i++) {
124+
if (layer.getNodes().get(i).hasProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT)) {
125+
propertyUnset = false;
126+
int nodeValue = layer.getNodes().get(i).getProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT);
127+
layerSplit = layerSplit < nodeValue ? layerSplit : nodeValue;
128+
}
129+
}
130+
if (propertyUnset) {
131+
layerSplit = LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT.getDefault();
132+
}
133+
return layerSplit;
134+
}
135+
136+
/**
137+
* Checks all nodes of a layer for the resetOnLongEdges property and if any sets the value to false, returns false.
138+
*
139+
* @param layer The layer to determine the resetOnLongEdges property for.
140+
* @return the resetOnLongEdges value
141+
*/
142+
private boolean getResetOnLongEdgesProperty(Layer layer) {
143+
for (int i = 0; i < layer.getNodes().size(); i++) {
144+
if (layer.getNodes().get(i).hasProperty(LayeredOptions.LAYER_UNZIPPING_RESET_ON_LONG_EDGES)) {
145+
if (!layer.getNodes().get(i).getProperty(LayeredOptions.LAYER_UNZIPPING_RESET_ON_LONG_EDGES)) {
146+
return false;
147+
}
148+
}
149+
}
150+
return true;
151+
}
113152

114153
/**
115154
* checks the layer split property of the first node in a layer and copies the property to the layer

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/options/LayerUnzippingStrategy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public enum LayerUnzippingStrategy {
1818

1919
NONE,
20-
/** Splits all layers with more than two nodes into two layers. */
21-
N_LAYERS;
20+
/** Splits all layers with more than two nodes into several layers in an alternating pattern. */
21+
ALTERNATING;
2222

2323
}
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
import org.junit.Test;
2323

2424
/**
25-
* Tests the general n-way layer unzipper.
25+
* Tests the general n-way alternating layer unzipper.
2626
*
2727
*/
28-
public class GeneralLayerUnzipperTest {
28+
public class AlternatingLayerUnzipperTest {
2929

3030
LayeredLayoutProvider layeredLayout;
3131

@@ -71,7 +71,7 @@ public void simpleTwoSplit() {
7171
ElkGraphUtil.createSimpleEdge(node4, nodeFinal);
7272

7373

74-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
74+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
7575
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
7676
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
7777

@@ -125,7 +125,7 @@ public void simpleThreeSplit() {
125125
ElkGraphUtil.createSimpleEdge(node4, nodeFinal);
126126
ElkGraphUtil.createSimpleEdge(node5, nodeFinal);
127127

128-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
128+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
129129
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
130130
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 3);
131131

@@ -173,7 +173,7 @@ public void danglingOutgoing() {
173173
ElkGraphUtil.createSimpleEdge(node1, node4);
174174
ElkGraphUtil.createSimpleEdge(node1, node5);
175175

176-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
176+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
177177
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
178178
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
179179

@@ -225,7 +225,7 @@ public void danglingIncoming() {
225225
ElkGraphUtil.createSimpleEdge(node4, nodeFinal);
226226
ElkGraphUtil.createSimpleEdge(node5, nodeFinal);
227227

228-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
228+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
229229
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
230230
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
231231

@@ -290,7 +290,7 @@ public void multipleLayersSplit() {
290290
ElkGraphUtil.createSimpleEdge(node4, node22);
291291
ElkGraphUtil.createSimpleEdge(node4, node23);
292292

293-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
293+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
294294
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
295295
node1.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
296296
node21.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
@@ -354,7 +354,7 @@ public void multipleIncomingEdges() {
354354
ElkGraphUtil.createSimpleEdge(node4, nodeFinal);
355355

356356

357-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
357+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
358358
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
359359
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
360360

@@ -409,7 +409,7 @@ public void multipleOutgoingEdges() {
409409
ElkGraphUtil.createSimpleEdge(node4, nodeFinal);
410410

411411

412-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
412+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
413413
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
414414
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
415415

@@ -466,7 +466,7 @@ public void mixedDanglingIncoming() {
466466
ElkGraphUtil.createSimpleEdge(node3, node6);
467467
ElkGraphUtil.createSimpleEdge(node4, node7);
468468

469-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
469+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
470470
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
471471
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
472472
node5.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
@@ -530,7 +530,7 @@ public void mixedDanglingOutgoing() {
530530
ElkGraphUtil.createSimpleEdge(node3, node6);
531531
ElkGraphUtil.createSimpleEdge(node4, node7);
532532

533-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
533+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
534534
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
535535
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
536536
node5.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
@@ -598,7 +598,7 @@ public void mixedTwoThreeLayerSplit() {
598598
ElkGraphUtil.createSimpleEdge(node4, node22);
599599
ElkGraphUtil.createSimpleEdge(node4, node23);
600600

601-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
601+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
602602
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
603603
node1.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 3);
604604
node21.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
@@ -652,7 +652,7 @@ public void resetOnLongEdges() {
652652

653653
ElkGraphUtil.createSimpleEdge(node4, node5);
654654

655-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
655+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
656656
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
657657
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
658658
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_RESET_ON_LONG_EDGES, true);
@@ -707,7 +707,7 @@ public void noResetOnLongEdges() {
707707
ElkGraphUtil.createSimpleEdge(node4, node5);
708708
ElkGraphUtil.createSimpleEdge(node1, node6);
709709

710-
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.N_LAYERS);
710+
graph.setProperty(LayeredOptions.LAYER_UNZIPPING_STRATEGY, LayerUnzippingStrategy.ALTERNATING);
711711
graph.setProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.PREFER_EDGES);
712712
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_LAYER_SPLIT, 2);
713713
node2.setProperty(LayeredOptions.LAYER_UNZIPPING_RESET_ON_LONG_EDGES, false);

0 commit comments

Comments
 (0)