/
RectPackingLayoutProvider.java
171 lines (158 loc) · 8.22 KB
/
RectPackingLayoutProvider.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*******************************************************************************
* Copyright (c) 2018, 2020 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.rectpacking;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.elk.alg.common.NodeMicroLayout;
import org.eclipse.elk.alg.rectpacking.firstiteration.AreaApproximation;
import org.eclipse.elk.alg.rectpacking.options.OptimizationGoal;
import org.eclipse.elk.alg.rectpacking.options.RectPackingOptions;
import org.eclipse.elk.alg.rectpacking.seconditeration.RowFillingAndCompaction;
import org.eclipse.elk.alg.rectpacking.util.DrawingData;
import org.eclipse.elk.alg.rectpacking.util.DrawingDataDescriptor;
import org.eclipse.elk.alg.rectpacking.util.DrawingUtil;
import org.eclipse.elk.core.AbstractLayoutProvider;
import org.eclipse.elk.core.math.ElkPadding;
import org.eclipse.elk.core.math.KVector;
import org.eclipse.elk.core.util.ElkUtil;
import org.eclipse.elk.core.util.IElkProgressMonitor;
import org.eclipse.elk.graph.ElkNode;
/**
* A layout algorithm that does not take edges into account, but treats all nodes as isolated boxes. This is useful for
* parts of a diagram that consist of objects without connections, such as parallel regions in Statecharts.
* <p>
* Nodes are viewed as rectangles and so {@link ElkNode}s are referred to as rectangles in the comments.
* </p>
* <p>
* Depending on the settings, checks for a specified special case, calculates a layout with a approximation algorithm or
* uses that approximation algorithm for the needed area of the rectangles and places the rectangles nicely aligned on
* the drawing area according to that approximation.
* </p>
*/
public class RectPackingLayoutProvider extends AbstractLayoutProvider {
/**
* Calculating and applying layout to the model.
*/
@Override
public void layout(final ElkNode layoutGraph, final IElkProgressMonitor progressMonitor) {
progressMonitor.begin("Rectangle Packing", 1);
if (progressMonitor.isLoggingEnabled()) {
progressMonitor.logGraph(layoutGraph, "Input");
}
// The desired aspect ratio.
double aspectRatio = layoutGraph.getProperty(RectPackingOptions.ASPECT_RATIO);
// The strategy for the initial width approximation.
OptimizationGoal goal = layoutGraph.getProperty(RectPackingOptions.OPTIMIZATION_GOAL);
// Option for better width approximation.
boolean lastPlaceShift = layoutGraph.getProperty(RectPackingOptions.LAST_PLACE_SHIFT);
// Option to only do the initial width approximation.
boolean onlyFirstIteration = layoutGraph.getProperty(RectPackingOptions.ONLY_FIRST_ITERATION);
// Option whether the nodes should be expanded to fill the bounding rectangle.
boolean expandNodes = layoutGraph.getProperty(RectPackingOptions.EXPAND_NODES);
// The padding surrounding the drawing.
ElkPadding padding = layoutGraph.getProperty(RectPackingOptions.PADDING);
// The spacing between two nodes.
double nodeNodeSpacing = layoutGraph.getProperty(RectPackingOptions.SPACING_NODE_NODE);
// Whether the nodes are compacted after the initial placement.
boolean compaction = layoutGraph.getProperty(RectPackingOptions.ROW_COMPACTION);
// Whether the nodes should be expanded to fit the aspect ratio during node expansion.
// Only effective if nodes are expanded.
boolean expandToAspectRatio = layoutGraph.getProperty(RectPackingOptions.EXPAND_TO_ASPECT_RATIO);
// Whether interactive layout is activ.
boolean interactive = layoutGraph.getProperty(RectPackingOptions.INTERACTIVE);
// A target width for the algorithm. If this is set the width approximation step is skipped.
double targetWidth = layoutGraph.getProperty(RectPackingOptions.TARGET_WIDTH);
List<ElkNode> rectangles = layoutGraph.getChildren();
DrawingUtil.resetCoordinates(rectangles);
DrawingData drawing;
if (interactive) {
List<ElkNode> fixedNodes = new ArrayList<>();
for (ElkNode elkNode : rectangles) {
if (elkNode.hasProperty(RectPackingOptions.DESIRED_POSITION)) {
fixedNodes.add(elkNode);
}
}
for (ElkNode elkNode : fixedNodes) {
rectangles.remove(elkNode);
}
Collections.sort(fixedNodes, (a, b) -> {
int positionA = a.getProperty(RectPackingOptions.DESIRED_POSITION);
int positionB = b.getProperty(RectPackingOptions.DESIRED_POSITION);
if (positionA == positionB) {
return -1;
} else {
return Integer.compare(positionA, positionB);
}
});
for (ElkNode elkNode : fixedNodes) {
int position = elkNode.getProperty(RectPackingOptions.DESIRED_POSITION);
position = Math.min(position, rectangles.size());
rectangles.add(position, elkNode);
}
int index = 0;
for (ElkNode elkNode: rectangles) {
elkNode.setProperty(RectPackingOptions.CURRENT_POSITION, index);
index++;
}
}
// Get minimum size of parent.
KVector minSize = ElkUtil.effectiveMinSizeConstraintFor(layoutGraph);
// Remove padding to get the space the algorithm can use.
minSize.x -= padding.getHorizontal();
minSize.y -= padding.getVertical();
double maxWidth = minSize.x;
if (targetWidth < 0 || targetWidth < minSize.x) {
// Initial width approximation.
AreaApproximation firstIt = new AreaApproximation(aspectRatio, goal, lastPlaceShift);
drawing = firstIt.approxBoundingBox(rectangles, nodeNodeSpacing, padding);
if (progressMonitor.isLoggingEnabled()) {
progressMonitor.logGraph(layoutGraph, "After approximation");
}
} else {
drawing = new DrawingData(aspectRatio, targetWidth, 0, DrawingDataDescriptor.WHOLE_DRAWING);
}
// Readd padding for next steps.
minSize.x += padding.getHorizontal();
minSize.y += padding.getVertical();
// Placement according to approximated width.
if (!onlyFirstIteration) {
DrawingUtil.resetCoordinates(rectangles);
RowFillingAndCompaction secondIt = new RowFillingAndCompaction(aspectRatio, expandNodes, expandToAspectRatio, compaction, nodeNodeSpacing);
// Modify the initial approximation if necessary.
maxWidth = Math.max(minSize.x, drawing.getDrawingWidth());
// Run placement, compaction, and expansion (if enabled).
drawing = secondIt.start(rectangles, maxWidth, minSize, progressMonitor, layoutGraph);
}
// Final touch.
applyPadding(rectangles, padding);
ElkUtil.resizeNode(layoutGraph, drawing.getDrawingWidth() + padding.getHorizontal(),
drawing.getDrawingHeight() + padding.getVertical(), false, true);
// if requested, compute nodes's dimensions, place node labels, ports, port labels, etc.
if (!layoutGraph.getProperty(RectPackingOptions.OMIT_NODE_MICRO_LAYOUT)) {
NodeMicroLayout.forGraph(layoutGraph).execute();
}
if (progressMonitor.isLoggingEnabled()) {
progressMonitor.logGraph(layoutGraph, "Output");
}
progressMonitor.done();
}
/**
* Shifts all rectangles to the right and bottom according to the specified padding.
*
* @param rectangles
* list of rectangles that have been placed.
*/
private static void applyPadding(final List<ElkNode> rectangles, ElkPadding padding) {
for (ElkNode rect : rectangles) {
rect.setLocation(rect.getX() + padding.getLeft(), rect.getY() + padding.getTop());
}
}
}