use of org.eclipse.elk.alg.layered.graph.LGraph in project elk by eclipse.
the class ComponentsProcessor method split.
/**
* Split the given graph into its connected components.
*
* @param graph an input graph with layerless nodes
* @return a list of components that can be processed one by one
*/
public List<LGraph> split(final LGraph graph) {
List<LGraph> result;
// Default to the simple graph placer
graphPlacer = simpleRowGraphPlacer;
// Whether separate components processing is requested
Boolean separateProperty = graph.getProperty(LayeredOptions.SEPARATE_CONNECTED_COMPONENTS);
boolean separate = separateProperty == null || separateProperty.booleanValue();
// Whether the graph contains external ports
boolean extPorts = graph.getProperty(InternalProperties.GRAPH_PROPERTIES).contains(GraphProperties.EXTERNAL_PORTS);
// The graph's external port constraints
PortConstraints extPortConstraints = graph.getProperty(LayeredOptions.PORT_CONSTRAINTS);
boolean compatiblePortConstraints = !extPortConstraints.isOrderFixed();
// FREE or FIXED_SIDES.
if (separate && (compatiblePortConstraints || !extPorts)) {
// Set id of all nodes to 0
for (LNode node : graph.getLayerlessNodes()) {
node.id = 0;
}
// Perform DFS starting on each node, collecting connected components
result = Lists.newArrayList();
for (LNode node : graph.getLayerlessNodes()) {
Pair<List<LNode>, Set<PortSide>> componentData = dfs(node, null);
if (componentData != null) {
LGraph newGraph = new LGraph();
newGraph.copyProperties(graph);
newGraph.setProperty(InternalProperties.EXT_PORT_CONNECTIONS, componentData.getSecond());
newGraph.getPadding().copy(graph.getPadding());
// If a minimum size was set on the original graph, setting it on the seperated graphs as well
// might enlarge them although their combined area might not actually have fallen below the minimum
// size; thus, remove the minimum size
newGraph.setProperty(LayeredOptions.NODE_SIZE_MINIMUM, null);
for (LNode n : componentData.getFirst()) {
newGraph.getLayerlessNodes().add(n);
n.setGraph(newGraph);
}
result.add(newGraph);
}
}
if (extPorts) {
// With external port connections, we want to use the more complex components
// placement algorithm
graphPlacer = componentGroupGraphPlacer;
}
} else {
result = Arrays.asList(graph);
}
// The component with the node with the smallest order should be first.
if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) {
Collections.sort(result, (g1, g2) -> {
int g1Order = Integer.MAX_VALUE;
for (LNode node : g1.getLayerlessNodes()) {
if (node.hasProperty(InternalProperties.MODEL_ORDER)) {
g1Order = Math.min(g1Order, node.getProperty(InternalProperties.MODEL_ORDER));
}
}
int g2Order = Integer.MAX_VALUE;
for (LNode node : g2.getLayerlessNodes()) {
if (node.hasProperty(InternalProperties.MODEL_ORDER)) {
g2Order = Math.min(g2Order, node.getProperty(InternalProperties.MODEL_ORDER));
}
}
return Integer.compare(g1Order, g2Order);
});
}
return result;
}
use of org.eclipse.elk.alg.layered.graph.LGraph in project elk by eclipse.
the class SimpleRowGraphPlacer method combine.
@Override
public void combine(final List<LGraph> components, final LGraph target) {
if (components.size() == 1) {
LGraph source = components.get(0);
if (source != target) {
target.getLayerlessNodes().clear();
moveGraph(target, source, 0, 0);
target.copyProperties(source);
target.getPadding().copy(source.getPadding());
target.getSize().x = source.getSize().x;
target.getSize().y = source.getSize().y;
}
return;
} else if (components.isEmpty()) {
target.getLayerlessNodes().clear();
target.getSize().x = 0;
target.getSize().y = 0;
return;
}
assert !components.contains(target);
// assign priorities
for (LGraph graph : components) {
int priority = 0;
for (LNode node : graph.getLayerlessNodes()) {
priority += node.getProperty(LayeredOptions.PRIORITY);
}
graph.id = priority;
}
// sort the components by their priority and size.
// If preserve order is set, we do not consider the size.
Collections.sort(components, new Comparator<LGraph>() {
public int compare(final LGraph graph1, final LGraph graph2) {
int prio = graph2.id - graph1.id;
if (prio == 0) {
if (graph1.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) == OrderingStrategy.NONE) {
double size1 = graph1.getSize().x * graph1.getSize().y;
double size2 = graph2.getSize().x * graph2.getSize().y;
return Double.compare(size1, size2);
}
}
return prio;
}
});
LGraph firstComponent = components.get(0);
target.getLayerlessNodes().clear();
target.copyProperties(firstComponent);
// determine the maximal row width by the maximal box width and the total area
double maxRowWidth = 0.0f;
double totalArea = 0.0f;
for (LGraph graph : components) {
KVector size = graph.getSize();
maxRowWidth = Math.max(maxRowWidth, size.x);
totalArea += size.x * size.y;
}
maxRowWidth = Math.max(maxRowWidth, (float) Math.sqrt(totalArea) * target.getProperty(LayeredOptions.ASPECT_RATIO));
double componentSpacing = target.getProperty(LayeredOptions.SPACING_COMPONENT_COMPONENT);
// place nodes iteratively into rows
double xpos = 0, ypos = 0, highestBox = 0, broadestRow = componentSpacing;
for (LGraph graph : components) {
KVector size = graph.getSize();
if (xpos + size.x > maxRowWidth) {
// place the graph into the next row
xpos = 0;
ypos += highestBox + componentSpacing;
highestBox = 0;
}
KVector offset = graph.getOffset();
offsetGraph(graph, xpos + offset.x, ypos + offset.y);
offset.reset();
broadestRow = Math.max(broadestRow, xpos + size.x);
highestBox = Math.max(highestBox, size.y);
xpos += size.x + componentSpacing;
}
target.getSize().x = broadestRow;
target.getSize().y = ypos + highestBox;
// if compaction is desired, do so!
if (firstComponent.getProperty(LayeredOptions.COMPACTION_CONNECTED_COMPONENTS)) {
ComponentsCompactor compactor = new ComponentsCompactor();
compactor.compact(components, target.getSize(), componentSpacing);
// therefore we have to use the final drawing's offset
for (LGraph h : components) {
h.getOffset().reset().add(compactor.getOffset());
}
// set the new graph size
target.getSize().reset().add(compactor.getGraphSize());
}
// finally move the components to the combined graph
moveGraphs(target, components, 0, 0);
}
use of org.eclipse.elk.alg.layered.graph.LGraph in project elk by eclipse.
the class CompoundGraphPostprocessor method process.
@Override
public void process(final LGraph graph, final IElkProgressMonitor monitor) {
monitor.begin("Compound graph postprocessor", 1);
// whether bend points should be added whenever crossing a hierarchy boundary
boolean addUnnecessaryBendpoints = graph.getProperty(LayeredOptions.UNNECESSARY_BENDPOINTS);
// restore the cross-hierarchy map that was built by the preprocessor
Multimap<LEdge, CrossHierarchyEdge> crossHierarchyMap = graph.getProperty(InternalProperties.CROSS_HIERARCHY_MAP);
// remember all dummy edges we encounter; these need to be removed at the end
Set<LEdge> dummyEdges = Sets.newHashSet();
// iterate over all original edges
for (LEdge origEdge : crossHierarchyMap.keySet()) {
// find all cross-hierarchy edges the original edge was split into, and sort them from source to target
List<CrossHierarchyEdge> crossHierarchyEdges = new ArrayList<CrossHierarchyEdge>(crossHierarchyMap.get(origEdge));
Collections.sort(crossHierarchyEdges, new CrossHierarchyEdgeComparator(graph));
// find the original source and target ports for the original edge
LPort sourcePort = crossHierarchyEdges.get(0).getActualSource();
LPort targetPort = crossHierarchyEdges.get(crossHierarchyEdges.size() - 1).getActualTarget();
// determine the reference graph for all bend points
LNode referenceNode = sourcePort.getNode();
LGraph referenceGraph;
if (LGraphUtil.isDescendant(targetPort.getNode(), referenceNode)) {
referenceGraph = referenceNode.getNestedGraph();
} else {
referenceGraph = referenceNode.getGraph();
}
// check whether there are any junction points
KVectorChain junctionPoints = clearJunctionPoints(origEdge, crossHierarchyEdges);
// reset bend points (we have computed new ones anyway)
origEdge.getBendPoints().clear();
// apply the computed layouts to the cross-hierarchy edge
KVector lastPoint = null;
for (CrossHierarchyEdge chEdge : crossHierarchyEdges) {
// transform all coordinates from the graph of the dummy edge to the reference graph
KVector offset = new KVector();
LGraphUtil.changeCoordSystem(offset, chEdge.getGraph(), referenceGraph);
LEdge ledge = chEdge.getEdge();
KVectorChain bendPoints = new KVectorChain();
bendPoints.addAllAsCopies(0, ledge.getBendPoints());
bendPoints.offset(offset);
// Note: if an NPE occurs here, that means ELK Layered has replaced the original edge
KVector sourcePoint = new KVector(ledge.getSource().getAbsoluteAnchor());
KVector targetPoint = new KVector(ledge.getTarget().getAbsoluteAnchor());
sourcePoint.add(offset);
targetPoint.add(offset);
if (lastPoint != null) {
KVector nextPoint;
if (bendPoints.isEmpty()) {
nextPoint = targetPoint;
} else {
nextPoint = bendPoints.getFirst();
}
// we add the source point as a bend point to properly connect the hierarchy levels
// either if the last point of the previous hierarchy edge segment is a certain
// level of tolerance away or if we are required to add unnecessary bend points
boolean xDiffEnough = Math.abs(lastPoint.x - nextPoint.x) > OrthogonalRoutingGenerator.TOLERANCE;
boolean yDiffEnough = Math.abs(lastPoint.y - nextPoint.y) > OrthogonalRoutingGenerator.TOLERANCE;
if ((!addUnnecessaryBendpoints && xDiffEnough && yDiffEnough) || (addUnnecessaryBendpoints && (xDiffEnough || yDiffEnough))) {
origEdge.getBendPoints().add(sourcePoint);
}
}
origEdge.getBendPoints().addAll(bendPoints);
if (bendPoints.isEmpty()) {
lastPoint = sourcePoint;
} else {
lastPoint = bendPoints.getLast();
}
// copy junction points
copyJunctionPoints(ledge, junctionPoints, offset);
// add offset to target port with a special property
if (chEdge.getActualTarget() == targetPort) {
if (targetPort.getNode().getGraph() != chEdge.getGraph()) {
// the target port is in a different coordinate system -- recompute the offset
offset = new KVector();
LGraphUtil.changeCoordSystem(offset, targetPort.getNode().getGraph(), referenceGraph);
}
origEdge.setProperty(InternalProperties.TARGET_OFFSET, offset);
}
// copy labels back to the original edge
copyLabelsBack(ledge, origEdge, referenceGraph);
// remember the dummy edge for later removal (dummy edges may be in use by several
// different original edges, which is why we cannot just go and remove it now)
dummyEdges.add(ledge);
}
// restore the original source port and target port
origEdge.setSource(sourcePort);
origEdge.setTarget(targetPort);
}
// remove the dummy edges from the graph (dummy ports and dummy nodes are retained)
for (LEdge dummyEdge : dummyEdges) {
dummyEdge.setSource(null);
dummyEdge.setTarget(null);
}
monitor.done();
}
use of org.eclipse.elk.alg.layered.graph.LGraph in project elk by eclipse.
the class ElkGraphLayoutTransferrer method applyLayout.
/**
* Applies the layout information contained in the given LGraph to the ElkGraph elements it was
* created from. All source ElkGraph elements are expected to be accessible through their LGraph
* counterparts through the {@link InternalProperties#ORIGIN} property.
*
* @param lgraph the LGraph whose layout information to apply.
*/
public void applyLayout(final LGraph lgraph) {
Object graphOrigin = lgraph.getProperty(InternalProperties.ORIGIN);
if (!(graphOrigin instanceof ElkNode)) {
return;
}
// The ElkNode that represents this graph in the original ElkGraph
ElkNode parentElkNode = (ElkNode) graphOrigin;
// The LNode that represents this graph in the upper hierarchy level, if any
LNode parentLNode = (LNode) lgraph.getParentNode();
// Get the offset to be added to all coordinates
KVector offset = new KVector(lgraph.getOffset());
// Adjust offset (and with it the positions) by the requested padding
LPadding lPadding = lgraph.getPadding();
offset.x += lPadding.left;
offset.y += lPadding.top;
// Set node padding, if it was computed during layout
final EnumSet<SizeOptions> sizeOptions = parentElkNode.getProperty(LayeredOptions.NODE_SIZE_OPTIONS);
if (sizeOptions.contains(SizeOptions.COMPUTE_PADDING)) {
ElkPadding padding = parentElkNode.getProperty(LayeredOptions.PADDING);
padding.setBottom(lPadding.bottom);
padding.setTop(lPadding.top);
padding.setLeft(lPadding.left);
padding.setRight(lPadding.right);
}
// Along the way, we collect the list of edges to be processed later
List<LEdge> edgeList = Lists.newArrayList();
// Process the nodes
for (LNode lnode : lgraph.getLayerlessNodes()) {
if (representsNode(lnode)) {
applyNodeLayout(lnode, offset);
} else if (representsExternalPort(lnode) && parentLNode == null) {
// We have an external port here on the top-most hierarchy level of the current (possibly
// hierarchical) layout run; set its position
ElkPort elkport = (ElkPort) lnode.getProperty(InternalProperties.ORIGIN);
KVector portPosition = LGraphUtil.getExternalPortPosition(lgraph, lnode, elkport.getWidth(), elkport.getHeight());
elkport.setLocation(portPosition.x, portPosition.y);
}
// correctly)
for (LPort port : lnode.getPorts()) {
port.getOutgoingEdges().stream().filter(edge -> !LGraphUtil.isDescendant(edge.getTarget().getNode(), lnode)).forEach(edge -> edgeList.add(edge));
}
}
// Collect edges that go from the current graph's representing LNode down into its descendants
if (parentLNode != null) {
for (LPort port : parentLNode.getPorts()) {
port.getOutgoingEdges().stream().filter(edge -> LGraphUtil.isDescendant(edge.getTarget().getNode(), parentLNode)).forEach(edge -> edgeList.add(edge));
}
}
// Iterate through all edges
EdgeRouting routing = parentElkNode.getProperty(LayeredOptions.EDGE_ROUTING);
for (LEdge ledge : edgeList) {
applyEdgeLayout(ledge, routing, offset, lPadding);
}
// Setup the parent node
applyParentNodeLayout(lgraph);
// Process nested subgraphs
for (LNode lnode : lgraph.getLayerlessNodes()) {
LGraph nestedGraph = lnode.getNestedGraph();
if (nestedGraph != null) {
applyLayout(nestedGraph);
}
}
}
use of org.eclipse.elk.alg.layered.graph.LGraph in project elk by eclipse.
the class ElkGraphLayoutTransferrer method calculateHierarchicalOffset.
/**
* If the coordinates of an edge must be relative to a different node than they are in the algorithm, this
* method returns the correct offset to translate from the algorithm's coordinate system to the necessary
* target coordinate system.
*
* @return the offset vector, which may simply be zero (but not {@code null}). Must not be modified.
*/
private KVector calculateHierarchicalOffset(final LEdge ledge) {
LGraph targetCoordinateSystem = ledge.getProperty(InternalProperties.COORDINATE_SYSTEM_ORIGIN);
if (targetCoordinateSystem != null) {
KVector result = new KVector();
// Edges this method is called on are always in the coordinate system of their source
LGraph currentGraph = ledge.getSource().getNode().getGraph();
while (currentGraph != targetCoordinateSystem) {
// The current graph should always have an upper level if we have not reached the target graph yet;
LNode representingNode = currentGraph.getParentNode();
currentGraph = representingNode.getGraph();
result.add(representingNode.getPosition()).add(currentGraph.getOffset()).add(currentGraph.getPadding().left, currentGraph.getPadding().top);
}
return result;
}
// No coordinate system conversion is required
return ZERO_OFFSET;
}
Aggregations