diff --git a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p5edges/OrthogonalEdgeRouter.java b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p5edges/OrthogonalEdgeRouter.java index 61a81ffde2..4d82ab66ad 100644 --- a/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p5edges/OrthogonalEdgeRouter.java +++ b/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p5edges/OrthogonalEdgeRouter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2015 Kiel University and others. + * Copyright (c) 2010, 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 @@ -50,10 +50,6 @@ *
Postcondition:
each node is assigned a horizontal coordinate; * the bend points of each edge are set; the width of the whole graph is set
* - * - * @author msp - * @author cds - * @author jjc */ public final class OrthogonalEdgeRouter implements ILayoutPhase { @@ -262,18 +258,22 @@ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor PolylineEdgeRouter.PRED_EXTERNAL_WEST_OR_EAST_PORT); if (slotsCount > 0) { - // The space between each pair of edge segments, and between nodes and edges - double increment = - edgeNodeSpacing + (slotsCount - 1) * edgeEdgeSpacing; + // Compute routing area's width + double routingWidth = (slotsCount - 1) * edgeEdgeSpacing; + + if (leftLayer != null) { + routingWidth += edgeNodeSpacing; + } + if (rightLayer != null) { - increment += edgeNodeSpacing; + routingWidth += edgeNodeSpacing; } // If we are between two layers, make sure their minimal spacing is preserved - if (increment < nodeNodeSpacing && !isLeftLayerExternal && !isRightLayerExternal) { - increment = nodeNodeSpacing; + if (routingWidth < nodeNodeSpacing && !isLeftLayerExternal && !isRightLayerExternal) { + routingWidth = nodeNodeSpacing; } - xpos += increment; + xpos += routingWidth; } else if (!isLeftLayerExternal && !isRightLayerExternal) { // If all edges are straight, use the usual spacing xpos += nodeNodeSpacing; diff --git a/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/issues/Issue734Test.java b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/issues/Issue734Test.java new file mode 100644 index 0000000000..09a5360f9e --- /dev/null +++ b/test/org.eclipse.elk.alg.layered.test/src/org/eclipse/elk/alg/layered/issues/Issue734Test.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 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.layered.issues; + +import static org.junit.Assert.assertEquals; + +import java.util.DoubleSummaryStatistics; +import java.util.List; + +import org.eclipse.elk.alg.layered.intermediate.greedyswitch.TestGraphCreator; +import org.eclipse.elk.alg.layered.options.LayeredOptions; +import org.eclipse.elk.alg.test.framework.LayoutTestRunner; +import org.eclipse.elk.alg.test.framework.annotations.Algorithm; +import org.eclipse.elk.alg.test.framework.annotations.DefaultConfiguration; +import org.eclipse.elk.alg.test.framework.annotations.GraphResourceProvider; +import org.eclipse.elk.alg.test.framework.io.AbstractResourcePath; +import org.eclipse.elk.alg.test.framework.io.FileNameFilter; +import org.eclipse.elk.alg.test.framework.io.ModelResourcePath; +import org.eclipse.elk.core.util.ElkUtil; +import org.eclipse.elk.graph.ElkEdge; +import org.eclipse.elk.graph.ElkNode; +import org.eclipse.emf.common.util.EList; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.google.common.collect.Lists; + +@RunWith(LayoutTestRunner.class) +@Algorithm(LayeredOptions.ALGORITHM_ID) +public class Issue734Test extends TestGraphCreator { + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Sources + + @GraphResourceProvider + public List testGraphs() { + return Lists.newArrayList( + new ModelResourcePath("tickets/layered/734_nodeEdgeSpacingFlat.elkt"), + new ModelResourcePath("tickets/layered/734_nodeEdgeSpacingFlatMoreComplex.elkt"), + new ModelResourcePath("tickets/layered/734_nodeEdgeSpacingHierarchical.elkt")); + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Tests + + @Test + public void testOrthogonalEdges(final ElkNode graph) { + // Compute the area occupied by nodes on the top level + CoordinateSpan nodeSpan = computeNodeCoordinateSpan(graph.getChildren()); + CoordinateSpan edgeSpan = computeEdgeCoordinateSpan(graph.getContainedEdges()); + + // If the edge is routed properly, the edge span should have the same distance to the node span on both + // sides. In other words, their respective centres should be aligned + double nodeSpanCenter = (nodeSpan.min + nodeSpan.max) / 2; + double edgeSpanCenter = (edgeSpan.min + edgeSpan.max) / 2; + + assertEquals(nodeSpanCenter, edgeSpanCenter, 0.5); + } + + private CoordinateSpan computeNodeCoordinateSpan(EList nodes) { + CoordinateSpan span = new CoordinateSpan(); + + span.min = nodes.stream() + .mapToDouble(node -> node.getX()) + .min() + .orElse(0); + span.max = nodes.stream() + .mapToDouble(node -> node.getX() + node.getWidth()) + .max() + .orElse(0); + + return span; + } + + private CoordinateSpan computeEdgeCoordinateSpan(EList containedEdges) { + DoubleSummaryStatistics stats = containedEdges.stream() + .flatMap(edge -> edge.getSections().stream()) + .map(section -> ElkUtil.createVectorChain(section)) + .flatMap(chain -> chain.stream()) + .mapToDouble(vec -> vec.x) + .summaryStatistics(); + return new CoordinateSpan(stats.getMin(), stats.getMax()); + } + + private static class CoordinateSpan { + private double min = Double.POSITIVE_INFINITY; + private double max = Double.NEGATIVE_INFINITY; + + public CoordinateSpan() { + } + + public CoordinateSpan(double min, double max) { + this.min = min; + this.max = max; + } + } + +}