From a135bf53d1c8233fe570e313c9747a99c6685c39 Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 5 Oct 2022 13:42:53 +0200 Subject: [PATCH 01/18] allow to specify initial fixed positions for the layout Signed-off-by: Luma --- .../com/powsybl/forcelayout/ForceLayout.java | 24 +++- .../powsybl/nad/layout/BasicForceLayout.java | 10 ++ .../com/powsybl/nad/model/AbstractNode.java | 11 ++ src/main/java/com/powsybl/nad/model/Node.java | 10 ++ .../ForceLayoutWithFixedPositionsTest.java | 105 ++++++++++++++++++ 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java diff --git a/src/main/java/com/powsybl/forcelayout/ForceLayout.java b/src/main/java/com/powsybl/forcelayout/ForceLayout.java index 65f48899..b5e94402 100644 --- a/src/main/java/com/powsybl/forcelayout/ForceLayout.java +++ b/src/main/java/com/powsybl/forcelayout/ForceLayout.java @@ -70,6 +70,7 @@ public class ForceLayout { private double friction; private double maxSpeed; private double springRepulsionFactor; + private Map fixedPoints; private final Graph graph; private final Map points = new LinkedHashMap<>(); @@ -124,9 +125,20 @@ public ForceLayout setSpringRepulsionFactor(double springRepulsionFactor) return this; } + public ForceLayout setFixedPoints(Map fixedPoints) { + this.fixedPoints = fixedPoints; + return this; + } + private void initializePoints() { for (V vertex : graph.vertexSet()) { - points.put(vertex, new Point(random.nextDouble(), random.nextDouble())); + Point p; + if (fixedPoints.containsKey(vertex)) { + p = fixedPoints.get(vertex); + } else { + p = new Point(random.nextDouble(), random.nextDouble()); + } + points.put(vertex, p); } } @@ -264,7 +276,15 @@ private void updateVelocity() { } private void updatePosition() { - for (Point point : points.values()) { + // FIXME(Luma) do not compute forces/update velocities for fixed points + // We have computed forces and velocities for all points, + // even for the fixed ones (this could be optimized) + // But we only update the position for the ones that do not have fixed positions + for (Map.Entry vertexPoint : points.entrySet()) { + if (fixedPoints.containsKey(vertexPoint.getKey())) { + continue; + } + Point point = vertexPoint.getValue(); Vector position = point.getPosition().add(point.getVelocity().multiply(deltaTime)); point.setPosition(position); } diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index c6277e3a..f1aba6ec 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -12,7 +12,9 @@ import org.jgrapht.alg.util.Pair; import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * @author Florian Dupuy @@ -24,6 +26,14 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { org.jgrapht.Graph jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout()); ForceLayout forceLayout = new ForceLayout<>(jgraphtGraph); forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout()); + + Map fixedPoints = new LinkedHashMap<>(); + jgraphtGraph.vertexSet().forEach(node -> { + if (node.isFixedPosition()) { + fixedPoints.put(node, new com.powsybl.forcelayout.Point(node.getPosition().getX(), node.getPosition().getY())); + } + }); + forceLayout.setFixedPoints(fixedPoints); forceLayout.execute(); jgraphtGraph.vertexSet().forEach(node -> { diff --git a/src/main/java/com/powsybl/nad/model/AbstractNode.java b/src/main/java/com/powsybl/nad/model/AbstractNode.java index 80ac8e1d..55356106 100644 --- a/src/main/java/com/powsybl/nad/model/AbstractNode.java +++ b/src/main/java/com/powsybl/nad/model/AbstractNode.java @@ -14,6 +14,7 @@ public abstract class AbstractNode extends AbstractIdentifiable implements Node private int width; private int height; private Point position; + private boolean fixedPosition; protected AbstractNode(String diagramId, String equipmentId, String name) { super(diagramId, equipmentId, name); @@ -47,6 +48,16 @@ public double getY() { return position.getY(); } + @Override + public boolean isFixedPosition() { + return fixedPosition; + } + + @Override + public void setFixedPosition(boolean b) { + this.fixedPosition = b; + } + public int getWidth() { return width; } diff --git a/src/main/java/com/powsybl/nad/model/Node.java b/src/main/java/com/powsybl/nad/model/Node.java index 0fdb36cf..51b62721 100644 --- a/src/main/java/com/powsybl/nad/model/Node.java +++ b/src/main/java/com/powsybl/nad/model/Node.java @@ -6,6 +6,8 @@ */ package com.powsybl.nad.model; +import com.powsybl.commons.PowsyblException; + /** * @author Florian Dupuy */ @@ -20,4 +22,12 @@ public interface Node extends Identifiable { double getX(); double getY(); + + default void setFixedPosition(boolean b) { + throw new PowsyblException("not implemented"); + } + + default boolean isFixedPosition() { + return false; + } } diff --git a/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java b/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java new file mode 100644 index 00000000..7f38e808 --- /dev/null +++ b/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java @@ -0,0 +1,105 @@ +package com.powsybl.nad.layout; + +import com.powsybl.iidm.network.Network; +import com.powsybl.nad.NetworkAreaDiagram; +import com.powsybl.nad.build.iidm.IntIdProvider; +import com.powsybl.nad.build.iidm.NetworkGraphBuilder; +import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Node; +import com.powsybl.nad.model.VoltageLevelNode; +import com.powsybl.nad.svg.LabelProvider; +import com.powsybl.nad.svg.StyleProvider; +import com.powsybl.nad.svg.SvgParameters; +import com.powsybl.nad.svg.SvgWriter; +import com.powsybl.nad.svg.iidm.DefaultLabelProvider; +import com.powsybl.nad.svg.iidm.NominalVoltageStyleProvider; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ForceLayoutWithFixedPositionsTest { + + static final Path OUTPUT = Paths.get("/Users/zamarrenolm/work/temp/nad/fixed-positions/output"); + static final double SPRING_REPULSION_FACTOR = 0.2; + + @Test + void testDiamond() { + draw(SimpleNetworkFactory.createDiamond()); + } + + private static void draw(Network network) { + LayoutParameters layoutParameters = layoutParameters(); + SvgParameters svgParameters = svgParameters(); + + new NetworkAreaDiagram(network).draw( + OUTPUT.resolve("all"), + svgParameters, + layoutParameters); + + // FIXME(Luma) The following lines should be used like: + // Map backbonePoints = new NetworkAreaDiagram(network).layout(layoutParameters); + Graph graph = new NetworkGraphBuilder(network, vl -> vl.getNominalV() >= 100, new IntIdProvider()).buildGraph(); + new BasicForceLayoutFactory().create().run(graph, layoutParameters); + writeSvg(graph, network, "backbone", svgParameters); + printPositions(graph); + + // FIXME(Luma) Then these lines like: + // layoutParameters.setFixedPoints(backbonePoints); + Graph graph2 = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER, new IntIdProvider()).buildGraph(); + for (VoltageLevelNode vlFixedNode : graph.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { + Node vlFixedNode2 = graph2.getNode(vlFixedNode.getEquipmentId()).orElseThrow(); + vlFixedNode2.setPosition(vlFixedNode.getPosition()); + vlFixedNode2.setFixedPosition(true); + } + new BasicForceLayoutFactory().create().run(graph2, layoutParameters); + writeSvg(graph2, network, "all-with-fixed-backbone", svgParameters); + printPositions(graph2); + + // Check all positions from graph have been preserved in graph2 + checkFixedPositions(graph, graph2); + } + + private static void writeSvg(Graph graph, Network network, String subset, SvgParameters svgParameters) { + Path svgFile = OUTPUT.resolve(network.getNameOrId() + "-" + subset + ".svg"); + new SvgWriter(svgParameters, styleProvider(network), labelProvider(network, svgParameters)).writeSvg(graph, svgFile); + } + + private static void checkFixedPositions(Graph expected, Graph actual) { + for (VoltageLevelNode vlNodeExpected : expected.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { + String equipmentId = vlNodeExpected.getEquipmentId(); + Node vlNodeActual = actual.getNode(equipmentId).orElseThrow(); + assertEquals(vlNodeExpected.getPosition().getX(), vlNodeActual.getPosition().getX()); + assertEquals(vlNodeExpected.getPosition().getY(), vlNodeActual.getPosition().getY()); + } + } + + private static void printPositions(Graph graph) { + for (VoltageLevelNode vlNode : graph.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { + System.out.printf("%10.4f %10.4f %-32s %s%n", vlNode.getPosition().getX(), vlNode.getPosition().getY(), vlNode.getEquipmentId(), vlNode.getName().orElse("")); + } + } + + private static SvgParameters svgParameters() { + return new SvgParameters() + .setInsertNameDesc(false) + .setSvgWidthAndHeightAdded(false); + } + + private static StyleProvider styleProvider(Network network) { + return new NominalVoltageStyleProvider(network); + } + + private static LabelProvider labelProvider(Network network, SvgParameters svgParameters) { + return new DefaultLabelProvider(network, svgParameters); + } + + private static LayoutParameters layoutParameters() { + return new LayoutParameters() + .setSpringRepulsionFactorForceLayout(SPRING_REPULSION_FACTOR); + } +} From 5a7cfb57d849c0a8d00b26dc33c5fa34cf86d3ce Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 20 Oct 2022 17:15:55 +0200 Subject: [PATCH 02/18] Initial positions for some nodes given as a layout parameter Signed-off-by: Luma --- .../com/powsybl/forcelayout/ForceLayout.java | 28 +++-- .../com/powsybl/nad/NetworkAreaDiagram.java | 27 ++++ .../powsybl/nad/layout/BasicForceLayout.java | 25 ++-- .../powsybl/nad/layout/LayoutParameters.java | 16 +++ .../com/powsybl/nad/model/AbstractNode.java | 11 -- src/main/java/com/powsybl/nad/model/Node.java | 10 -- .../powsybl/nad/layout/ForceLayoutTest.java | 116 +----------------- .../ForceLayoutWithFixedPositionsTest.java | 105 ---------------- .../LayoutWithInitialPositionsTest.java | 62 ++++++++++ 9 files changed, 142 insertions(+), 258 deletions(-) delete mode 100644 src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java create mode 100644 src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java diff --git a/src/main/java/com/powsybl/forcelayout/ForceLayout.java b/src/main/java/com/powsybl/forcelayout/ForceLayout.java index b5e94402..b7a53e68 100644 --- a/src/main/java/com/powsybl/forcelayout/ForceLayout.java +++ b/src/main/java/com/powsybl/forcelayout/ForceLayout.java @@ -70,7 +70,8 @@ public class ForceLayout { private double friction; private double maxSpeed; private double springRepulsionFactor; - private Map fixedPoints; + private Map initialPoints; // Initial locations for some nodes + private Set fixedNodes; // The location of these nodes should not be modified by the layout private final Graph graph; private final Map points = new LinkedHashMap<>(); @@ -88,6 +89,8 @@ public ForceLayout(Graph graph) { this.springRepulsionFactor = DEFAULT_SPRING_REPULSION_FACTOR; this.graph = Objects.requireNonNull(graph); + this.initialPoints = Collections.emptyMap(); + this.fixedNodes = Collections.emptySet(); } public ForceLayout setMaxSteps(int maxSteps) { @@ -125,16 +128,21 @@ public ForceLayout setSpringRepulsionFactor(double springRepulsionFactor) return this; } - public ForceLayout setFixedPoints(Map fixedPoints) { - this.fixedPoints = fixedPoints; + public ForceLayout setInitialPoints(Map initialPoints) { + this.initialPoints = Objects.requireNonNull(initialPoints); + return this; + } + + public ForceLayout setFixedNodes(Set fixedNodes) { + this.fixedNodes = Objects.requireNonNull(fixedNodes); return this; } private void initializePoints() { for (V vertex : graph.vertexSet()) { Point p; - if (fixedPoints.containsKey(vertex)) { - p = fixedPoints.get(vertex); + if (initialPoints.containsKey(vertex)) { + p = initialPoints.get(vertex); } else { p = new Point(random.nextDouble(), random.nextDouble()); } @@ -276,12 +284,12 @@ private void updateVelocity() { } private void updatePosition() { - // FIXME(Luma) do not compute forces/update velocities for fixed points - // We have computed forces and velocities for all points, - // even for the fixed ones (this could be optimized) - // But we only update the position for the ones that do not have fixed positions + // TODO do not compute forces or update velocities for fixed nodes + // We have computed forces and velocities for all nodes, even for the fixed ones + // We can optimize calculations by ignoring fixed nodes in those calculations + // Here we only update the position for the nodes that do not have fixed positions for (Map.Entry vertexPoint : points.entrySet()) { - if (fixedPoints.containsKey(vertexPoint.getKey())) { + if (fixedNodes.contains(vertexPoint.getKey())) { continue; } Point point = vertexPoint.getValue(); diff --git a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java index e0336bd3..a464050f 100644 --- a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java +++ b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java @@ -16,6 +16,8 @@ import com.powsybl.nad.layout.LayoutFactory; import com.powsybl.nad.layout.LayoutParameters; import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Point; +import com.powsybl.nad.model.VoltageLevelNode; import com.powsybl.nad.svg.LabelProvider; import com.powsybl.nad.svg.StyleProvider; import com.powsybl.nad.svg.SvgParameters; @@ -29,8 +31,10 @@ import java.io.Writer; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * @author Florian Dupuy @@ -61,6 +65,10 @@ public NetworkAreaDiagram(Network network, Predicate voltageLevelF this.voltageLevelFilter = Objects.requireNonNull(voltageLevelFilter); } + public Network getNetwork() { + return network; + } + public void draw(Path svgFile) { draw(svgFile, new SvgParameters()); } @@ -88,6 +96,14 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay draw(svgFile, svgParameters, layoutParameters, styleProvider, labelProvider, layoutFactory, new IntIdProvider()); } + public Map layout(LayoutParameters layoutParameters) { + return layout(layoutParameters, new BasicForceLayoutFactory()); + } + + public Map layout(LayoutParameters layoutParameters, LayoutFactory layoutFactory) { + return layout(layoutParameters, layoutFactory, new IntIdProvider()); + } + public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters layoutParameters, StyleProvider styleProvider, LabelProvider labelProvider, LayoutFactory layoutFactory, IdProvider idProvider) { @@ -103,6 +119,17 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay new SvgWriter(svgParameters, styleProvider, labelProvider).writeSvg(graph, svgFile); } + public Map layout(LayoutParameters layoutParameters, LayoutFactory layoutFactory, IdProvider idProvider) { + Graph graph = new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); + layoutFactory.create().run(graph, layoutParameters); + return graph.getVoltageLevelNodesStream() + .filter(VoltageLevelNode::isVisible) + .collect(Collectors.toMap( + VoltageLevelNode::getEquipmentId, + VoltageLevelNode::getPosition + )); + } + public void draw(Writer writer) { draw(writer, new SvgParameters()); } diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index f1aba6ec..3f65ecf7 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -12,9 +12,9 @@ import org.jgrapht.alg.util.Pair; import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * @author Florian Dupuy @@ -27,13 +27,22 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { ForceLayout forceLayout = new ForceLayout<>(jgraphtGraph); forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout()); - Map fixedPoints = new LinkedHashMap<>(); - jgraphtGraph.vertexSet().forEach(node -> { - if (node.isFixedPosition()) { - fixedPoints.put(node, new com.powsybl.forcelayout.Point(node.getPosition().getX(), node.getPosition().getY())); - } - }); - forceLayout.setFixedPoints(fixedPoints); + // Define initial positions for the layout algorithm + Map initialPoints = layoutParameters.getInitialPositions().entrySet().stream() + // Only accept positions for nodes in the graph + .filter(idPoint -> graph.getNode(idPoint.getKey()).isPresent()) + .collect(Collectors.toMap( + idPoint -> graph.getNode(idPoint.getKey()).orElseThrow(), + idPoint -> new com.powsybl.forcelayout.Point(idPoint.getValue().getX(), idPoint.getValue().getY()), + // If same node has two points, keep the first one considered + (point1, point2) -> point1 + )); + forceLayout.setInitialPoints(initialPoints); + // TODO Here we are considered all nodes with initial position as fixed + // The fixed nodes could be a subset of the ones for which we give initial position + // For non-fixed nodes, initial position is just a "hint" for the layout algorithm + forceLayout.setFixedNodes(initialPoints.keySet()); + forceLayout.execute(); jgraphtGraph.vertexSet().forEach(node -> { diff --git a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java index 55dfa7ab..1ee3e054 100644 --- a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java +++ b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java @@ -6,12 +6,19 @@ */ package com.powsybl.nad.layout; +import com.powsybl.nad.model.Point; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * @author Florian Dupuy */ public class LayoutParameters { private boolean textNodesForceLayout = false; private double springRepulsionFactorForceLayout = 0.0; + private Map initialPositions = Collections.emptyMap(); public LayoutParameters() { } @@ -19,6 +26,15 @@ public LayoutParameters() { public LayoutParameters(LayoutParameters other) { this.textNodesForceLayout = other.textNodesForceLayout; this.springRepulsionFactorForceLayout = other.springRepulsionFactorForceLayout; + this.initialPositions = new HashMap<>(initialPositions); + } + + public Map getInitialPositions() { + return Collections.unmodifiableMap(initialPositions); + } + + public void setInitialPositions(Map initialPositions) { + this.initialPositions = new HashMap<>(initialPositions); } public boolean isTextNodesForceLayout() { diff --git a/src/main/java/com/powsybl/nad/model/AbstractNode.java b/src/main/java/com/powsybl/nad/model/AbstractNode.java index 55356106..80ac8e1d 100644 --- a/src/main/java/com/powsybl/nad/model/AbstractNode.java +++ b/src/main/java/com/powsybl/nad/model/AbstractNode.java @@ -14,7 +14,6 @@ public abstract class AbstractNode extends AbstractIdentifiable implements Node private int width; private int height; private Point position; - private boolean fixedPosition; protected AbstractNode(String diagramId, String equipmentId, String name) { super(diagramId, equipmentId, name); @@ -48,16 +47,6 @@ public double getY() { return position.getY(); } - @Override - public boolean isFixedPosition() { - return fixedPosition; - } - - @Override - public void setFixedPosition(boolean b) { - this.fixedPosition = b; - } - public int getWidth() { return width; } diff --git a/src/main/java/com/powsybl/nad/model/Node.java b/src/main/java/com/powsybl/nad/model/Node.java index 51b62721..0fdb36cf 100644 --- a/src/main/java/com/powsybl/nad/model/Node.java +++ b/src/main/java/com/powsybl/nad/model/Node.java @@ -6,8 +6,6 @@ */ package com.powsybl.nad.model; -import com.powsybl.commons.PowsyblException; - /** * @author Florian Dupuy */ @@ -22,12 +20,4 @@ public interface Node extends Identifiable { double getX(); double getY(); - - default void setFixedPosition(boolean b) { - throw new PowsyblException("not implemented"); - } - - default boolean isFixedPosition() { - return false; - } } diff --git a/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java b/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java index 2054a592..61939a55 100644 --- a/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java +++ b/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java @@ -45,7 +45,7 @@ protected LabelProvider getLabelProvider(Network network) { void testDiamondNoSpringRepulsionFactor() { assertEquals( toString("/diamond-spring-repulsion-factor-0.0.svg"), - generateSvgString(createDiamondNetwork(), "/diamond-spring-repulsion-factor-0.0.svg")); + generateSvgString(NetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.0.svg")); } @Test @@ -53,118 +53,6 @@ void testDiamondSmallSpringRepulsionFactor() { getLayoutParameters().setSpringRepulsionFactorForceLayout(0.2); assertEquals( toString("/diamond-spring-repulsion-factor-0.2.svg"), - generateSvgString(createDiamondNetwork(), "/diamond-spring-repulsion-factor-0.2.svg")); - } - - static Network createDiamondNetwork() { - Network network = NetworkFactory.findDefault().createNetwork("diamond", "manual"); - network.setName("diamond"); - - Substation subA = network.newSubstation().setId("A").add(); - Bus subA400 = createBus(subA, 400); - Bus subA230 = createBus(subA, 230); - createTransformer(subA400, subA230); - - Substation subB = network.newSubstation().setId("B").add(); - Bus subB230 = createBus(subB, 230); - createLine(subA230, subB230); - - Substation subC = network.newSubstation().setId("C").add(); - Bus subC230 = createBus(subC, 230); - Bus subC66 = createBus(subC, 66); - Bus subC20 = createBus(subC, 20); - createTransformer(subC230, subC66); - createTransformer(subC66, subC20); - createLine(subB230, subC230); - - Substation subD = network.newSubstation().setId("D").add(); - Bus subD66 = createBus(subD, 66); - Bus subD10 = createBus(subD, 10); - createTransformer(subD66, subD10); - createLine(subC66, subD66); - - Substation subE = network.newSubstation().setId("E").add(); - Bus subE10 = createBus(subE, 10); - createLine(subD10, subE10); - - Bus subF10 = createBus(network, "F", 10); - Bus subG10 = createBus(network, "G", 10); - Bus subH10 = createBus(network, "H", 10); - Bus subI10 = createBus(network, "I", 10); - Bus subJ10 = createBus(network, "J", 10); - Bus subK10 = createBus(network, "K", 10); - - createLine(subE10, subF10); - createLine(subF10, subG10); - createLine(subG10, subH10); - createLine(subH10, subD10); - - createLine(subF10, subI10); - createLine(subI10, subJ10); - createLine(subJ10, subK10); - createLine(subK10, subD10); - - return network; - } - - private static Bus createBus(Network network, String substationId, double nominalVoltage) { - Substation substation = network.newSubstation().setId(substationId).add(); - return createBus(substation, nominalVoltage); - } - - private static Bus createBus(Substation substation, double nominalVoltage) { - String vlId = String.format("%s %.0f", substation.getId(), nominalVoltage); - String busId = String.format("%s %s", vlId, "Bus"); - return substation.newVoltageLevel() - .setId(vlId) - .setNominalV(nominalVoltage) - .setTopologyKind(TopologyKind.BUS_BREAKER) - .add() - .getBusBreakerView() - .newBus() - .setId(busId) - .add(); - } - - private static void createTransformer(Bus bus1, Bus bus2) { - Substation substation = bus1.getVoltageLevel().getSubstation().orElseThrow(); - String id = String.format("%s %.0f %.0f", - substation.getId(), - bus1.getVoltageLevel().getNominalV(), - bus2.getVoltageLevel().getNominalV()); - substation.newTwoWindingsTransformer().setId(id) - .setR(0.0) - .setX(1.0) - .setG(0.0) - .setB(0.0) - .setVoltageLevel1(bus1.getVoltageLevel().getId()) - .setVoltageLevel2(bus2.getVoltageLevel().getId()) - .setConnectableBus1(bus1.getId()) - .setConnectableBus2(bus2.getId()) - .setRatedU1(bus1.getVoltageLevel().getNominalV()) - .setRatedU2(bus2.getVoltageLevel().getNominalV()) - .setBus1(bus1.getId()) - .setBus2(bus2.getId()) - .add(); - } - - private static void createLine(Bus bus1, Bus bus2) { - String id = String.format("%s - %s", - bus1.getVoltageLevel().getSubstation().orElseThrow().getId(), - bus2.getVoltageLevel().getSubstation().orElseThrow().getId()); - bus1.getNetwork().newLine().setId(id) - .setR(0.0) - .setX(1.0) - .setG1(0.0) - .setB1(0.0) - .setG2(0.0) - .setB2(0.0) - .setVoltageLevel1(bus1.getVoltageLevel().getId()) - .setVoltageLevel2(bus2.getVoltageLevel().getId()) - .setConnectableBus1(bus1.getId()) - .setConnectableBus2(bus2.getId()) - .setBus1(bus1.getId()) - .setBus2(bus2.getId()) - .add(); + generateSvgString(NetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.2.svg")); } } diff --git a/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java b/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java deleted file mode 100644 index 7f38e808..00000000 --- a/src/test/java/com/powsybl/nad/layout/ForceLayoutWithFixedPositionsTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.powsybl.nad.layout; - -import com.powsybl.iidm.network.Network; -import com.powsybl.nad.NetworkAreaDiagram; -import com.powsybl.nad.build.iidm.IntIdProvider; -import com.powsybl.nad.build.iidm.NetworkGraphBuilder; -import com.powsybl.nad.build.iidm.VoltageLevelFilter; -import com.powsybl.nad.model.Graph; -import com.powsybl.nad.model.Node; -import com.powsybl.nad.model.VoltageLevelNode; -import com.powsybl.nad.svg.LabelProvider; -import com.powsybl.nad.svg.StyleProvider; -import com.powsybl.nad.svg.SvgParameters; -import com.powsybl.nad.svg.SvgWriter; -import com.powsybl.nad.svg.iidm.DefaultLabelProvider; -import com.powsybl.nad.svg.iidm.NominalVoltageStyleProvider; -import org.junit.jupiter.api.Test; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ForceLayoutWithFixedPositionsTest { - - static final Path OUTPUT = Paths.get("/Users/zamarrenolm/work/temp/nad/fixed-positions/output"); - static final double SPRING_REPULSION_FACTOR = 0.2; - - @Test - void testDiamond() { - draw(SimpleNetworkFactory.createDiamond()); - } - - private static void draw(Network network) { - LayoutParameters layoutParameters = layoutParameters(); - SvgParameters svgParameters = svgParameters(); - - new NetworkAreaDiagram(network).draw( - OUTPUT.resolve("all"), - svgParameters, - layoutParameters); - - // FIXME(Luma) The following lines should be used like: - // Map backbonePoints = new NetworkAreaDiagram(network).layout(layoutParameters); - Graph graph = new NetworkGraphBuilder(network, vl -> vl.getNominalV() >= 100, new IntIdProvider()).buildGraph(); - new BasicForceLayoutFactory().create().run(graph, layoutParameters); - writeSvg(graph, network, "backbone", svgParameters); - printPositions(graph); - - // FIXME(Luma) Then these lines like: - // layoutParameters.setFixedPoints(backbonePoints); - Graph graph2 = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER, new IntIdProvider()).buildGraph(); - for (VoltageLevelNode vlFixedNode : graph.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { - Node vlFixedNode2 = graph2.getNode(vlFixedNode.getEquipmentId()).orElseThrow(); - vlFixedNode2.setPosition(vlFixedNode.getPosition()); - vlFixedNode2.setFixedPosition(true); - } - new BasicForceLayoutFactory().create().run(graph2, layoutParameters); - writeSvg(graph2, network, "all-with-fixed-backbone", svgParameters); - printPositions(graph2); - - // Check all positions from graph have been preserved in graph2 - checkFixedPositions(graph, graph2); - } - - private static void writeSvg(Graph graph, Network network, String subset, SvgParameters svgParameters) { - Path svgFile = OUTPUT.resolve(network.getNameOrId() + "-" + subset + ".svg"); - new SvgWriter(svgParameters, styleProvider(network), labelProvider(network, svgParameters)).writeSvg(graph, svgFile); - } - - private static void checkFixedPositions(Graph expected, Graph actual) { - for (VoltageLevelNode vlNodeExpected : expected.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { - String equipmentId = vlNodeExpected.getEquipmentId(); - Node vlNodeActual = actual.getNode(equipmentId).orElseThrow(); - assertEquals(vlNodeExpected.getPosition().getX(), vlNodeActual.getPosition().getX()); - assertEquals(vlNodeExpected.getPosition().getY(), vlNodeActual.getPosition().getY()); - } - } - - private static void printPositions(Graph graph) { - for (VoltageLevelNode vlNode : graph.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) { - System.out.printf("%10.4f %10.4f %-32s %s%n", vlNode.getPosition().getX(), vlNode.getPosition().getY(), vlNode.getEquipmentId(), vlNode.getName().orElse("")); - } - } - - private static SvgParameters svgParameters() { - return new SvgParameters() - .setInsertNameDesc(false) - .setSvgWidthAndHeightAdded(false); - } - - private static StyleProvider styleProvider(Network network) { - return new NominalVoltageStyleProvider(network); - } - - private static LabelProvider labelProvider(Network network, SvgParameters svgParameters) { - return new DefaultLabelProvider(network, svgParameters); - } - - private static LayoutParameters layoutParameters() { - return new LayoutParameters() - .setSpringRepulsionFactorForceLayout(SPRING_REPULSION_FACTOR); - } -} diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java new file mode 100644 index 00000000..2010fe96 --- /dev/null +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad.layout; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.nad.NetworkAreaDiagram; +import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Point; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.function.Predicate; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Luma Zamarreno + */ +class LayoutWithInitialPositionsTest { + + @Test + void testDiamond() { + checkLayoutWithInitialPositions(NetworkFactory.createDiamond()); + } + + private static void checkLayoutWithInitialPositions(Network network) { + Predicate filter = vl -> vl.getNominalV() >= 100; + LayoutParameters layoutParameters = new LayoutParameters() + .setSpringRepulsionFactorForceLayout(0.2); + + // Perform an initial layout with only a few voltage levels of the network + NetworkAreaDiagram initialDiagram = new NetworkAreaDiagram(network, filter); + Map initialPositions = initialDiagram.layout(layoutParameters); + + // Check initial points contains an entry for all voltage levels filtered + network.getVoltageLevelStream().filter(filter).forEach(vl -> assertTrue(initialPositions.containsKey(vl.getId()))); + // Check we have voltage levels in the network that are not filtered and thus will not have an initial positions + assertTrue(network.getVoltageLevelStream().anyMatch(filter.negate())); + network.getVoltageLevelStream().filter(filter.negate()).forEach(vl -> assertFalse(initialPositions.containsKey(vl.getId()))); + + // Perform a global layout with all the voltage levels in the network, + // giving initial (fixed) positions for some equipment + layoutParameters.setInitialPositions(initialPositions); + NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); + Map allPositions = completeNetworkDiagram.layout(layoutParameters); + + // Check positions of initial layout have been preserved in global layout + for (Map.Entry l : initialPositions.entrySet()) { + String equipmentId = l.getKey(); + Point expected = l.getValue(); + Point actual = allPositions.get(equipmentId); + assertNotNull(actual); + assertEquals(expected.getX(), actual.getX()); + assertEquals(expected.getY(), actual.getY()); + } + } +} From 4a388e90ba4174f8fea2f639a4993c78dbb9560c Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 20 Oct 2022 17:28:01 +0200 Subject: [PATCH 03/18] No need to access the network of the diagram Signed-off-by: Luma --- src/main/java/com/powsybl/nad/NetworkAreaDiagram.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java index a464050f..c097a724 100644 --- a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java +++ b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java @@ -65,10 +65,6 @@ public NetworkAreaDiagram(Network network, Predicate voltageLevelF this.voltageLevelFilter = Objects.requireNonNull(voltageLevelFilter); } - public Network getNetwork() { - return network; - } - public void draw(Path svgFile) { draw(svgFile, new SvgParameters()); } From 2f7d7dc78521fdc9480b1e82e520b2480bb97bd3 Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 20 Oct 2022 17:35:50 +0200 Subject: [PATCH 04/18] checkstyle fixes; rename network factory used for layout tests Signed-off-by: Luma --- .../powsybl/nad/layout/BasicForceLayout.java | 8 +- .../powsybl/nad/layout/ForceLayoutTest.java | 4 +- .../nad/layout/LayoutNetworkFactory.java | 131 ++++++++++++++++++ .../LayoutWithInitialPositionsTest.java | 2 +- 4 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/powsybl/nad/layout/LayoutNetworkFactory.java diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index 3f65ecf7..0af2c5ab 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -32,10 +32,10 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { // Only accept positions for nodes in the graph .filter(idPoint -> graph.getNode(idPoint.getKey()).isPresent()) .collect(Collectors.toMap( - idPoint -> graph.getNode(idPoint.getKey()).orElseThrow(), - idPoint -> new com.powsybl.forcelayout.Point(idPoint.getValue().getX(), idPoint.getValue().getY()), - // If same node has two points, keep the first one considered - (point1, point2) -> point1 + idPoint -> graph.getNode(idPoint.getKey()).orElseThrow(), + idPoint -> new com.powsybl.forcelayout.Point(idPoint.getValue().getX(), idPoint.getValue().getY()), + // If same node has two points, keep the first one considered + (point1, point2) -> point1 )); forceLayout.setInitialPoints(initialPoints); // TODO Here we are considered all nodes with initial position as fixed diff --git a/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java b/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java index 61939a55..d092d580 100644 --- a/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java +++ b/src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java @@ -45,7 +45,7 @@ protected LabelProvider getLabelProvider(Network network) { void testDiamondNoSpringRepulsionFactor() { assertEquals( toString("/diamond-spring-repulsion-factor-0.0.svg"), - generateSvgString(NetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.0.svg")); + generateSvgString(LayoutNetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.0.svg")); } @Test @@ -53,6 +53,6 @@ void testDiamondSmallSpringRepulsionFactor() { getLayoutParameters().setSpringRepulsionFactorForceLayout(0.2); assertEquals( toString("/diamond-spring-repulsion-factor-0.2.svg"), - generateSvgString(NetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.2.svg")); + generateSvgString(LayoutNetworkFactory.createDiamond(), "/diamond-spring-repulsion-factor-0.2.svg")); } } diff --git a/src/test/java/com/powsybl/nad/layout/LayoutNetworkFactory.java b/src/test/java/com/powsybl/nad/layout/LayoutNetworkFactory.java new file mode 100644 index 00000000..8889e21c --- /dev/null +++ b/src/test/java/com/powsybl/nad/layout/LayoutNetworkFactory.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad.layout; + +import com.powsybl.iidm.network.*; + +/** + * @author Luma Zamarreno + */ +public final class LayoutNetworkFactory { + + private LayoutNetworkFactory() { + // Empty + } + + public static Network createDiamond() { + Network network = com.powsybl.iidm.network.NetworkFactory.findDefault().createNetwork("diamond", "manual"); + network.setName("diamond"); + + Substation subA = network.newSubstation().setId("A").add(); + Bus subA400 = createBus(subA, 400); + Bus subA230 = createBus(subA, 230); + createTransformer(subA400, subA230); + + Substation subB = network.newSubstation().setId("B").add(); + Bus subB230 = createBus(subB, 230); + createLine(subA230, subB230); + + Substation subC = network.newSubstation().setId("C").add(); + Bus subC230 = createBus(subC, 230); + Bus subC66 = createBus(subC, 66); + Bus subC20 = createBus(subC, 20); + createTransformer(subC230, subC66); + createTransformer(subC66, subC20); + createLine(subB230, subC230); + + Substation subD = network.newSubstation().setId("D").add(); + Bus subD66 = createBus(subD, 66); + Bus subD10 = createBus(subD, 10); + createTransformer(subD66, subD10); + createLine(subC66, subD66); + + Substation subE = network.newSubstation().setId("E").add(); + Bus subE10 = createBus(subE, 10); + createLine(subD10, subE10); + + Bus subF10 = createBus(network, "F", 10); + Bus subG10 = createBus(network, "G", 10); + Bus subH10 = createBus(network, "H", 10); + Bus subI10 = createBus(network, "I", 10); + Bus subJ10 = createBus(network, "J", 10); + Bus subK10 = createBus(network, "K", 10); + + createLine(subE10, subF10); + createLine(subF10, subG10); + createLine(subG10, subH10); + createLine(subH10, subD10); + + createLine(subF10, subI10); + createLine(subI10, subJ10); + createLine(subJ10, subK10); + createLine(subK10, subD10); + + return network; + } + + private static Bus createBus(Network network, String substationId, double nominalVoltage) { + Substation substation = network.newSubstation().setId(substationId).add(); + return createBus(substation, nominalVoltage); + } + + private static Bus createBus(Substation substation, double nominalVoltage) { + String vlId = String.format("%s %.0f", substation.getId(), nominalVoltage); + String busId = String.format("%s %s", vlId, "Bus"); + return substation.newVoltageLevel() + .setId(vlId) + .setNominalV(nominalVoltage) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add() + .getBusBreakerView() + .newBus() + .setId(busId) + .add(); + } + + private static void createTransformer(Bus bus1, Bus bus2) { + Substation substation = bus1.getVoltageLevel().getSubstation().orElseThrow(); + String id = String.format("%s %.0f %.0f", + substation.getId(), + bus1.getVoltageLevel().getNominalV(), + bus2.getVoltageLevel().getNominalV()); + substation.newTwoWindingsTransformer().setId(id) + .setR(0.0) + .setX(1.0) + .setG(0.0) + .setB(0.0) + .setVoltageLevel1(bus1.getVoltageLevel().getId()) + .setVoltageLevel2(bus2.getVoltageLevel().getId()) + .setConnectableBus1(bus1.getId()) + .setConnectableBus2(bus2.getId()) + .setRatedU1(bus1.getVoltageLevel().getNominalV()) + .setRatedU2(bus2.getVoltageLevel().getNominalV()) + .setBus1(bus1.getId()) + .setBus2(bus2.getId()) + .add(); + } + + private static void createLine(Bus bus1, Bus bus2) { + String id = String.format("%s - %s", + bus1.getVoltageLevel().getSubstation().orElseThrow().getId(), + bus2.getVoltageLevel().getSubstation().orElseThrow().getId()); + bus1.getNetwork().newLine().setId(id) + .setR(0.0) + .setX(1.0) + .setG1(0.0) + .setB1(0.0) + .setG2(0.0) + .setB2(0.0) + .setVoltageLevel1(bus1.getVoltageLevel().getId()) + .setVoltageLevel2(bus2.getVoltageLevel().getId()) + .setConnectableBus1(bus1.getId()) + .setConnectableBus2(bus2.getId()) + .setBus1(bus1.getId()) + .setBus2(bus2.getId()) + .add(); + } +} diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index 2010fe96..38ed962d 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -25,7 +25,7 @@ class LayoutWithInitialPositionsTest { @Test void testDiamond() { - checkLayoutWithInitialPositions(NetworkFactory.createDiamond()); + checkLayoutWithInitialPositions(LayoutNetworkFactory.createDiamond()); } private static void checkLayoutWithInitialPositions(Network network) { From 90620ce9fc1da5ea19dfdd62757da404f446ad21 Mon Sep 17 00:00:00 2001 From: Luma Date: Tue, 25 Oct 2022 15:22:53 +0200 Subject: [PATCH 05/18] chain setter Signed-off-by: Luma --- src/main/java/com/powsybl/nad/layout/LayoutParameters.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java index 1ee3e054..05cf3b18 100644 --- a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java +++ b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java @@ -33,8 +33,9 @@ public Map getInitialPositions() { return Collections.unmodifiableMap(initialPositions); } - public void setInitialPositions(Map initialPositions) { + public LayoutParameters setInitialPositions(Map initialPositions) { this.initialPositions = new HashMap<>(initialPositions); + return this; } public boolean isTextNodesForceLayout() { From 47ec75fa89a90c65f8e4903b90d1c88c06cca0c8 Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 10:54:19 +0100 Subject: [PATCH 06/18] initial positions are not parameters Signed-off-by: Luma --- .../com/powsybl/nad/NetworkAreaDiagram.java | 31 ++++++---------- .../powsybl/nad/layout/AbstractLayout.java | 37 ++++++++++++++++++- .../powsybl/nad/layout/BasicForceLayout.java | 35 ++++++++++-------- .../java/com/powsybl/nad/layout/Layout.java | 14 ++++++- .../powsybl/nad/layout/LayoutParameters.java | 17 --------- .../LayoutWithInitialPositionsTest.java | 8 ++-- 6 files changed, 85 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java index c097a724..ae877c3f 100644 --- a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java +++ b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java @@ -13,11 +13,10 @@ import com.powsybl.nad.build.iidm.NetworkGraphBuilder; import com.powsybl.nad.build.iidm.VoltageLevelFilter; import com.powsybl.nad.layout.BasicForceLayoutFactory; +import com.powsybl.nad.layout.Layout; import com.powsybl.nad.layout.LayoutFactory; import com.powsybl.nad.layout.LayoutParameters; import com.powsybl.nad.model.Graph; -import com.powsybl.nad.model.Point; -import com.powsybl.nad.model.VoltageLevelNode; import com.powsybl.nad.svg.LabelProvider; import com.powsybl.nad.svg.StyleProvider; import com.powsybl.nad.svg.SvgParameters; @@ -31,10 +30,8 @@ import java.io.Writer; import java.nio.file.Path; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * @author Florian Dupuy @@ -92,12 +89,17 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay draw(svgFile, svgParameters, layoutParameters, styleProvider, labelProvider, layoutFactory, new IntIdProvider()); } - public Map layout(LayoutParameters layoutParameters) { - return layout(layoutParameters, new BasicForceLayoutFactory()); + public Graph buildGraph() { + return buildGraph(new IntIdProvider()); } - public Map layout(LayoutParameters layoutParameters, LayoutFactory layoutFactory) { - return layout(layoutParameters, layoutFactory, new IntIdProvider()); + public Graph buildGraph(IdProvider idProvider) { + Objects.requireNonNull(idProvider); + return new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); + } + + public Layout getLayout() { + return new BasicForceLayoutFactory().create(); } public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters layoutParameters, @@ -110,22 +112,11 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay Objects.requireNonNull(layoutFactory); Objects.requireNonNull(idProvider); - Graph graph = new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); + Graph graph = buildGraph(idProvider); layoutFactory.create().run(graph, layoutParameters); new SvgWriter(svgParameters, styleProvider, labelProvider).writeSvg(graph, svgFile); } - public Map layout(LayoutParameters layoutParameters, LayoutFactory layoutFactory, IdProvider idProvider) { - Graph graph = new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); - layoutFactory.create().run(graph, layoutParameters); - return graph.getVoltageLevelNodesStream() - .filter(VoltageLevelNode::isVisible) - .collect(Collectors.toMap( - VoltageLevelNode::getEquipmentId, - VoltageLevelNode::getPosition - )); - } - public void draw(Writer writer) { draw(writer, new SvgParameters()); } diff --git a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java index 94eaa8e7..0cf5d8ab 100644 --- a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java +++ b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java @@ -2,13 +2,20 @@ import com.powsybl.nad.model.*; +import java.util.Collections; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class AbstractLayout implements Layout { + private Map initialNodePositions = Collections.emptyMap(); + private Set nodesWithFixedPosition = Collections.emptySet(); + @Override - public void run(Graph graph, LayoutParameters layoutParameters) { + public Map run(Graph graph, LayoutParameters layoutParameters) { Objects.requireNonNull(graph); Objects.requireNonNull(layoutParameters); @@ -17,6 +24,34 @@ public void run(Graph graph, LayoutParameters layoutParameters) { edgesLayout(graph, layoutParameters); computeSize(graph); + + return graph.getVoltageLevelNodesStream() + .filter(VoltageLevelNode::isVisible) + .collect(Collectors.toMap( + VoltageLevelNode::getEquipmentId, + VoltageLevelNode::getPosition + )); + } + + @Override + public Map getInitialNodePositions() { + return initialNodePositions; + } + + @Override + public void setInitialNodePositions(Map initialNodePositions) { + Objects.requireNonNull(initialNodePositions); + this.initialNodePositions = initialNodePositions; + } + + @Override + public void setNodesWithFixedPosition(Set nodesWithFixedPosition) { + this.nodesWithFixedPosition = nodesWithFixedPosition; + } + + @Override + public Set getNodesWithFixedPosition() { + return nodesWithFixedPosition; } protected abstract void nodesLayout(Graph graph, LayoutParameters layoutParameters); diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index 0af2c5ab..86d309fd 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -14,6 +14,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -27,21 +28,11 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { ForceLayout forceLayout = new ForceLayout<>(jgraphtGraph); forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout()); - // Define initial positions for the layout algorithm - Map initialPoints = layoutParameters.getInitialPositions().entrySet().stream() - // Only accept positions for nodes in the graph - .filter(idPoint -> graph.getNode(idPoint.getKey()).isPresent()) - .collect(Collectors.toMap( - idPoint -> graph.getNode(idPoint.getKey()).orElseThrow(), - idPoint -> new com.powsybl.forcelayout.Point(idPoint.getValue().getX(), idPoint.getValue().getY()), - // If same node has two points, keep the first one considered - (point1, point2) -> point1 - )); - forceLayout.setInitialPoints(initialPoints); - // TODO Here we are considered all nodes with initial position as fixed - // The fixed nodes could be a subset of the ones for which we give initial position - // For non-fixed nodes, initial position is just a "hint" for the layout algorithm - forceLayout.setFixedNodes(initialPoints.keySet()); + setInitialPositions(forceLayout, graph); + forceLayout.setFixedNodes(getNodesWithFixedPosition().stream() + .map(graph::getNode) + .flatMap(Optional::stream) + .collect(Collectors.toSet())); forceLayout.execute(); @@ -55,6 +46,20 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { } } + private void setInitialPositions(ForceLayout forceLayout, Graph graph) { + Map initialPoints = getInitialNodePositions().entrySet().stream() + // Only accept positions for nodes in the graph + .filter(nodePosition -> graph.getNode(nodePosition.getKey()).isPresent()) + .collect(Collectors.toMap( + nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(), + nodePosition -> new com.powsybl.forcelayout.Point(nodePosition.getValue().getX(), nodePosition.getValue().getY()), + // If same node has two points, keep the first one considered + (point1, point2) -> point1 + )); + forceLayout.setInitialPoints(initialPoints); + } + + @Override protected void busNodesLayout(Graph graph, LayoutParameters layoutParameters) { Comparator c = Comparator.comparing(bn -> graph.getBusEdges(bn).size()); graph.getVoltageLevelNodesStream().forEach(n -> { diff --git a/src/main/java/com/powsybl/nad/layout/Layout.java b/src/main/java/com/powsybl/nad/layout/Layout.java index 65977d83..9bf6e1c1 100644 --- a/src/main/java/com/powsybl/nad/layout/Layout.java +++ b/src/main/java/com/powsybl/nad/layout/Layout.java @@ -7,10 +7,22 @@ package com.powsybl.nad.layout; import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Point; + +import java.util.Map; +import java.util.Set; /** * @author Florian Dupuy */ public interface Layout { - void run(Graph graph, LayoutParameters layoutParameters); + Map run(Graph graph, LayoutParameters layoutParameters); + + void setInitialNodePositions(Map initialNodePositions); + + void setNodesWithFixedPosition(Set nodesWithFixedPosition); + + Map getInitialNodePositions(); + + Set getNodesWithFixedPosition(); } diff --git a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java index 05cf3b18..55dfa7ab 100644 --- a/src/main/java/com/powsybl/nad/layout/LayoutParameters.java +++ b/src/main/java/com/powsybl/nad/layout/LayoutParameters.java @@ -6,19 +6,12 @@ */ package com.powsybl.nad.layout; -import com.powsybl.nad.model.Point; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - /** * @author Florian Dupuy */ public class LayoutParameters { private boolean textNodesForceLayout = false; private double springRepulsionFactorForceLayout = 0.0; - private Map initialPositions = Collections.emptyMap(); public LayoutParameters() { } @@ -26,16 +19,6 @@ public LayoutParameters() { public LayoutParameters(LayoutParameters other) { this.textNodesForceLayout = other.textNodesForceLayout; this.springRepulsionFactorForceLayout = other.springRepulsionFactorForceLayout; - this.initialPositions = new HashMap<>(initialPositions); - } - - public Map getInitialPositions() { - return Collections.unmodifiableMap(initialPositions); - } - - public LayoutParameters setInitialPositions(Map initialPositions) { - this.initialPositions = new HashMap<>(initialPositions); - return this; } public boolean isTextNodesForceLayout() { diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index 38ed962d..d3f5e91d 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -35,7 +35,7 @@ private static void checkLayoutWithInitialPositions(Network network) { // Perform an initial layout with only a few voltage levels of the network NetworkAreaDiagram initialDiagram = new NetworkAreaDiagram(network, filter); - Map initialPositions = initialDiagram.layout(layoutParameters); + Map initialPositions = initialDiagram.getLayout().run(initialDiagram.buildGraph(), layoutParameters); // Check initial points contains an entry for all voltage levels filtered network.getVoltageLevelStream().filter(filter).forEach(vl -> assertTrue(initialPositions.containsKey(vl.getId()))); @@ -45,9 +45,11 @@ private static void checkLayoutWithInitialPositions(Network network) { // Perform a global layout with all the voltage levels in the network, // giving initial (fixed) positions for some equipment - layoutParameters.setInitialPositions(initialPositions); NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); - Map allPositions = completeNetworkDiagram.layout(layoutParameters); + Layout layout = completeNetworkDiagram.getLayout(); + layout.setInitialNodePositions(initialPositions); + layout.setNodesWithFixedPosition(initialPositions.keySet()); + Map allPositions = layout.run(completeNetworkDiagram.buildGraph(), layoutParameters); // Check positions of initial layout have been preserved in global layout for (Map.Entry l : initialPositions.entrySet()) { From 77596364fe67203d818047f6f5ca40e5d7a44b39 Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 11:06:08 +0100 Subject: [PATCH 07/18] fix indentation Signed-off-by: Luma --- .../java/com/powsybl/nad/layout/BasicForceLayout.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index 86d309fd..58968bc9 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -51,10 +51,10 @@ private void setInitialPositions(ForceLayout forceLayout, Graph grap // Only accept positions for nodes in the graph .filter(nodePosition -> graph.getNode(nodePosition.getKey()).isPresent()) .collect(Collectors.toMap( - nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(), - nodePosition -> new com.powsybl.forcelayout.Point(nodePosition.getValue().getX(), nodePosition.getValue().getY()), - // If same node has two points, keep the first one considered - (point1, point2) -> point1 + nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(), + nodePosition -> new com.powsybl.forcelayout.Point(nodePosition.getValue().getX(), nodePosition.getValue().getY()), + // If same node has two points, keep the first one considered + (point1, point2) -> point1 )); forceLayout.setInitialPoints(initialPoints); } From 5fe3f5db05762abbc75a666b4e15280cfccbc639 Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 16:18:52 +0100 Subject: [PATCH 08/18] new layout with fixed, provided, positions Signed-off-by: Luma --- .../powsybl/nad/layout/BasicFixedLayout.java | 60 +++++++++++++++++++ .../nad/layout/BasicFixedLayoutFactory.java | 17 ++++++ 2 files changed, 77 insertions(+) create mode 100644 src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java create mode 100644 src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java diff --git a/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java b/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java new file mode 100644 index 00000000..17c77823 --- /dev/null +++ b/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad.layout; + +import com.powsybl.nad.model.*; +import org.jgrapht.alg.util.Pair; + +import java.util.Comparator; +import java.util.List; + +/** + * @author Luma Zamarreño + */ +public class BasicFixedLayout extends AbstractLayout { + + @Override + protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { + org.jgrapht.Graph jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout()); + + jgraphtGraph.vertexSet().forEach(node -> { + Point p = getInitialNodePositions().get(node.getEquipmentId()); + if (p != null) { + node.setPosition(p.getX(), p.getY()); + } + }); + + if (!layoutParameters.isTextNodesForceLayout()) { + graph.getTextEdgesMap().forEach(this::fixedTextNodeLayout); + } + } + + @Override + protected void busNodesLayout(Graph graph, LayoutParameters layoutParameters) { + Comparator c = Comparator.comparing(bn -> graph.getBusEdges(bn).size()); + graph.getVoltageLevelNodesStream().forEach(n -> { + n.sortBusNodes(c); + List sortedNodes = n.getBusNodes(); + for (int i = 0; i < sortedNodes.size(); i++) { + BusNode busNode = sortedNodes.get(i); + busNode.setIndex(i); + busNode.setNbNeighbouringBusNodes(sortedNodes.size() - 1); + busNode.setPosition(n.getPosition()); + } + }); + } + + private void fixedTextNodeLayout(TextEdge textEdge, Pair nodes) { + Point fixedShift = getTextNodeFixedShift(); + Point textPos = nodes.getFirst().getPosition().shift(fixedShift.getX(), fixedShift.getY()); + nodes.getSecond().setPosition(textPos); + } + + protected Point getTextNodeFixedShift() { + return new Point(1, 0); + } +} diff --git a/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java b/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java new file mode 100644 index 00000000..d26ffa08 --- /dev/null +++ b/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad.layout; + +/** + * @author Luma Zamarreño + */ +public class BasicFixedLayoutFactory implements LayoutFactory { + @Override + public Layout create() { + return new BasicFixedLayout(); + } +} From f2f3ee077a8fbf5ac0438445845753c3ff1e0f0b Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 16:39:05 +0100 Subject: [PATCH 09/18] unit test for fixed layout Signed-off-by: Luma --- .../java/com/powsybl/nad/model/Point.java | 17 ++++++++ .../powsybl/nad/layout/FixedLayoutTest.java | 39 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java diff --git a/src/main/java/com/powsybl/nad/model/Point.java b/src/main/java/com/powsybl/nad/model/Point.java index c1c8bf4b..b357e852 100644 --- a/src/main/java/com/powsybl/nad/model/Point.java +++ b/src/main/java/com/powsybl/nad/model/Point.java @@ -25,6 +25,23 @@ public Point() { this(0, 0); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Point point = (Point) o; + return x == point.x && y == point.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + public static Point createMiddlePoint(Point point1, Point point2) { Objects.requireNonNull(point1); Objects.requireNonNull(point2); diff --git a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java new file mode 100644 index 00000000..9dd013c9 --- /dev/null +++ b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad.layout; + +import com.powsybl.iidm.network.Network; +import com.powsybl.nad.build.iidm.NetworkGraphBuilder; +import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Point; +import com.powsybl.nad.svg.NetworkTestFactory; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreno + */ +class FixedLayoutTest { + + @Test + void testCurrentLimits() { + Network network = NetworkTestFactory.createTwoVoltageLevels(); + + Map expected = Map.of( + "vl1", new Point(1, 0), + "vl2", new Point(2, 1)); + Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); + Layout fixedLayout = new BasicFixedLayoutFactory().create(); + fixedLayout.setInitialNodePositions(expected); + Map actual = fixedLayout.run(graph, new LayoutParameters()); + assertEquals(expected, actual); + } +} From c7114a46e294069ab9cd103894883b28d463568a Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 16:54:45 +0100 Subject: [PATCH 10/18] avoid duplicated code Signed-off-by: Luma --- .../powsybl/nad/layout/AbstractLayout.java | 30 +++++++++++++--- .../powsybl/nad/layout/BasicFixedLayout.java | 34 +++---------------- .../powsybl/nad/layout/BasicForceLayout.java | 31 ++--------------- 3 files changed, 32 insertions(+), 63 deletions(-) diff --git a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java index 0cf5d8ab..00ce4cd7 100644 --- a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java +++ b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java @@ -1,11 +1,9 @@ package com.powsybl.nad.layout; import com.powsybl.nad.model.*; +import org.jgrapht.alg.util.Pair; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -56,7 +54,29 @@ public Set getNodesWithFixedPosition() { protected abstract void nodesLayout(Graph graph, LayoutParameters layoutParameters); - protected abstract void busNodesLayout(Graph graph, LayoutParameters layoutParameters); + protected void busNodesLayout(Graph graph, LayoutParameters ignoredLayoutParameters) { + Comparator c = Comparator.comparing(bn -> graph.getBusEdges(bn).size()); + graph.getVoltageLevelNodesStream().forEach(n -> { + n.sortBusNodes(c); + List sortedNodes = n.getBusNodes(); + for (int i = 0; i < sortedNodes.size(); i++) { + BusNode busNode = sortedNodes.get(i); + busNode.setIndex(i); + busNode.setNbNeighbouringBusNodes(sortedNodes.size() - 1); + busNode.setPosition(n.getPosition()); + } + }); + } + + protected void fixedTextNodeLayout(TextEdge ignoredTextEdge, Pair nodes) { + Point fixedShift = getTextNodeFixedShift(); + Point textPos = nodes.getFirst().getPosition().shift(fixedShift.getX(), fixedShift.getY()); + nodes.getSecond().setPosition(textPos); + } + + protected Point getTextNodeFixedShift() { + return new Point(1, 0); + } protected void edgesLayout(Graph graph, LayoutParameters layoutParameters) { Objects.requireNonNull(graph); diff --git a/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java b/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java index 17c77823..6442a9ba 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicFixedLayout.java @@ -6,11 +6,10 @@ */ package com.powsybl.nad.layout; -import com.powsybl.nad.model.*; -import org.jgrapht.alg.util.Pair; - -import java.util.Comparator; -import java.util.List; +import com.powsybl.nad.model.Edge; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Node; +import com.powsybl.nad.model.Point; /** * @author Luma Zamarreño @@ -32,29 +31,4 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { graph.getTextEdgesMap().forEach(this::fixedTextNodeLayout); } } - - @Override - protected void busNodesLayout(Graph graph, LayoutParameters layoutParameters) { - Comparator c = Comparator.comparing(bn -> graph.getBusEdges(bn).size()); - graph.getVoltageLevelNodesStream().forEach(n -> { - n.sortBusNodes(c); - List sortedNodes = n.getBusNodes(); - for (int i = 0; i < sortedNodes.size(); i++) { - BusNode busNode = sortedNodes.get(i); - busNode.setIndex(i); - busNode.setNbNeighbouringBusNodes(sortedNodes.size() - 1); - busNode.setPosition(n.getPosition()); - } - }); - } - - private void fixedTextNodeLayout(TextEdge textEdge, Pair nodes) { - Point fixedShift = getTextNodeFixedShift(); - Point textPos = nodes.getFirst().getPosition().shift(fixedShift.getX(), fixedShift.getY()); - nodes.getSecond().setPosition(textPos); - } - - protected Point getTextNodeFixedShift() { - return new Point(1, 0); - } } diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index 58968bc9..e5bba7be 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -8,11 +8,10 @@ import com.powsybl.forcelayout.ForceLayout; import com.powsybl.forcelayout.Vector; -import com.powsybl.nad.model.*; -import org.jgrapht.alg.util.Pair; +import com.powsybl.nad.model.Edge; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Node; -import java.util.Comparator; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -59,28 +58,4 @@ private void setInitialPositions(ForceLayout forceLayout, Graph grap forceLayout.setInitialPoints(initialPoints); } - @Override - protected void busNodesLayout(Graph graph, LayoutParameters layoutParameters) { - Comparator c = Comparator.comparing(bn -> graph.getBusEdges(bn).size()); - graph.getVoltageLevelNodesStream().forEach(n -> { - n.sortBusNodes(c); - List sortedNodes = n.getBusNodes(); - for (int i = 0; i < sortedNodes.size(); i++) { - BusNode busNode = sortedNodes.get(i); - busNode.setIndex(i); - busNode.setNbNeighbouringBusNodes(sortedNodes.size() - 1); - busNode.setPosition(n.getPosition()); - } - }); - } - - private void fixedTextNodeLayout(TextEdge textEdge, Pair nodes) { - Point fixedShift = getTextNodeFixedShift(); - Point textPos = nodes.getFirst().getPosition().shift(fixedShift.getX(), fixedShift.getY()); - nodes.getSecond().setPosition(textPos); - } - - protected Point getTextNodeFixedShift() { - return new Point(1, 0); - } } From e4b0ef6c3cfd2edace02792120b480a6d998b958 Mon Sep 17 00:00:00 2001 From: Luma Date: Thu, 3 Nov 2022 17:03:07 +0100 Subject: [PATCH 11/18] complete coverage Signed-off-by: Luma --- .../java/com/powsybl/nad/layout/FixedLayoutTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java index 9dd013c9..b11bd7d9 100644 --- a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java +++ b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java @@ -17,12 +17,21 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; /** * @author Luma Zamarreno */ class FixedLayoutTest { + @Test + void testPointsAreDifferent() { + Point p0 = new Point(1, 0); + Point p1 = new Point(2, 0); + assertNotEquals(p0, p1); + assertNotEquals(p0.hashCode(), p1.hashCode()); + } + @Test void testCurrentLimits() { Network network = NetworkTestFactory.createTwoVoltageLevels(); From f6f808bcdffe2b50b947ecafc48e95000efc5e77 Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 9 Nov 2022 11:02:45 +0100 Subject: [PATCH 12/18] changes after review Signed-off-by: Luma --- .../com/powsybl/forcelayout/ForceLayout.java | 21 +++++--- .../java/com/powsybl/nad/layout/Layout.java | 5 ++ .../LayoutWithInitialPositionsTest.java | 52 +++++++++++++++---- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/powsybl/forcelayout/ForceLayout.java b/src/main/java/com/powsybl/forcelayout/ForceLayout.java index b7a53e68..05c54f47 100644 --- a/src/main/java/com/powsybl/forcelayout/ForceLayout.java +++ b/src/main/java/com/powsybl/forcelayout/ForceLayout.java @@ -53,7 +53,8 @@ public class ForceLayout { private static final Logger LOGGER = LoggerFactory.getLogger(ForceLayout.class); - private final Random random = new Random(3L); // deterministic randomness + /** Deterministic randomness */ + private final Random random = new Random(3L); private static final int DEFAULT_MAX_STEPS = 1000; private static final double DEFAULT_MIN_ENERGY_THRESHOLD = 0.001; @@ -61,7 +62,8 @@ public class ForceLayout { private static final double DEFAULT_REPULSION = 800.0; private static final double DEFAULT_FRICTION = 500; private static final double DEFAULT_MAX_SPEED = 100; - private static final double DEFAULT_SPRING_REPULSION_FACTOR = 0.0; // Disabled by default + /** Spring repulsion is disabled by default */ + private static final double DEFAULT_SPRING_REPULSION_FACTOR = 0.0; private int maxSteps; private double minEnergyThreshold; @@ -70,8 +72,10 @@ public class ForceLayout { private double friction; private double maxSpeed; private double springRepulsionFactor; - private Map initialPoints; // Initial locations for some nodes - private Set fixedNodes; // The location of these nodes should not be modified by the layout + /** Initial location for some nodes */ + private Map initialPoints = Collections.emptyMap(); + /** The location of these nodes should not be modified by the layout */ + private Set fixedNodes = Collections.emptySet(); private final Graph graph; private final Map points = new LinkedHashMap<>(); @@ -87,10 +91,7 @@ public ForceLayout(Graph graph) { this.friction = DEFAULT_FRICTION; this.maxSpeed = DEFAULT_MAX_SPEED; this.springRepulsionFactor = DEFAULT_SPRING_REPULSION_FACTOR; - this.graph = Objects.requireNonNull(graph); - this.initialPoints = Collections.emptyMap(); - this.fixedNodes = Collections.emptySet(); } public ForceLayout setMaxSteps(int maxSteps) { @@ -133,6 +134,12 @@ public ForceLayout setInitialPoints(Map initialPoints) { return this; } + public ForceLayout setFixedPoints(Map fixedPoints) { + this.initialPoints = Objects.requireNonNull(fixedPoints); + setFixedNodes(fixedPoints.keySet()); + return this; + } + public ForceLayout setFixedNodes(Set fixedNodes) { this.fixedNodes = Objects.requireNonNull(fixedNodes); return this; diff --git a/src/main/java/com/powsybl/nad/layout/Layout.java b/src/main/java/com/powsybl/nad/layout/Layout.java index 9bf6e1c1..3f5bdb7d 100644 --- a/src/main/java/com/powsybl/nad/layout/Layout.java +++ b/src/main/java/com/powsybl/nad/layout/Layout.java @@ -22,6 +22,11 @@ public interface Layout { void setNodesWithFixedPosition(Set nodesWithFixedPosition); + default void setFixedNodePositions(Map fixedNodePositions) { + setInitialNodePositions(fixedNodePositions); + setNodesWithFixedPosition(fixedNodePositions.keySet()); + } + Map getInitialNodePositions(); Set getNodesWithFixedPosition(); diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index d3f5e91d..ea8f67e7 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import static org.junit.jupiter.api.Assertions.*; @@ -30,12 +31,10 @@ void testDiamond() { private static void checkLayoutWithInitialPositions(Network network) { Predicate filter = vl -> vl.getNominalV() >= 100; - LayoutParameters layoutParameters = new LayoutParameters() - .setSpringRepulsionFactorForceLayout(0.2); // Perform an initial layout with only a few voltage levels of the network NetworkAreaDiagram initialDiagram = new NetworkAreaDiagram(network, filter); - Map initialPositions = initialDiagram.getLayout().run(initialDiagram.buildGraph(), layoutParameters); + Map initialPositions = initialDiagram.getLayout().run(initialDiagram.buildGraph(), getLayoutParameters()); // Check initial points contains an entry for all voltage levels filtered network.getVoltageLevelStream().filter(filter).forEach(vl -> assertTrue(initialPositions.containsKey(vl.getId()))); @@ -43,22 +42,55 @@ private static void checkLayoutWithInitialPositions(Network network) { assertTrue(network.getVoltageLevelStream().anyMatch(filter.negate())); network.getVoltageLevelStream().filter(filter.negate()).forEach(vl -> assertFalse(initialPositions.containsKey(vl.getId()))); + checkAllInitialPositionsFixed(network, initialPositions); + checkOnlySomeInitialPositionsFixed(network, initialPositions); + } + + private static void checkAllInitialPositionsFixed(Network network, Map initialPositions) { // Perform a global layout with all the voltage levels in the network, - // giving initial (fixed) positions for some equipment + // giving fixed positions for some equipment NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); Layout layout = completeNetworkDiagram.getLayout(); - layout.setInitialNodePositions(initialPositions); - layout.setNodesWithFixedPosition(initialPositions.keySet()); - Map allPositions = layout.run(completeNetworkDiagram.buildGraph(), layoutParameters); + layout.setFixedNodePositions(initialPositions); + Map allPositions = layout.run(completeNetworkDiagram.buildGraph(), getLayoutParameters()); // Check positions of initial layout have been preserved in global layout for (Map.Entry l : initialPositions.entrySet()) { String equipmentId = l.getKey(); Point expected = l.getValue(); Point actual = allPositions.get(equipmentId); - assertNotNull(actual); - assertEquals(expected.getX(), actual.getX()); - assertEquals(expected.getY(), actual.getY()); + assertEquals(expected, actual); } } + + private static void checkOnlySomeInitialPositionsFixed(Network network, Map initialPositions) { + // Perform a global layout with all the voltage levels in the network, + // giving initial positions for some equipment, + // and fixing the position for only some equipment + NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); + Layout layout = completeNetworkDiagram.getLayout(); + layout.setInitialNodePositions(initialPositions); + // Only consider fixed the first one in the initial layout + Set fixedNodes = Set.of(initialPositions.keySet().iterator().next()); + layout.setNodesWithFixedPosition(fixedNodes); + Map allPositions1 = layout.run(completeNetworkDiagram.buildGraph(), getLayoutParameters()); + + // Check positions of initial layout have been preserved in global layout + for (Map.Entry l : initialPositions.entrySet()) { + String equipmentId = l.getKey(); + Point expected = l.getValue(); + Point actual = allPositions1.get(equipmentId); + if (fixedNodes.contains(equipmentId)) { + assertEquals(expected, actual); + } else { + // We expect that the nodes with initial position but that have not been fixed have been moved + assertNotEquals(expected, actual); + } + } + } + + private static LayoutParameters getLayoutParameters() { + return new LayoutParameters() + .setSpringRepulsionFactorForceLayout(0.2); + } } From 523ca72a91dd3946f04754d6cd4203010e5c7596 Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 9 Nov 2022 11:20:35 +0100 Subject: [PATCH 13/18] coverage Signed-off-by: Luma --- src/test/java/com/powsybl/nad/PointTest.java | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/test/java/com/powsybl/nad/PointTest.java diff --git a/src/test/java/com/powsybl/nad/PointTest.java b/src/test/java/com/powsybl/nad/PointTest.java new file mode 100644 index 00000000..94d1ce31 --- /dev/null +++ b/src/test/java/com/powsybl/nad/PointTest.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.nad; + +import com.powsybl.nad.model.Point; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * @author Luma Zamarreño + */ +class PointTest { + @Test + void testPoint() { + assertEquals(new Point(0, 0), new Point(0, 0)); + assertNotEquals(new Point(0, 0), new Point(0, 1)); + } +} From 9ede08bd551921fdb81546651339eb1afba4150e Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 9 Nov 2022 11:43:21 +0100 Subject: [PATCH 14/18] remove equals from point Signed-off-by: Luma --- .../java/com/powsybl/nad/model/Point.java | 17 ------------- src/test/java/com/powsybl/nad/PointTest.java | 24 ------------------- .../powsybl/nad/layout/FixedLayoutTest.java | 19 +++++++-------- .../LayoutWithInitialPositionsTest.java | 10 +++++--- 4 files changed, 16 insertions(+), 54 deletions(-) delete mode 100644 src/test/java/com/powsybl/nad/PointTest.java diff --git a/src/main/java/com/powsybl/nad/model/Point.java b/src/main/java/com/powsybl/nad/model/Point.java index b357e852..c1c8bf4b 100644 --- a/src/main/java/com/powsybl/nad/model/Point.java +++ b/src/main/java/com/powsybl/nad/model/Point.java @@ -25,23 +25,6 @@ public Point() { this(0, 0); } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Point point = (Point) o; - return x == point.x && y == point.y; - } - - @Override - public int hashCode() { - return Objects.hash(x, y); - } - public static Point createMiddlePoint(Point point1, Point point2) { Objects.requireNonNull(point1); Objects.requireNonNull(point2); diff --git a/src/test/java/com/powsybl/nad/PointTest.java b/src/test/java/com/powsybl/nad/PointTest.java deleted file mode 100644 index 94d1ce31..00000000 --- a/src/test/java/com/powsybl/nad/PointTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package com.powsybl.nad; - -import com.powsybl.nad.model.Point; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -/** - * @author Luma Zamarreño - */ -class PointTest { - @Test - void testPoint() { - assertEquals(new Point(0, 0), new Point(0, 0)); - assertNotEquals(new Point(0, 0), new Point(0, 1)); - } -} diff --git a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java index b11bd7d9..cc63eb42 100644 --- a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java +++ b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java @@ -17,21 +17,13 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author Luma Zamarreno */ class FixedLayoutTest { - @Test - void testPointsAreDifferent() { - Point p0 = new Point(1, 0); - Point p1 = new Point(2, 0); - assertNotEquals(p0, p1); - assertNotEquals(p0.hashCode(), p1.hashCode()); - } - @Test void testCurrentLimits() { Network network = NetworkTestFactory.createTwoVoltageLevels(); @@ -43,6 +35,13 @@ void testCurrentLimits() { Layout fixedLayout = new BasicFixedLayoutFactory().create(); fixedLayout.setInitialNodePositions(expected); Map actual = fixedLayout.run(graph, new LayoutParameters()); - assertEquals(expected, actual); + assertEquals(expected.keySet(), actual.keySet()); + expected.keySet().forEach(k -> { + Point pexpected = expected.get(k); + Point pactual = actual.get(k); + assertNotNull(pactual); + assertEquals(pexpected.getX(), pactual.getX()); + assertEquals(pexpected.getY(), pactual.getY()); + }); } } diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index ea8f67e7..ac7c09f9 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -59,7 +59,9 @@ private static void checkAllInitialPositionsFixed(Network network, Map Date: Wed, 9 Nov 2022 16:42:52 +0100 Subject: [PATCH 15/18] remove graph and layout from Diagram API Signed-off-by: Luma --- .../com/powsybl/nad/NetworkAreaDiagram.java | 20 +--- .../LayoutWithInitialPositionsTest.java | 93 +++++++++++++++---- 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java index ae877c3f..5d218d69 100644 --- a/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java +++ b/src/main/java/com/powsybl/nad/NetworkAreaDiagram.java @@ -13,7 +13,6 @@ import com.powsybl.nad.build.iidm.NetworkGraphBuilder; import com.powsybl.nad.build.iidm.VoltageLevelFilter; import com.powsybl.nad.layout.BasicForceLayoutFactory; -import com.powsybl.nad.layout.Layout; import com.powsybl.nad.layout.LayoutFactory; import com.powsybl.nad.layout.LayoutParameters; import com.powsybl.nad.model.Graph; @@ -62,6 +61,10 @@ public NetworkAreaDiagram(Network network, Predicate voltageLevelF this.voltageLevelFilter = Objects.requireNonNull(voltageLevelFilter); } + public Network getNetwork() { + return network; + } + public void draw(Path svgFile) { draw(svgFile, new SvgParameters()); } @@ -89,19 +92,6 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay draw(svgFile, svgParameters, layoutParameters, styleProvider, labelProvider, layoutFactory, new IntIdProvider()); } - public Graph buildGraph() { - return buildGraph(new IntIdProvider()); - } - - public Graph buildGraph(IdProvider idProvider) { - Objects.requireNonNull(idProvider); - return new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); - } - - public Layout getLayout() { - return new BasicForceLayoutFactory().create(); - } - public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters layoutParameters, StyleProvider styleProvider, LabelProvider labelProvider, LayoutFactory layoutFactory, IdProvider idProvider) { @@ -112,7 +102,7 @@ public void draw(Path svgFile, SvgParameters svgParameters, LayoutParameters lay Objects.requireNonNull(layoutFactory); Objects.requireNonNull(idProvider); - Graph graph = buildGraph(idProvider); + Graph graph = new NetworkGraphBuilder(network, voltageLevelFilter, idProvider).buildGraph(); layoutFactory.create().run(graph, layoutParameters); new SvgWriter(svgParameters, styleProvider, labelProvider).writeSvg(graph, svgFile); } diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index ac7c09f9..5b6d1021 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -8,11 +8,21 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.nad.AbstractTest; import com.powsybl.nad.NetworkAreaDiagram; import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; +import com.powsybl.nad.svg.LabelProvider; +import com.powsybl.nad.svg.StyleProvider; +import com.powsybl.nad.svg.SvgParameters; +import com.powsybl.nad.svg.iidm.DefaultLabelProvider; +import com.powsybl.nad.svg.iidm.NominalVoltageStyleProvider; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.StringWriter; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.function.Predicate; @@ -22,19 +32,38 @@ /** * @author Luma Zamarreno */ -class LayoutWithInitialPositionsTest { +class LayoutWithInitialPositionsTest extends AbstractTest { + + @BeforeEach + public void setup() { + setLayoutParameters(new LayoutParameters()); + setSvgParameters(new SvgParameters() + .setInsertNameDesc(true) + .setSvgWidthAndHeightAdded(true) + .setFixedWidth(800)); + } + + @Override + protected StyleProvider getStyleProvider(Network network) { + return new NominalVoltageStyleProvider(network); + } + + @Override + protected LabelProvider getLabelProvider(Network network) { + return new DefaultLabelProvider(network, getSvgParameters()); + } @Test void testDiamond() { checkLayoutWithInitialPositions(LayoutNetworkFactory.createDiamond()); } - private static void checkLayoutWithInitialPositions(Network network) { + private void checkLayoutWithInitialPositions(Network network) { Predicate filter = vl -> vl.getNominalV() >= 100; // Perform an initial layout with only a few voltage levels of the network NetworkAreaDiagram initialDiagram = new NetworkAreaDiagram(network, filter); - Map initialPositions = initialDiagram.getLayout().run(initialDiagram.buildGraph(), getLayoutParameters()); + Map initialPositions = layoutResult(initialDiagram); // Check initial points contains an entry for all voltage levels filtered network.getVoltageLevelStream().filter(filter).forEach(vl -> assertTrue(initialPositions.containsKey(vl.getId()))); @@ -46,13 +75,11 @@ private static void checkLayoutWithInitialPositions(Network network) { checkOnlySomeInitialPositionsFixed(network, initialPositions); } - private static void checkAllInitialPositionsFixed(Network network, Map initialPositions) { + private void checkAllInitialPositionsFixed(Network network, Map initialPositions) { // Perform a global layout with all the voltage levels in the network, // giving fixed positions for some equipment NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); - Layout layout = completeNetworkDiagram.getLayout(); - layout.setFixedNodePositions(initialPositions); - Map allPositions = layout.run(completeNetworkDiagram.buildGraph(), getLayoutParameters()); + Map allPositions = layoutResult(completeNetworkDiagram, initialPositions); // Check positions of initial layout have been preserved in global layout for (Map.Entry l : initialPositions.entrySet()) { @@ -65,23 +92,20 @@ private static void checkAllInitialPositionsFixed(Network network, Map initialPositions) { + private void checkOnlySomeInitialPositionsFixed(Network network, Map initialPositions) { // Perform a global layout with all the voltage levels in the network, // giving initial positions for some equipment, // and fixing the position for only some equipment NetworkAreaDiagram completeNetworkDiagram = new NetworkAreaDiagram(network, VoltageLevelFilter.NO_FILTER); - Layout layout = completeNetworkDiagram.getLayout(); - layout.setInitialNodePositions(initialPositions); // Only consider fixed the first one in the initial layout Set fixedNodes = Set.of(initialPositions.keySet().iterator().next()); - layout.setNodesWithFixedPosition(fixedNodes); - Map allPositions1 = layout.run(completeNetworkDiagram.buildGraph(), getLayoutParameters()); + Map allPositions = layoutResult(completeNetworkDiagram, initialPositions, fixedNodes); // Check positions of initial layout have been preserved in global layout for (Map.Entry l : initialPositions.entrySet()) { String equipmentId = l.getKey(); Point expected = l.getValue(); - Point actual = allPositions1.get(equipmentId); + Point actual = allPositions.get(equipmentId); assertNotNull(actual); if (fixedNodes.contains(equipmentId)) { assertEquals(expected.getX(), actual.getX()); @@ -93,8 +117,45 @@ private static void checkOnlySomeInitialPositionsFixed(Network network, Map positions; + } + + private Map layoutResult(NetworkAreaDiagram nad) { + return layoutResult(nad, Collections.emptyMap(), Collections.emptySet(), Collections.emptyMap()); + } + + private Map layoutResult(NetworkAreaDiagram nad, Map initialNodePositions, Set nodesWithFixedPositions) { + return layoutResult(nad, initialNodePositions, nodesWithFixedPositions, Collections.emptyMap()); + } + + private Map layoutResult(NetworkAreaDiagram nad, Map fixedNodePositions) { + return layoutResult(nad, Collections.emptyMap(), Collections.emptySet(), fixedNodePositions); + } + + private Map layoutResult(NetworkAreaDiagram nad, + Map initialNodePositions, + Set nodesWithFixedPositions, + Map fixedNodePositions + ) { + final LayoutResult layoutResult = new LayoutResult(); + LayoutFactory wrappedLayoutFactory = () -> new BasicForceLayout() { + @Override + public Map run(Graph graph, LayoutParameters layoutParameters) { + setInitialNodePositions(initialNodePositions); + setNodesWithFixedPosition(nodesWithFixedPositions); + setFixedNodePositions(fixedNodePositions); + layoutResult.positions = super.run(graph, layoutParameters); + return layoutResult.positions; + } + }; + StringWriter writer = new StringWriter(); + nad.draw(writer, + getSvgParameters(), + getLayoutParameters(), + getStyleProvider(nad.getNetwork()), + getLabelProvider(nad.getNetwork()), + wrappedLayoutFactory); + return layoutResult.positions; } } From bd1d51a3fe9922959bb4ea2918f5958a001ffbb8 Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 9 Nov 2022 17:45:28 +0100 Subject: [PATCH 16/18] only apply final scaling on nodes with position not fixed Signed-off-by: Luma --- .../java/com/powsybl/nad/layout/BasicForceLayout.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index b0f78011..cd050091 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -28,16 +29,18 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout()); setInitialPositions(forceLayout, graph); - forceLayout.setFixedNodes(getNodesWithFixedPosition().stream() + Set fixedNodes = getNodesWithFixedPosition().stream() .map(graph::getNode) .flatMap(Optional::stream) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()); + forceLayout.setFixedNodes(fixedNodes); forceLayout.execute(); jgraphtGraph.vertexSet().forEach(node -> { Vector p = forceLayout.getStablePosition(node); - node.setPosition(100 * p.getX(), 100 * p.getY()); + double scale = fixedNodes.contains(node) ? 1 : 100; + node.setPosition(scale * p.getX(), scale * p.getY()); }); if (!layoutParameters.isTextNodesForceLayout()) { From 3a20b739d18978aaa3a65004b9d505b073b451c4 Mon Sep 17 00:00:00 2001 From: Luma Date: Wed, 9 Nov 2022 17:47:26 +0100 Subject: [PATCH 17/18] Example of layout factory wrapper that considers positions Signed-off-by: Luma --- .../java/com/powsybl/nad/layout/Layout.java | 15 +++- .../LayoutWithInitialPositionsTest.java | 75 ++++++++++++++----- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/powsybl/nad/layout/Layout.java b/src/main/java/com/powsybl/nad/layout/Layout.java index 3f5bdb7d..982a7114 100644 --- a/src/main/java/com/powsybl/nad/layout/Layout.java +++ b/src/main/java/com/powsybl/nad/layout/Layout.java @@ -9,6 +9,7 @@ import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; +import java.util.Collections; import java.util.Map; import java.util.Set; @@ -18,16 +19,22 @@ public interface Layout { Map run(Graph graph, LayoutParameters layoutParameters); - void setInitialNodePositions(Map initialNodePositions); + default void setInitialNodePositions(Map initialNodePositions) { + } - void setNodesWithFixedPosition(Set nodesWithFixedPosition); + default void setNodesWithFixedPosition(Set nodesWithFixedPosition) { + } default void setFixedNodePositions(Map fixedNodePositions) { setInitialNodePositions(fixedNodePositions); setNodesWithFixedPosition(fixedNodePositions.keySet()); } - Map getInitialNodePositions(); + default Map getInitialNodePositions() { + return Collections.emptyMap(); + } - Set getNodesWithFixedPosition(); + default Set getNodesWithFixedPosition() { + return Collections.emptySet(); + } } diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index 5b6d1021..b30ed343 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -11,7 +11,6 @@ import com.powsybl.nad.AbstractTest; import com.powsybl.nad.NetworkAreaDiagram; import com.powsybl.nad.build.iidm.VoltageLevelFilter; -import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; import com.powsybl.nad.svg.LabelProvider; import com.powsybl.nad.svg.StyleProvider; @@ -117,10 +116,6 @@ private void checkOnlySomeInitialPositionsFixed(Network network, Map positions; - } - private Map layoutResult(NetworkAreaDiagram nad) { return layoutResult(nad, Collections.emptyMap(), Collections.emptySet(), Collections.emptyMap()); } @@ -137,25 +132,67 @@ private Map layoutResult(NetworkAreaDiagram nad, Map initialNodePositions, Set nodesWithFixedPositions, Map fixedNodePositions - ) { - final LayoutResult layoutResult = new LayoutResult(); - LayoutFactory wrappedLayoutFactory = () -> new BasicForceLayout() { - @Override - public Map run(Graph graph, LayoutParameters layoutParameters) { - setInitialNodePositions(initialNodePositions); - setNodesWithFixedPosition(nodesWithFixedPositions); - setFixedNodePositions(fixedNodePositions); - layoutResult.positions = super.run(graph, layoutParameters); - return layoutResult.positions; - } - }; + ) { + LayoutFactory delegateLayoutFactory = new BasicForceLayoutFactory(); + PositionsLayoutFactory positionsLayoutFactory = new PositionsLayoutFactory( + delegateLayoutFactory, + initialNodePositions, + nodesWithFixedPositions, + fixedNodePositions); StringWriter writer = new StringWriter(); nad.draw(writer, getSvgParameters(), getLayoutParameters(), getStyleProvider(nad.getNetwork()), getLabelProvider(nad.getNetwork()), - wrappedLayoutFactory); - return layoutResult.positions; + positionsLayoutFactory); + return positionsLayoutFactory.getLayoutResult().positions; + } + + static class PositionsLayoutFactory implements LayoutFactory { + static class LayoutResult { + Map positions; + } + + private final LayoutFactory delegateLayoutFactory; + private final LayoutResult layoutResult = new LayoutResult(); + private final Map initialNodePositions; + private final Set nodesWithFixedPositions; + private final Map fixedNodePositions; + + PositionsLayoutFactory(LayoutFactory delegateLayoutFactory, + Map initialNodePositions, + Set nodesWithFixedPositions, + Map fixedNodePositions + ) { + this.delegateLayoutFactory = delegateLayoutFactory; + this.initialNodePositions = initialNodePositions; + this.nodesWithFixedPositions = nodesWithFixedPositions; + this.fixedNodePositions = fixedNodePositions; + } + + public LayoutResult getLayoutResult() { + return layoutResult; + } + + @Override + public Layout create() { + final Layout delegateLayout = delegateLayoutFactory.create(); + return (graph, layoutParameters) -> { + if (!initialNodePositions.isEmpty()) { + delegateLayout.setInitialNodePositions(initialNodePositions); + } + if (!nodesWithFixedPositions.isEmpty()) { + delegateLayout.setNodesWithFixedPosition(nodesWithFixedPositions); + } + // only if not empty, + // setting nodes with fixed node positions will invalidate previous nodes with fixed positions + if (!fixedNodePositions.isEmpty()) { + delegateLayout.setFixedNodePositions(fixedNodePositions); + } + layoutResult.positions = delegateLayout.run(graph, layoutParameters); + return layoutResult.positions; + }; + } } } From 8b5ffd31742e9f709134244f5aaef9d3c027ca58 Mon Sep 17 00:00:00 2001 From: Luma Date: Mon, 14 Nov 2022 11:49:16 +0100 Subject: [PATCH 18/18] changes after review Signed-off-by: Luma --- .../powsybl/nad/layout/AbstractLayout.java | 15 +++--- .../nad/layout/BasicFixedLayoutFactory.java | 17 ++++++- .../powsybl/nad/layout/BasicForceLayout.java | 11 +++-- .../java/com/powsybl/nad/layout/Layout.java | 22 ++------- .../java/com/powsybl/nad/model/Graph.java | 9 ++++ .../powsybl/nad/layout/FixedLayoutTest.java | 7 +-- .../LayoutWithInitialPositionsTest.java | 47 ++++++++++++++----- 7 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java index b94340c4..cee1c330 100644 --- a/src/main/java/com/powsybl/nad/layout/AbstractLayout.java +++ b/src/main/java/com/powsybl/nad/layout/AbstractLayout.java @@ -4,7 +4,6 @@ import org.jgrapht.alg.util.Pair; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class AbstractLayout implements Layout { @@ -13,7 +12,7 @@ public abstract class AbstractLayout implements Layout { private Set nodesWithFixedPosition = Collections.emptySet(); @Override - public Map run(Graph graph, LayoutParameters layoutParameters) { + public void run(Graph graph, LayoutParameters layoutParameters) { Objects.requireNonNull(graph); Objects.requireNonNull(layoutParameters); @@ -22,13 +21,6 @@ public Map run(Graph graph, LayoutParameters layoutParameters) { edgesLayout(graph, layoutParameters); computeSize(graph); - - return graph.getVoltageLevelNodesStream() - .filter(VoltageLevelNode::isVisible) - .collect(Collectors.toMap( - VoltageLevelNode::getEquipmentId, - VoltageLevelNode::getPosition - )); } @Override @@ -52,6 +44,11 @@ public Set getNodesWithFixedPosition() { return nodesWithFixedPosition; } + public void setFixedNodePositions(Map fixedNodePositions) { + setInitialNodePositions(fixedNodePositions); + setNodesWithFixedPosition(fixedNodePositions.keySet()); + } + protected abstract void nodesLayout(Graph graph, LayoutParameters layoutParameters); protected void busNodesLayout(Graph graph, LayoutParameters ignoredLayoutParameters) { diff --git a/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java b/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java index d26ffa08..341e28b9 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java +++ b/src/main/java/com/powsybl/nad/layout/BasicFixedLayoutFactory.java @@ -6,12 +6,27 @@ */ package com.powsybl.nad.layout; +import com.powsybl.nad.model.Point; + +import java.util.Map; +import java.util.Objects; + /** * @author Luma Zamarreño */ public class BasicFixedLayoutFactory implements LayoutFactory { + + private final Map fixedPositions; + + public BasicFixedLayoutFactory(Map fixedPositions) { + Objects.requireNonNull(fixedPositions); + this.fixedPositions = fixedPositions; + } + @Override public Layout create() { - return new BasicFixedLayout(); + AbstractLayout layout = new BasicFixedLayout(); + layout.setFixedNodePositions(fixedPositions); + return layout; } } diff --git a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java index cd050091..a6bcbd16 100644 --- a/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java +++ b/src/main/java/com/powsybl/nad/layout/BasicForceLayout.java @@ -22,6 +22,8 @@ */ public class BasicForceLayout extends AbstractLayout { + private static final int SCALE = 100; + @Override protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { org.jgrapht.Graph jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout()); @@ -39,8 +41,7 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) { jgraphtGraph.vertexSet().forEach(node -> { Vector p = forceLayout.getStablePosition(node); - double scale = fixedNodes.contains(node) ? 1 : 100; - node.setPosition(scale * p.getX(), scale * p.getY()); + node.setPosition(SCALE * p.getX(), SCALE * p.getY()); }); if (!layoutParameters.isTextNodesForceLayout()) { @@ -54,9 +55,9 @@ private void setInitialPositions(ForceLayout forceLayout, Graph grap .filter(nodePosition -> graph.getNode(nodePosition.getKey()).isPresent()) .collect(Collectors.toMap( nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(), - nodePosition -> new com.powsybl.forcelayout.Point(nodePosition.getValue().getX(), nodePosition.getValue().getY()), - // If same node has two points, keep the first one considered - (point1, point2) -> point1 + nodePosition -> new com.powsybl.forcelayout.Point( + nodePosition.getValue().getX() / SCALE, + nodePosition.getValue().getY() / SCALE) )); forceLayout.setInitialPoints(initialPoints); } diff --git a/src/main/java/com/powsybl/nad/layout/Layout.java b/src/main/java/com/powsybl/nad/layout/Layout.java index 982a7114..fd86e68e 100644 --- a/src/main/java/com/powsybl/nad/layout/Layout.java +++ b/src/main/java/com/powsybl/nad/layout/Layout.java @@ -9,7 +9,6 @@ import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; -import java.util.Collections; import java.util.Map; import java.util.Set; @@ -17,24 +16,13 @@ * @author Florian Dupuy */ public interface Layout { - Map run(Graph graph, LayoutParameters layoutParameters); + void run(Graph graph, LayoutParameters layoutParameters); - default void setInitialNodePositions(Map initialNodePositions) { - } + void setInitialNodePositions(Map initialNodePositions); - default void setNodesWithFixedPosition(Set nodesWithFixedPosition) { - } + void setNodesWithFixedPosition(Set nodesWithFixedPosition); - default void setFixedNodePositions(Map fixedNodePositions) { - setInitialNodePositions(fixedNodePositions); - setNodesWithFixedPosition(fixedNodePositions.keySet()); - } + Map getInitialNodePositions(); - default Map getInitialNodePositions() { - return Collections.emptyMap(); - } - - default Set getNodesWithFixedPosition() { - return Collections.emptySet(); - } + Set getNodesWithFixedPosition(); } diff --git a/src/main/java/com/powsybl/nad/model/Graph.java b/src/main/java/com/powsybl/nad/model/Graph.java index 7f91ab8f..100a6985 100644 --- a/src/main/java/com/powsybl/nad/model/Graph.java +++ b/src/main/java/com/powsybl/nad/model/Graph.java @@ -313,4 +313,13 @@ public boolean containsNode(String equipmentId) { public boolean isLoop(Edge edge) { return getNode1(edge) == getNode2(edge); } + + public Map getNodePositions() { + return getVoltageLevelNodesStream() + .filter(VoltageLevelNode::isVisible) + .collect(Collectors.toMap( + VoltageLevelNode::getEquipmentId, + VoltageLevelNode::getPosition + )); + } } diff --git a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java index cc63eb42..ff1d1ecb 100644 --- a/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java +++ b/src/test/java/com/powsybl/nad/layout/FixedLayoutTest.java @@ -32,9 +32,10 @@ void testCurrentLimits() { "vl1", new Point(1, 0), "vl2", new Point(2, 1)); Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); - Layout fixedLayout = new BasicFixedLayoutFactory().create(); - fixedLayout.setInitialNodePositions(expected); - Map actual = fixedLayout.run(graph, new LayoutParameters()); + Layout fixedLayout = new BasicFixedLayoutFactory(expected).create(); + fixedLayout.run(graph, new LayoutParameters()); + Map actual = graph.getNodePositions(); + assertEquals(expected.keySet(), actual.keySet()); expected.keySet().forEach(k -> { Point pexpected = expected.get(k); diff --git a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java index b30ed343..4c363054 100644 --- a/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java +++ b/src/test/java/com/powsybl/nad/layout/LayoutWithInitialPositionsTest.java @@ -6,11 +6,13 @@ */ package com.powsybl.nad.layout; +import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.nad.AbstractTest; import com.powsybl.nad.NetworkAreaDiagram; import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; import com.powsybl.nad.svg.LabelProvider; import com.powsybl.nad.svg.StyleProvider; @@ -178,20 +180,43 @@ public LayoutResult getLayoutResult() { @Override public Layout create() { final Layout delegateLayout = delegateLayoutFactory.create(); - return (graph, layoutParameters) -> { - if (!initialNodePositions.isEmpty()) { - delegateLayout.setInitialNodePositions(initialNodePositions); + return new Layout() { + @Override + public void run(Graph graph, LayoutParameters layoutParameters) { + if (!initialNodePositions.isEmpty()) { + delegateLayout.setInitialNodePositions(initialNodePositions); + } + if (!nodesWithFixedPositions.isEmpty()) { + delegateLayout.setNodesWithFixedPosition(nodesWithFixedPositions); + } + // only if not empty, + // setting nodes with fixed node positions will invalidate previous nodes with fixed positions + if (!fixedNodePositions.isEmpty() && delegateLayout instanceof AbstractLayout) { + ((AbstractLayout) delegateLayout).setFixedNodePositions(fixedNodePositions); + } + delegateLayout.run(graph, layoutParameters); + layoutResult.positions = graph.getNodePositions(); } - if (!nodesWithFixedPositions.isEmpty()) { - delegateLayout.setNodesWithFixedPosition(nodesWithFixedPositions); + + @Override + public void setInitialNodePositions(Map initialNodePositions) { + throw new PowsyblException("not implemented"); + } + + @Override + public void setNodesWithFixedPosition(Set nodesWithFixedPosition) { + throw new PowsyblException("not implemented"); } - // only if not empty, - // setting nodes with fixed node positions will invalidate previous nodes with fixed positions - if (!fixedNodePositions.isEmpty()) { - delegateLayout.setFixedNodePositions(fixedNodePositions); + + @Override + public Map getInitialNodePositions() { + throw new PowsyblException("not implemented"); + } + + @Override + public Set getNodesWithFixedPosition() { + throw new PowsyblException("not implemented"); } - layoutResult.positions = delegateLayout.run(graph, layoutParameters); - return layoutResult.positions; }; } }