From 09bb1c69daade515084a88653b5503a559309aef Mon Sep 17 00:00:00 2001 From: Max Kasperowski Date: Wed, 19 Jul 2023 16:08:18 +0200 Subject: [PATCH 1/3] Adds options to control the rotation of radial layouts. Particularly with top-down layout. --- .../org/eclipse/elk/alg/radial/Radial.melk | 43 ++++++++++ .../elk/alg/radial/RadialLayoutProvider.java | 13 ++- .../intermediate/EdgeAngleCalculator.java | 47 +++++++++++ .../IntermediateProcessorStrategy.java | 14 +++- .../intermediate/rotation/AngleRotation.java | 84 +++++++++++++++++++ .../intermediate/rotation/GeneralRotator.java | 32 +++++++ .../intermediate/rotation/IRadialRotator.java | 28 +++++++ .../alg/radial/options/RotationStrategy.java | 40 +++++++++ .../org/eclipse/elk/core/math/KVector.java | 14 +++- 9 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java create mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java create mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java create mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/IRadialRotator.java create mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk index 28088112a7..3885b9f0e0 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk @@ -38,6 +38,10 @@ algorithm radial(RadialLayoutProvider) { supports org.eclipse.elk.portLabels.placement supports compactionStepSize supports compactor + supports rotator + supports targetAngle + supports computeAdditionalWedgeSpace + supports outgoingEdgeAngles supports optimizationCriteria supports orderId supports radius @@ -72,6 +76,45 @@ option radius: double { targets parents } +// Rotation +option rotator: RotationStrategy { + label "Rotation" + description + "The rotator option determines how a rotation of the layout should be performed." + targets parents + default = RotationStrategy.NONE +} + +option targetAngle: double { + label "Target Angle" + description + "The angle in radians that the layout should be rotated to after layout. Used by + {@link RotationStrategy.ROTATE_TO_ANGLE}." + targets parents + default = 0 + requires rotator == RotationStrategy.ROTATE_TO_ANGLE +} + +advanced option computeAdditionalWedgeSpace: boolean { + label "Additional Wedge Space" + description + "If set to true, modifies the target angle by rotating further such that space is left + for an edge to pass in between the nodes. This option should only be used in conjunction + with top-down layout." + targets parents + default = false + requires rotator == RotationStrategy.ROTATE_TO_ANGLE +} + +advanced option outgoingEdgeAngles: boolean { + label "Outgoing Edge Angles" + description + "Calculate the required angle of connected nodes to leave space for an incoming edge. This + option should only be used in conjunction with top-down layout." + targets parents + default = false +} + //Compaction option compactor: CompactionStrategy { label "Compaction" diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java index 8e64145634..c70b815acc 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java @@ -12,17 +12,15 @@ import java.util.List; import org.eclipse.elk.alg.common.NodeMicroLayout; -import org.eclipse.elk.alg.common.nodespacing.NodeDimensionCalculation; import org.eclipse.elk.alg.radial.intermediate.IntermediateProcessorStrategy; import org.eclipse.elk.alg.radial.options.CompactionStrategy; import org.eclipse.elk.alg.radial.options.RadialOptions; +import org.eclipse.elk.alg.radial.options.RotationStrategy; import org.eclipse.elk.core.AbstractLayoutProvider; import org.eclipse.elk.core.alg.AlgorithmAssembler; import org.eclipse.elk.core.alg.ILayoutProcessor; import org.eclipse.elk.core.alg.LayoutProcessorConfiguration; import org.eclipse.elk.core.util.IElkProgressMonitor; -import org.eclipse.elk.core.util.adapters.ElkGraphAdapters; -import org.eclipse.elk.core.util.adapters.ElkGraphAdapters.ElkGraphAdapter; import org.eclipse.elk.graph.ElkNode; /** @@ -89,8 +87,17 @@ private List> assembleAlgorithm(final ElkNode layoutGr if (layoutGraph.getProperty(RadialOptions.COMPACTOR) != CompactionStrategy.NONE) { configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.COMPACTION); } + + if (layoutGraph.getProperty(RadialOptions.ROTATOR) != RotationStrategy.NONE) { + configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.ROTATION); + } + configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.GRAPH_SIZE_CALCULATION); + + if (layoutGraph.getProperty(RadialOptions.OUTGOING_EDGE_ANGLES)) { + configuration.addAfter(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.OUTGOING_EDGE_ANGLES); + } algorithmAssembler.addProcessorConfiguration(configuration); diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java new file mode 100644 index 0000000000..090293279a --- /dev/null +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2023 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.radial.intermediate; + +import org.eclipse.elk.alg.radial.InternalProperties; +import org.eclipse.elk.alg.radial.options.RadialOptions; +import org.eclipse.elk.core.alg.ILayoutProcessor; +import org.eclipse.elk.core.math.KVector; +import org.eclipse.elk.core.util.IElkProgressMonitor; +import org.eclipse.elk.graph.ElkEdge; +import org.eclipse.elk.graph.ElkNode; + +/** + * Calculates the angles of outgoing edges so that they can be used as an input by subsequent child + * layouts. Only makes sense when used in a top-down layout. + * + */ +public class EdgeAngleCalculator implements ILayoutProcessor { + + /* (non-Javadoc) + * @see org.eclipse.elk.core.alg.ILayoutProcessor#process(java.lang.Object, org.eclipse.elk.core.util.IElkProgressMonitor) + */ + @Override + public void process(ElkNode graph, IElkProgressMonitor progressMonitor) { + + ElkNode root = graph.getProperty(InternalProperties.ROOT_NODE); + for (ElkEdge edge : root.getOutgoingEdges()) { + + KVector start = new KVector(edge.getSections().get(0).getStartX(), edge.getSections().get(0).getStartY()); + KVector end = new KVector(edge.getSections().get(0).getEndX(), edge.getSections().get(0).getEndY()); + + KVector edgeVector = end.add(start.scale(-1, -1)); + double angle = Math.atan2(edgeVector.y, edgeVector.x); + + edge.getTargets().get(0).setProperty(RadialOptions.TARGET_ANGLE, angle); + } + + } + +} diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java index 039e0e0aa7..c43beae9c0 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java @@ -11,6 +11,7 @@ import org.eclipse.elk.alg.radial.intermediate.compaction.GeneralCompactor; import org.eclipse.elk.alg.radial.intermediate.overlaps.RadiusExtensionOverlapRemoval; +import org.eclipse.elk.alg.radial.intermediate.rotation.GeneralRotator; import org.eclipse.elk.core.alg.ILayoutProcessor; import org.eclipse.elk.core.alg.ILayoutProcessorFactory; import org.eclipse.elk.graph.ElkNode; @@ -27,8 +28,15 @@ public enum IntermediateProcessorStrategy implements ILayoutProcessorFactory create() { @@ -37,8 +45,12 @@ public ILayoutProcessor create() { return new RadiusExtensionOverlapRemoval(); case COMPACTION: return new GeneralCompactor(); + case ROTATION: + return new GeneralRotator(); case GRAPH_SIZE_CALCULATION: return new CalculateGraphSize(); + case OUTGOING_EDGE_ANGLES: + return new EdgeAngleCalculator(); default: throw new IllegalArgumentException( "No implementation is available for the layout processor " + this.toString()); diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java new file mode 100644 index 0000000000..52e58d27c9 --- /dev/null +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2023 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.radial.intermediate.rotation; + +import org.eclipse.elk.alg.radial.InternalProperties; +import org.eclipse.elk.alg.radial.options.RadialOptions; +import org.eclipse.elk.core.math.KVector; +import org.eclipse.elk.graph.ElkNode; + +/** + * Rotates the entire layout around the origin to a set target angle. + * + */ +public class AngleRotation implements IRadialRotator { + + /* (non-Javadoc) + * @see org.eclipse.elk.alg.radial.intermediate.rotation.IRadialRotator#rotate(org.eclipse.elk.graph.ElkNode) + */ + @Override + public void rotate(ElkNode graph) { + double targetAngle = graph.getProperty(RadialOptions.TARGET_ANGLE); + + if (graph.getProperty(RadialOptions.COMPUTE_ADDITIONAL_WEDGE_SPACE)) { + // additionally we want to get half the angle of the first wedge to leave space + // take first two nodes and compute angle between their incident edges + ElkNode root = graph.getProperty(InternalProperties.ROOT_NODE); + if (root.getOutgoingEdges().size() <= 1) { + // leave angle untouched + if (targetAngle < 0) { + targetAngle += 2*Math.PI; + } + ElkNode firstNode = (ElkNode) root.getOutgoingEdges().get(0).getTargets().get(0); + KVector firstVector = new KVector(firstNode.getX() + firstNode.getWidth() / 2, firstNode.getY() + firstNode.getHeight() / 2); + + double alignmentAngle = Math.atan2(firstVector.y, firstVector.x); + if (alignmentAngle < 0) { + alignmentAngle += 2*Math.PI; + } + + targetAngle = Math.PI - (alignmentAngle - targetAngle + Math.PI); + } else { + ElkNode firstNode = (ElkNode) root.getOutgoingEdges().get(0).getTargets().get(0); + ElkNode secondNode = (ElkNode) root.getOutgoingEdges().get(1).getTargets().get(0); + KVector firstVector = new KVector(firstNode.getX() + firstNode.getWidth() / 2, firstNode.getY() + firstNode.getHeight() / 2); + KVector secondVector = new KVector(secondNode.getX() + secondNode.getWidth() / 2, secondNode.getY() + secondNode.getHeight() / 2); + + double alpha = targetAngle; + if (alpha < 0) { + alpha += 2*Math.PI; + } + + double wedgeAngle = firstVector.angle(secondVector); + if (wedgeAngle < 0) { + wedgeAngle += 2*Math.PI; + } + + double alignmentAngle = Math.atan2(firstVector.y, firstVector.x); + if (alignmentAngle < 0) { + alignmentAngle += 2*Math.PI; + } + + targetAngle = Math.PI - (alignmentAngle - alpha + wedgeAngle / 2); + + } + } + + // rotate all nodes around the origin, because the root node is positioned at the origin + // nodes are positioned with their center on the radius so use that for the rotation + for (ElkNode node : graph.getChildren()) { + KVector pos = new KVector(node.getX() + node.getWidth() / 2, node.getY() + node.getHeight() / 2); + pos.rotate(targetAngle); + node.setLocation(pos.x - node.getWidth() / 2, pos.y - node.getHeight() / 2); + } + + } + +} diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java new file mode 100644 index 0000000000..f18ab9b249 --- /dev/null +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2023 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.radial.intermediate.rotation; + +import org.eclipse.elk.alg.radial.options.RadialOptions; +import org.eclipse.elk.core.alg.ILayoutProcessor; +import org.eclipse.elk.core.util.IElkProgressMonitor; +import org.eclipse.elk.graph.ElkNode; + +/** + * The layout processor for rotation. + * + */ +public class GeneralRotator implements ILayoutProcessor { + + @Override + public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) { + progressMonitor.begin("General 'Rotator", 1); + progressMonitor.logGraph(graph, "Before"); + IRadialRotator rotator = graph.getProperty(RadialOptions.ROTATOR).create(); + rotator.rotate(graph); + progressMonitor.logGraph(graph, "After"); + } + +} diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/IRadialRotator.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/IRadialRotator.java new file mode 100644 index 0000000000..e02af67c30 --- /dev/null +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/IRadialRotator.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2023 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.radial.intermediate.rotation; + +import org.eclipse.elk.graph.ElkNode; + +/** + * An interface for rotating the radial layout. + * + */ +public interface IRadialRotator { + + /** + * Rotate the graph. + * + * @param graph + * The graph which is already radial. + */ + void rotate(ElkNode graph); + +} \ No newline at end of file diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java new file mode 100644 index 0000000000..e2941a8d40 --- /dev/null +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2023 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.radial.options; + +import org.eclipse.elk.alg.radial.intermediate.rotation.AngleRotation; +import org.eclipse.elk.alg.radial.intermediate.rotation.IRadialRotator; + +/** + * The list of selectable rotation algorithms. + * + */ +public enum RotationStrategy { + + /** No rotation. **/ + NONE, + /** Rotate to a given angle. */ + ROTATE_TO_ANGLE; + + /** + * Instantiate the chosen rotation strategy. + * + * @return A rotator. + */ + public IRadialRotator create() { + switch (this) { + case ROTATE_TO_ANGLE: + return new AngleRotation(); + default: + throw new IllegalArgumentException( + "No implementation is available for the layout option " + this.toString()); + } + } +} diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/math/KVector.java b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/math/KVector.java index 1f35375674..edfb99529a 100644 --- a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/math/KVector.java +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/math/KVector.java @@ -435,10 +435,20 @@ public static double crossProduct(final KVector v, final KVector w) { * @return the rotated vector */ public KVector rotate(final double angle) { - this.x = this.x * Math.cos(angle) - this.y * Math.sin(angle); - this.y = this.y * Math.sin(angle) + this.y * Math.cos(angle); + double newX = this.x * Math.cos(angle) - this.y * Math.sin(angle); + this.y = this.x * Math.sin(angle) + this.y * Math.cos(angle); + this.x = newX; return this; } + + /** + * Returns the angle between this vector and another given vector in radians. + * @param other + * @return angle between vectors + */ + public double angle(KVector other) { + return Math.acos(this.dotProduct(other) / (this.length() * other.length())); + } /** * Apply the given bounds to this vector. From e810340161f7cb65e7a7d3a5e2d26bcbc23f3dc6 Mon Sep 17 00:00:00 2001 From: Max Kasperowski Date: Thu, 20 Jul 2023 13:59:42 +0200 Subject: [PATCH 2/3] Updated documentation and refactoring --- .../org/eclipse/elk/alg/radial/Radial.melk | 72 ++++++++--------- .../elk/alg/radial/RadialLayoutProvider.java | 7 +- .../intermediate/EdgeAngleCalculator.java | 11 ++- .../IntermediateProcessorStrategy.java | 2 +- .../compaction/GeneralCompactor.java | 14 +++- .../intermediate/rotation/AngleRotation.java | 77 ++++++++----------- .../intermediate/rotation/GeneralRotator.java | 17 ++-- .../alg/radial/options/RotationStrategy.java | 40 ---------- 8 files changed, 105 insertions(+), 135 deletions(-) delete mode 100644 plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk index 3885b9f0e0..afef45d06f 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/Radial.melk @@ -38,10 +38,10 @@ algorithm radial(RadialLayoutProvider) { supports org.eclipse.elk.portLabels.placement supports compactionStepSize supports compactor - supports rotator - supports targetAngle - supports computeAdditionalWedgeSpace - supports outgoingEdgeAngles + supports rotate + supports rotation.targetAngle + supports rotation.computeAdditionalWedgeSpace + supports rotation.outgoingEdgeAngles supports optimizationCriteria supports orderId supports radius @@ -77,44 +77,46 @@ option radius: double { } // Rotation -option rotator: RotationStrategy { - label "Rotation" +option rotate: boolean { + label "Rotate" description - "The rotator option determines how a rotation of the layout should be performed." - targets parents - default = RotationStrategy.NONE -} - -option targetAngle: double { - label "Target Angle" - description - "The angle in radians that the layout should be rotated to after layout. Used by - {@link RotationStrategy.ROTATE_TO_ANGLE}." - targets parents - default = 0 - requires rotator == RotationStrategy.ROTATE_TO_ANGLE -} - -advanced option computeAdditionalWedgeSpace: boolean { - label "Additional Wedge Space" - description - "If set to true, modifies the target angle by rotating further such that space is left - for an edge to pass in between the nodes. This option should only be used in conjunction - with top-down layout." + "The rotate option determines whether a rotation of the layout should be performed." targets parents default = false - requires rotator == RotationStrategy.ROTATE_TO_ANGLE } -advanced option outgoingEdgeAngles: boolean { - label "Outgoing Edge Angles" - description - "Calculate the required angle of connected nodes to leave space for an incoming edge. This - option should only be used in conjunction with top-down layout." - targets parents - default = false +group rotation { + option targetAngle: double { + label "Target Angle" + description + "The angle in radians that the layout should be rotated to after layout." + targets parents + default = 0 + requires rotate + } + + advanced option computeAdditionalWedgeSpace: boolean { + label "Additional Wedge Space" + description + "If set to true, modifies the target angle by rotating further such that space is left + for an edge to pass in between the nodes. This option should only be used in conjunction + with top-down layout." + targets parents + default = false + requires rotate + } + + advanced option outgoingEdgeAngles: boolean { + label "Outgoing Edge Angles" + description + "Calculate the required angle of connected nodes to leave space for an incoming edge. This + option should only be used in conjunction with top-down layout." + targets parents + default = false + } } + //Compaction option compactor: CompactionStrategy { label "Compaction" diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java index c70b815acc..3c323d4577 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Kiel University and others. + * Copyright (c) 2017, 2020, 2023 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 @@ -15,7 +15,6 @@ import org.eclipse.elk.alg.radial.intermediate.IntermediateProcessorStrategy; import org.eclipse.elk.alg.radial.options.CompactionStrategy; import org.eclipse.elk.alg.radial.options.RadialOptions; -import org.eclipse.elk.alg.radial.options.RotationStrategy; import org.eclipse.elk.core.AbstractLayoutProvider; import org.eclipse.elk.core.alg.AlgorithmAssembler; import org.eclipse.elk.core.alg.ILayoutProcessor; @@ -88,14 +87,14 @@ private List> assembleAlgorithm(final ElkNode layoutGr configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.COMPACTION); } - if (layoutGraph.getProperty(RadialOptions.ROTATOR) != RotationStrategy.NONE) { + if (layoutGraph.getProperty(RadialOptions.ROTATE)) { configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.ROTATION); } configuration.addBefore(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.GRAPH_SIZE_CALCULATION); - if (layoutGraph.getProperty(RadialOptions.OUTGOING_EDGE_ANGLES)) { + if (layoutGraph.getProperty(RadialOptions.ROTATION_OUTGOING_EDGE_ANGLES)) { configuration.addAfter(RadialLayoutPhases.P2_EDGE_ROUTING, IntermediateProcessorStrategy.OUTGOING_EDGE_ANGLES); } diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java index 090293279a..d272bea223 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/EdgeAngleCalculator.java @@ -24,8 +24,11 @@ */ public class EdgeAngleCalculator implements ILayoutProcessor { - /* (non-Javadoc) - * @see org.eclipse.elk.core.alg.ILayoutProcessor#process(java.lang.Object, org.eclipse.elk.core.util.IElkProgressMonitor) + /** + * For each of edges connected to the root node we calculate its angle and store that information on the + * connected target node. This node can then later use that information as basis to align its own layout + * to the incoming edge. Because this sets an option on child nodes, this is only useful when laying the + * graph out in a top-down manner (or possibly in multiple layout runs). */ @Override public void process(ElkNode graph, IElkProgressMonitor progressMonitor) { @@ -36,10 +39,10 @@ public void process(ElkNode graph, IElkProgressMonitor progressMonitor) { KVector start = new KVector(edge.getSections().get(0).getStartX(), edge.getSections().get(0).getStartY()); KVector end = new KVector(edge.getSections().get(0).getEndX(), edge.getSections().get(0).getEndY()); - KVector edgeVector = end.add(start.scale(-1, -1)); + KVector edgeVector = KVector.diff(end, start); double angle = Math.atan2(edgeVector.y, edgeVector.x); - edge.getTargets().get(0).setProperty(RadialOptions.TARGET_ANGLE, angle); + edge.getTargets().get(0).setProperty(RadialOptions.ROTATION_TARGET_ANGLE, angle); } } diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java index c43beae9c0..9a9ae7ee9a 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/IntermediateProcessorStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Kiel University and others. + * Copyright (c) 2017, 2023 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 diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/compaction/GeneralCompactor.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/compaction/GeneralCompactor.java index 65262d6f13..37ccdc4026 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/compaction/GeneralCompactor.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/compaction/GeneralCompactor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Kiel University and others. + * Copyright (c) 2017, 2023 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 @@ -22,9 +22,17 @@ public class GeneralCompactor implements ILayoutProcessor { @Override public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) { progressMonitor.begin("General Compactor", 1); - progressMonitor.logGraph(graph, "Before"); + // elkjs-exclude-start + if (progressMonitor.isLoggingEnabled()) { + progressMonitor.logGraph(graph, "Before"); + } + // elkjs-exclude-end IRadialCompactor compactor = graph.getProperty(RadialOptions.COMPACTOR).create(); compactor.compact(graph); - progressMonitor.logGraph(graph, "After"); + // elkjs-exclude-start + if (progressMonitor.isLoggingEnabled()) { + progressMonitor.logGraph(graph, "After"); + } + // elkjs-exclude-end } } \ No newline at end of file diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java index 52e58d27c9..797b95d9d0 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/AngleRotation.java @@ -20,55 +20,46 @@ */ public class AngleRotation implements IRadialRotator { - /* (non-Javadoc) - * @see org.eclipse.elk.alg.radial.intermediate.rotation.IRadialRotator#rotate(org.eclipse.elk.graph.ElkNode) - */ @Override public void rotate(ElkNode graph) { - double targetAngle = graph.getProperty(RadialOptions.TARGET_ANGLE); + double targetAngle = graph.getProperty(RadialOptions.ROTATION_TARGET_ANGLE); - if (graph.getProperty(RadialOptions.COMPUTE_ADDITIONAL_WEDGE_SPACE)) { - // additionally we want to get half the angle of the first wedge to leave space - // take first two nodes and compute angle between their incident edges + if (graph.getProperty(RadialOptions.ROTATION_COMPUTE_ADDITIONAL_WEDGE_SPACE)) { + // Using the target angle as our base alignment we want to further rotate the layout such that a line + // following the target angle runs directly through the middle of the wedge between the first and last node. ElkNode root = graph.getProperty(InternalProperties.ROOT_NODE); - if (root.getOutgoingEdges().size() <= 1) { - // leave angle untouched - if (targetAngle < 0) { - targetAngle += 2*Math.PI; - } - ElkNode firstNode = (ElkNode) root.getOutgoingEdges().get(0).getTargets().get(0); - KVector firstVector = new KVector(firstNode.getX() + firstNode.getWidth() / 2, firstNode.getY() + firstNode.getHeight() / 2); - - double alignmentAngle = Math.atan2(firstVector.y, firstVector.x); - if (alignmentAngle < 0) { - alignmentAngle += 2*Math.PI; - } - - targetAngle = Math.PI - (alignmentAngle - targetAngle + Math.PI); - } else { - ElkNode firstNode = (ElkNode) root.getOutgoingEdges().get(0).getTargets().get(0); - ElkNode secondNode = (ElkNode) root.getOutgoingEdges().get(1).getTargets().get(0); - KVector firstVector = new KVector(firstNode.getX() + firstNode.getWidth() / 2, firstNode.getY() + firstNode.getHeight() / 2); - KVector secondVector = new KVector(secondNode.getX() + secondNode.getWidth() / 2, secondNode.getY() + secondNode.getHeight() / 2); - - double alpha = targetAngle; - if (alpha < 0) { - alpha += 2*Math.PI; - } - - double wedgeAngle = firstVector.angle(secondVector); - if (wedgeAngle < 0) { - wedgeAngle += 2*Math.PI; - } - - double alignmentAngle = Math.atan2(firstVector.y, firstVector.x); - if (alignmentAngle < 0) { - alignmentAngle += 2*Math.PI; - } - - targetAngle = Math.PI - (alignmentAngle - alpha + wedgeAngle / 2); + + ElkNode lastNode = (ElkNode) root.getOutgoingEdges().get(root.getOutgoingEdges().size() - 1).getTargets().get(0); + ElkNode firstNode = (ElkNode) root.getOutgoingEdges().get(0).getTargets().get(0); + KVector lastVector = new KVector(lastNode.getX() + lastNode.getWidth() / 2, lastNode.getY() + lastNode.getHeight() / 2); + KVector firstVector = new KVector(firstNode.getX() + firstNode.getWidth() / 2, firstNode.getY() + firstNode.getHeight() / 2); + // we shift all angles into the range (0,pi] to avoid dealing with negative angles. + double alpha = targetAngle; + if (alpha <= 0) { + alpha += 2*Math.PI; + } + + double wedgeAngle = lastVector.angle(firstVector); + if (wedgeAngle <= 0) { + wedgeAngle += 2*Math.PI; } + + double alignmentAngle = Math.atan2(lastVector.y, lastVector.x); + if (alignmentAngle <= 0) { + alignmentAngle += 2*Math.PI; + } + + // alpha (originally targetAngle) is the angle of the incoming edge that we wish to align ourselves with. + // wedgeAngle is the angle between the first and last nodes of our own layout. For the case of a single + // node this is 360 degrees. + // alignmentAngle is the angle of the vector pointing to the last node i.e. the end part of the segment + // we rotate the entire layout by subtracting the incoming angle alpha and we add half the wedge angle back + // to make the alignment go through the center of the wedge. Finally, we need to do a transformation to + // make all this work in our downward facing coordinate system. So everything is inverted and we need subtract + // the result from 180 degrees. + targetAngle = Math.PI - (alignmentAngle - alpha + wedgeAngle / 2); + } // rotate all nodes around the origin, because the root node is positioned at the origin diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java index f18ab9b249..4b8c2a9881 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/intermediate/rotation/GeneralRotator.java @@ -9,13 +9,12 @@ *******************************************************************************/ package org.eclipse.elk.alg.radial.intermediate.rotation; -import org.eclipse.elk.alg.radial.options.RadialOptions; import org.eclipse.elk.core.alg.ILayoutProcessor; import org.eclipse.elk.core.util.IElkProgressMonitor; import org.eclipse.elk.graph.ElkNode; /** - * The layout processor for rotation. + * The layout processor for rotation. Sets up logging and calls the angle rotation implementation. * */ public class GeneralRotator implements ILayoutProcessor { @@ -23,10 +22,18 @@ public class GeneralRotator implements ILayoutProcessor { @Override public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) { progressMonitor.begin("General 'Rotator", 1); - progressMonitor.logGraph(graph, "Before"); - IRadialRotator rotator = graph.getProperty(RadialOptions.ROTATOR).create(); + // elkjs-exclude-start + if (progressMonitor.isLoggingEnabled()) { + progressMonitor.logGraph(graph, "Before"); + } + // elkjs-exclude-end + IRadialRotator rotator = new AngleRotation(); rotator.rotate(graph); - progressMonitor.logGraph(graph, "After"); + // elkjs-exclude-start + if (progressMonitor.isLoggingEnabled()) { + progressMonitor.logGraph(graph, "After"); + } + // elkjs-exclude-end } } diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java deleted file mode 100644 index e2941a8d40..0000000000 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/options/RotationStrategy.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023 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.radial.options; - -import org.eclipse.elk.alg.radial.intermediate.rotation.AngleRotation; -import org.eclipse.elk.alg.radial.intermediate.rotation.IRadialRotator; - -/** - * The list of selectable rotation algorithms. - * - */ -public enum RotationStrategy { - - /** No rotation. **/ - NONE, - /** Rotate to a given angle. */ - ROTATE_TO_ANGLE; - - /** - * Instantiate the chosen rotation strategy. - * - * @return A rotator. - */ - public IRadialRotator create() { - switch (this) { - case ROTATE_TO_ANGLE: - return new AngleRotation(); - default: - throw new IllegalArgumentException( - "No implementation is available for the layout option " + this.toString()); - } - } -} From 7523b97ac09329e7dcd671767000002c236052a5 Mon Sep 17 00:00:00 2001 From: Max Kasperowski Date: Thu, 20 Jul 2023 16:01:58 +0200 Subject: [PATCH 3/3] update copyright dates --- .../src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java index 3c323d4577..a2c7f34780 100644 --- a/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java +++ b/plugins/org.eclipse.elk.alg.radial/src/org/eclipse/elk/alg/radial/RadialLayoutProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020, 2023 Kiel University and others. + * Copyright (c) 2017 - 2023 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