use of org.eclipse.elk.graph.ElkEdge in project elk by eclipse.
the class GraphRenderingCanvas method calculateRequiredCanvasSizeAndBaseOffset.
/**
* Sets size of the canvas by looking up the biggest distances between graph elements in x- and y-direction
* and return a KVector with the required offset of the origin coordinates to fit all elements on the canvas.
*
* @param graph to be painted
*/
private KVector calculateRequiredCanvasSizeAndBaseOffset(final ElkNode graph) {
if (graph != null) {
double minX = Double.MAX_VALUE;
double maxX = Double.MIN_VALUE;
double minY = Double.MAX_VALUE;
double maxY = Double.MIN_VALUE;
// check all nodes for their coordinates
for (ElkNode node : graph.getChildren()) {
minX = Math.min(minX, node.getX());
maxX = Math.max(maxX, node.getX() + node.getWidth());
minY = Math.min(minY, node.getY());
maxY = Math.max(maxY, node.getY() + node.getHeight());
// check node labels
for (ElkLabel nodeLabel : node.getLabels()) {
minX = Math.min(minX, node.getX() + nodeLabel.getX());
maxX = Math.max(maxX, node.getX() + nodeLabel.getX() + nodeLabel.getWidth());
minY = Math.min(minY, node.getY() + nodeLabel.getY());
maxY = Math.max(maxY, node.getY() + nodeLabel.getY() + nodeLabel.getHeight());
}
}
for (ElkEdge edge : graph.getContainedEdges()) {
// check all sections of the edges for their coordinates
for (ElkEdgeSection edgeSection : edge.getSections()) {
minX = Math.min(minX, edgeSection.getStartX());
minY = Math.min(minY, edgeSection.getStartY());
maxX = Math.max(maxX, edgeSection.getStartX());
maxY = Math.max(maxY, edgeSection.getStartY());
minX = Math.min(minX, edgeSection.getEndX());
minY = Math.min(minY, edgeSection.getEndY());
maxX = Math.max(maxX, edgeSection.getEndX());
maxY = Math.max(maxY, edgeSection.getEndY());
for (ElkBendPoint bendPoint : edgeSection.getBendPoints()) {
minX = Math.min(minX, bendPoint.getX());
minY = Math.min(minY, bendPoint.getY());
maxX = Math.max(maxX, bendPoint.getX());
maxY = Math.max(maxY, bendPoint.getY());
}
}
// check edge labels
for (ElkLabel edgeLabel : edge.getLabels()) {
minX = Math.min(minX, edgeLabel.getX());
maxX = Math.max(maxX, edgeLabel.getX() + edgeLabel.getWidth());
minY = Math.min(minY, edgeLabel.getY());
maxY = Math.max(maxY, edgeLabel.getY() + edgeLabel.getHeight());
}
}
int x = ((int) (Math.max(graph.getWidth(), maxX) - Math.min(0, minX))) + 1;
int y = ((int) (Math.max(graph.getHeight(), maxY) - Math.min(0, minY))) + 1;
setSize(new Point(x, y));
return new KVector((-Math.min(0, minX)), (-Math.min(0, minY)));
}
return new KVector();
}
use of org.eclipse.elk.graph.ElkEdge in project elk by eclipse.
the class RecursiveGraphLayoutEngine method postProcessInsideSelfLoops.
/**
* Post-processes self loops routed inside by offsetting their coordinates by the coordinates of
* their parent node. The post processing is necessary since the self loop coordinates are
* relative to their parent node's upper left corner since, at that point, the parent node's
* final coordinates are not determined yet.
*
* @param insideSelfLoops
* list of inside self loops to post-process.
*/
protected void postProcessInsideSelfLoops(final List<ElkEdge> insideSelfLoops) {
for (final ElkEdge selfLoop : insideSelfLoops) {
// MIGRATE Adapt to hyperedges and make error-safe
final ElkConnectableShape node = ElkGraphUtil.connectableShapeToNode(selfLoop.getSources().get(0));
final double xOffset = node.getX();
final double yOffset = node.getY();
// Offset the edge coordinates by the node's position
// MIGRATE Adapt to hyperedges. Also, what about multiple edge sections?
ElkEdgeSection section = selfLoop.getSections().get(0);
section.setStartLocation(section.getStartX() + xOffset, section.getStartY() + yOffset);
section.setEndLocation(section.getEndX() + xOffset, section.getEndY() + yOffset);
for (final ElkBendPoint bend : section.getBendPoints()) {
bend.set(bend.getX() + xOffset, bend.getY() + yOffset);
}
// Offset junction points by the node position
selfLoop.getProperty(CoreOptions.JUNCTION_POINTS).offset(xOffset, yOffset);
}
}
use of org.eclipse.elk.graph.ElkEdge in project elk by eclipse.
the class RecursiveGraphLayoutEngine method layoutRecursively.
/**
* Recursive function to enable layout of hierarchy. The leafs are laid out first to use their
* layout information in the levels above.
*
* <p>This method returns self loops routed inside the given layout node. Those will have
* coordinates relative to the node's top left corner, which is incorrect. Once the node's
* final coordinates in its container are determined, any inside self loops will have to be offset
* by the node's position.</p>
*
* @param layoutNode the node with children to be laid out
* @param testController an optional test controller if this layout run is part of a unit test
* @param progressMonitor monitor used to keep track of progress
* @return list of self loops routed inside the node.
*/
protected List<ElkEdge> layoutRecursively(final ElkNode layoutNode, final TestController testController, final IElkProgressMonitor progressMonitor) {
if (progressMonitor.isCanceled()) {
return Collections.emptyList();
}
// Check if the node should be laid out at all
if (layoutNode.getProperty(CoreOptions.NO_LAYOUT)) {
return Collections.emptyList();
}
// We have to process the node if it has children...
final boolean hasChildren = !layoutNode.getChildren().isEmpty();
// ...or if inside self loop processing is enabled and it actually has inside self loops
final List<ElkEdge> insideSelfLoops = gatherInsideSelfLoops(layoutNode);
final boolean hasInsideSelfLoops = !insideSelfLoops.isEmpty();
if (hasChildren || hasInsideSelfLoops) {
// Fetch the layout algorithm that should be used to compute a layout for its content
final LayoutAlgorithmData algorithmData = layoutNode.getProperty(CoreOptions.RESOLVED_ALGORITHM);
if (algorithmData == null) {
throw new UnsupportedConfigurationException("Resolved algorithm is not set;" + " apply a LayoutAlgorithmResolver before computing layout.");
}
final boolean supportsInsideSelfLoops = algorithmData.supportsFeature(GraphFeature.INSIDE_SELF_LOOPS);
// Persist the Hierarchy Handling in the nodes by querying the parent node
evaluateHierarchyHandlingInheritance(layoutNode);
// algorithm doesn't actually support inside self loops, we cancel
if (!hasChildren && hasInsideSelfLoops && !supportsInsideSelfLoops) {
return Collections.emptyList();
}
// We collect inside self loops of children and post-process them later
List<ElkEdge> childrenInsideSelfLoops = Lists.newArrayList();
// If the layout provider supports hierarchy, it is expected to layout the node's compound
// node children as well
int nodeCount;
if (layoutNode.getProperty(CoreOptions.HIERARCHY_HANDLING) == HierarchyHandling.INCLUDE_CHILDREN && (algorithmData.supportsFeature(GraphFeature.COMPOUND) || algorithmData.supportsFeature(GraphFeature.CLUSTERS))) {
// The layout algorithm will compute a layout for multiple levels of hierarchy under the current one
nodeCount = countNodesWithHierarchy(layoutNode);
// Look for nodes that stop the hierarchy handling, evaluating the inheritance on the way
final Queue<ElkNode> nodeQueue = Lists.newLinkedList();
nodeQueue.addAll(layoutNode.getChildren());
while (!nodeQueue.isEmpty()) {
ElkNode node = nodeQueue.poll();
// Persist the Hierarchy Handling in every case. (Won't hurt with nodes that are
// evaluated in the next recursion)
evaluateHierarchyHandlingInheritance(node);
final boolean stopHierarchy = node.getProperty(CoreOptions.HIERARCHY_HANDLING) == HierarchyHandling.SEPARATE_CHILDREN;
// In that case, a separate recursive call is used for child nodes
if (stopHierarchy || (node.hasProperty(CoreOptions.ALGORITHM) && !algorithmData.equals(node.getProperty(CoreOptions.RESOLVED_ALGORITHM)))) {
List<ElkEdge> childLayoutSelfLoops = layoutRecursively(node, testController, progressMonitor);
childrenInsideSelfLoops.addAll(childLayoutSelfLoops);
// Explicitly disable hierarchical layout for the child node. Simplifies the
// handling of switching algorithms in the layouter.
node.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.SEPARATE_CHILDREN);
// Apply the LayoutOptions.SCALE_FACTOR if present
ElkUtil.applyConfiguredNodeScaling(node);
} else {
// Child should be included in current layout, possibly adding its own children
nodeQueue.addAll(node.getChildren());
}
}
} else {
// Layout each compound node contained in this node separately
nodeCount = layoutNode.getChildren().size();
for (ElkNode child : layoutNode.getChildren()) {
List<ElkEdge> childLayoutSelfLoops = layoutRecursively(child, testController, progressMonitor);
childrenInsideSelfLoops.addAll(childLayoutSelfLoops);
// Apply the LayoutOptions.SCALE_FACTOR if present
ElkUtil.applyConfiguredNodeScaling(child);
}
}
if (progressMonitor.isCanceled()) {
return Collections.emptyList();
}
// from being laid out again
for (final ElkEdge selfLoop : childrenInsideSelfLoops) {
selfLoop.setProperty(CoreOptions.NO_LAYOUT, true);
}
executeAlgorithm(layoutNode, algorithmData, testController, progressMonitor.subTask(nodeCount));
// Post-process the inner self loops we collected
postProcessInsideSelfLoops(childrenInsideSelfLoops);
// Return our own inside self loops to be processed later
if (hasInsideSelfLoops && supportsInsideSelfLoops) {
return insideSelfLoops;
} else {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
}
use of org.eclipse.elk.graph.ElkEdge in project elk by eclipse.
the class AllowNonFlowPortsToSwitchSidesTest method testOnlySwitchPortSidesIfPermitted.
@Test
public void testOnlySwitchPortSidesIfPermitted() {
final ElkNode graph = createSimpleGraph(portConstraints, node1MaySwitchPortSides, node2MaySwitchPortSides);
final ElkEdge e1 = graph.getContainedEdges().get(0);
final ElkEdge e2 = graph.getContainedEdges().get(1);
layeredLayout.layout(graph, new NullElkProgressMonitor());
assertEquals(edgesIntersect, intersect(e1, e2));
}
use of org.eclipse.elk.graph.ElkEdge in project elk by eclipse.
the class NextToPortLabelPlacementTest method shouldLabelBePlacedNextToPort.
private boolean shouldLabelBePlacedNextToPort(final ElkPort port, final boolean insideLabelPlacement) {
// Shortcut: if there is no incident edge, we can place the label next to the port
if (port.getIncomingEdges().isEmpty() && port.getOutgoingEdges().isEmpty()) {
return true;
}
// Iterate over all edges and check whether there are (i) edges that connect to the parent node's insides, and
// (ii) edges that connect to somewhere else
boolean edgesToInsides = false;
boolean edgesToSomewhereElse = false;
for (ElkEdge outEdge : port.getOutgoingEdges()) {
boolean insideEdge = ElkGraphUtil.isDescendant(ElkGraphUtil.connectableShapeToNode(outEdge.getTargets().get(0)), port.getParent());
edgesToInsides |= insideEdge;
edgesToSomewhereElse |= !insideEdge;
}
for (ElkEdge inEdge : port.getIncomingEdges()) {
boolean insideEdge = ElkGraphUtil.isDescendant(ElkGraphUtil.connectableShapeToNode(inEdge.getSources().get(0)), port.getParent());
edgesToInsides |= insideEdge;
edgesToSomewhereElse |= !insideEdge;
}
return (insideLabelPlacement && !edgesToInsides) || (!insideLabelPlacement && !edgesToSomewhereElse);
}
Aggregations