Skip to content
This repository has been archived by the owner on Nov 18, 2022. It is now read-only.

Edge info labels on the edge #57

Merged
merged 13 commits into from
Mar 1, 2022
18 changes: 9 additions & 9 deletions src/main/java/com/powsybl/nad/svg/EdgeInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ public class EdgeInfo {

private final String infoType;
private final Direction arrowDirection;
private final String leftLabel;
private final String rightLabel;
private final String internalLabel;
private final String externalLabel;

public EdgeInfo(String infoType, Direction arrowDirection, String leftLabel, String rightLabel) {
public EdgeInfo(String infoType, Direction arrowDirection, String internalLabel, String externalLabel) {
this.infoType = infoType;
this.arrowDirection = arrowDirection;
this.leftLabel = leftLabel;
this.rightLabel = rightLabel;
this.internalLabel = internalLabel;
this.externalLabel = externalLabel;
}

public EdgeInfo(String infoType, double value) {
Expand All @@ -39,12 +39,12 @@ public Optional<Direction> getDirection() {
return Optional.ofNullable(arrowDirection);
}

public Optional<String> getLeftLabel() {
return Optional.ofNullable(leftLabel);
public Optional<String> getInternalLabel() {
return Optional.ofNullable(internalLabel);
}

public Optional<String> getRightLabel() {
return Optional.ofNullable(rightLabel);
public Optional<String> getExternalLabel() {
return Optional.ofNullable(externalLabel);
}

public enum Direction {
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/com/powsybl/nad/svg/SvgParameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class SvgParameters {
private int fixedWidth = -1;
private int fixedHeight = -1;
private double arrowShift = 0.3;
private double arrowLabelShift = 0.12;
private double arrowLabelShift = 0.19;
private double converterStationWidth = 0.6;
private double voltageLevelCircleRadius = 0.3;
private double fictitiousVoltageLevelCircleRadius = 0.15;
Expand All @@ -35,6 +35,7 @@ public class SvgParameters {
private double loopEdgesAperture = Math.toRadians(60);
private double loopControlDistance = 0.4;
private boolean textNodeBackground = true;
private boolean edgeInfoAlongEdge = true;

public enum CssLocation {
INSERTED_IN_SVG, EXTERNAL_IMPORTED, EXTERNAL_NO_IMPORT
Expand Down Expand Up @@ -243,4 +244,13 @@ public SvgParameters setTextNodeBackground(boolean textNodeBackground) {
this.textNodeBackground = textNodeBackground;
return this;
}

public boolean isEdgeInfoAlongEdge() {
return edgeInfoAlongEdge;
}

public SvgParameters setEdgeInfoAlongEdge(boolean edgeInfoAlongEdge) {
this.edgeInfoAlongEdge = edgeInfoAlongEdge;
return this;
}
}
47 changes: 36 additions & 11 deletions src/main/java/com/powsybl/nad/svg/SvgWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -298,18 +298,17 @@ private void drawEdgeInfo(XMLStreamWriter writer, List<EdgeInfo> edgeInfos, Poin
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.EDGE_INFOS_CLASS);
writer.writeAttribute(TRANSFORM_ATTRIBUTE, getTranslateString(infoCenter));
double textAngle = Math.sin(edgeAngle) > 0 ? -Math.PI / 2 + edgeAngle : Math.PI / 2 + edgeAngle;
for (EdgeInfo info : edgeInfos) {
writer.writeStartElement(GROUP_ELEMENT_NAME);
addStylesIfAny(writer, styleProvider.getEdgeInfoStyles(info));
drawInAndOutArrows(writer, edgeAngle);
Optional<String> rightLabel = info.getRightLabel();
if (rightLabel.isPresent()) {
drawLabel(writer, rightLabel.get(), svgParameters.getArrowLabelShift(), textAngle, "dominant-baseline:middle");
Optional<String> externalLabel = info.getExternalLabel();
if (externalLabel.isPresent()) {
drawLabel(writer, externalLabel.get(), edgeAngle, true);
}
Optional<String> leftLabel = info.getLeftLabel();
if (leftLabel.isPresent()) {
drawLabel(writer, leftLabel.get(), -svgParameters.getArrowLabelShift(), textAngle, "dominant-baseline:middle; text-anchor:end");
Optional<String> internalLabel = info.getInternalLabel();
if (internalLabel.isPresent()) {
drawLabel(writer, internalLabel.get(), edgeAngle, false);
}
writer.writeEndElement();
}
Expand All @@ -329,11 +328,37 @@ private void drawInAndOutArrows(XMLStreamWriter writer, double edgeAngle) throws
writer.writeEndElement();
}

private void drawLabel(XMLStreamWriter writer, String label, double labelShiftX, double angle, String style) throws XMLStreamException {
private void drawLabel(XMLStreamWriter writer, String label, double edgeAngle, boolean externalLabel) throws XMLStreamException {
if (svgParameters.isEdgeInfoAlongEdge()) {
drawLabelAlongEdge(writer, label, edgeAngle, externalLabel);
} else {
drawLabelPerpendicularToEdge(writer, label, edgeAngle, externalLabel);
}
}

private void drawLabelAlongEdge(XMLStreamWriter writer, String label, double edgeAngle, boolean externalLabel) throws XMLStreamException {
boolean textFlipped = Math.cos(edgeAngle) < 0;
String style = externalLabel == textFlipped ? "text-anchor:end" : null;
double textAngle = textFlipped ? edgeAngle - Math.PI : edgeAngle;
double shift = svgParameters.getArrowLabelShift() * (externalLabel ? 1 : -1);
drawLabel(writer, label, textFlipped ? -shift : shift, style, textAngle, X_ATTRIBUTE);
}

private void drawLabelPerpendicularToEdge(XMLStreamWriter writer, String label, double edgeAngle, boolean externalLabel) throws XMLStreamException {
boolean textFlipped = Math.sin(edgeAngle) > 0;
double textAngle = textFlipped ? -Math.PI / 2 + edgeAngle : Math.PI / 2 + edgeAngle;
double shift = svgParameters.getArrowLabelShift();
double shiftAdjusted = externalLabel == textFlipped ? shift * 1.15 : -shift; // to have a nice compact rendering, shift needs to be adjusted, because of dominant-baseline:middle (text is expected to be a number, hence not below the line)
drawLabel(writer, label, shiftAdjusted, "text-anchor:middle", textAngle, Y_ATTRIBUTE);
}

private void drawLabel(XMLStreamWriter writer, String label, double shift, String style, double textAngle, String shiftAxis) throws XMLStreamException {
writer.writeStartElement(TEXT_ELEMENT_NAME);
writer.writeAttribute(TRANSFORM_ATTRIBUTE, getRotateString(angle));
writer.writeAttribute(X_ATTRIBUTE, getFormattedValue(labelShiftX));
writer.writeAttribute(STYLE_ELEMENT_NAME, style);
writer.writeAttribute(TRANSFORM_ATTRIBUTE, getRotateString(textAngle));
writer.writeAttribute(shiftAxis, getFormattedValue(shift));
if (style != null) {
writer.writeAttribute(STYLE_ELEMENT_NAME, style);
}
writer.writeCharacters(label);
writer.writeEndElement();
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/resources/nominalStyle.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.nad-branch-edges circle {stroke: var(--nad-vl-color, lightgrey); stroke-width: 0.05; fill: white}
.nad-3wt-edges polyline {stroke: var(--nad-vl-color, lightgrey); stroke-width: 0.05; fill: none}
.nad-text-edges {stroke: black; stroke-width: 0.02; stroke-dasharray: .03,.05}
.nad-disconnected {stroke-dasharray: .1,.1}
.nad-branch-edges .nad-disconnected polyline {stroke-dasharray: .1,.1}
.nad-vl-nodes circle {fill: var(--nad-vl-color, lightblue); stroke-width: 0.05; stroke: white}
.nad-vl-nodes circle.nad-unknown-busnode {stroke: lightgrey; stroke-width: 0.05; stroke-dasharray: .05,.05; fill: none}
.nad-vl-nodes path {fill: var(--nad-vl-color, lightblue); stroke-width: 0.05; stroke: white; stroke-linejoin:round;}
Expand All @@ -17,9 +17,9 @@
.nad-reactive path {stroke: none; fill: #0277bd}
.nad-text-background {flood-color: #90a4aeaa}
.nad-text-nodes {font: 0.25px "Verdana"; fill: black}
.nad-edge-infos {font: 0.2px "Verdana"}
.nad-edge-infos .nad-state-in {fill: #b71c1c}
.nad-edge-infos .nad-state-out {fill: #2e7d32}
.nad-edge-infos text {font: 0.2px "Verdana"; dominant-baseline:middle; stroke: #FFFFFFAA; stroke-width: 0.1; stroke-linejoin:round; paint-order: stroke}
.nad-edge-infos .nad-state-in text {fill: #b71c1c}
.nad-edge-infos .nad-state-out text {fill: #2e7d32}
.nad-vl0to30 {--nad-vl-color: #AFB42B}
.nad-vl30to50 {--nad-vl-color: #EF9A9A}
.nad-vl50to70 {--nad-vl-color: #9C27B0}
Expand Down
9 changes: 4 additions & 5 deletions src/main/resources/topologicalStyle.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.nad-branch-edges circle {stroke: var(--nad-vl-color, lightgrey); stroke-width: 0.05; fill: white}
.nad-3wt-edges polyline {stroke: var(--nad-vl-color, lightgrey); stroke-width: 0.05; fill: none}
.nad-text-edges {stroke: black; stroke-width: 0.02; stroke-dasharray: .03,.05}
.nad-branch-edges .nad-disconnected {stroke-dasharray: .1,.1}
.nad-branch-edges .nad-disconnected polyline {stroke-dasharray: .1,.1}
.nad-vl-nodes circle {fill: var(--nad-vl-color, lightgrey); stroke-width: 0.05; stroke: white}
.nad-vl-nodes circle.nad-unknown-busnode {stroke: var(--nad-vl-color, #808080); stroke-width: 0.05; stroke-dasharray: .05,.05; fill: none}
.nad-vl-nodes path {fill: var(--nad-vl-color, lightgrey); stroke-width: 0.05; stroke: white; stroke-linejoin:round;}
Expand All @@ -17,9 +17,9 @@
.nad-reactive path {stroke: none; fill: #0277bd}
.nad-text-background {flood-color: #90a4aeaa}
.nad-text-nodes {font: 0.25px "Verdana"; fill: black}
.nad-edge-infos {font: 0.2px "Verdana"}
.nad-edge-infos .nad-state-in {fill: #b71c1c}
.nad-edge-infos .nad-state-out {fill: #2e7d32}
.nad-edge-infos text {font: 0.2px "Verdana"; dominant-baseline:middle; stroke: #FFFFFFAA; stroke-width: 0.1; stroke-linejoin:round; paint-order: stroke}
.nad-edge-infos .nad-state-in text {fill: #b71c1c}
.nad-edge-infos .nad-state-out text {fill: #2e7d32}
.nad-disconnected {--nad-vl-color: #808080}
.nad-vl0to30-0 {--nad-vl-color: #afb42b}
.nad-vl0to30-1 {--nad-vl-color: #e6ee9c}
Expand Down Expand Up @@ -91,4 +91,3 @@
.nad-vl300to500-7 {--nad-vl-color: #ef9a9a}
.nad-vl300to500-8 {--nad-vl-color: #f44336}
.nad-vl300to500-9 {--nad-vl-color: #c62828}

20 changes: 18 additions & 2 deletions src/test/java/com/powsybl/nad/AbstractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public abstract class AbstractTest {
protected boolean debugSvg = false;
protected boolean overrideTestReferences = false;

protected abstract LayoutParameters getLayoutParameters();
private SvgParameters svgParameters;

protected abstract SvgParameters getSvgParameters();
private LayoutParameters layoutParameters;

protected abstract StyleProvider getStyleProvider(Network network);

Expand Down Expand Up @@ -92,4 +92,20 @@ private static String normalizeLineSeparator(String str) {
return str.replace("\r\n", "\n")
.replace("\r", "\n");
}

protected LayoutParameters getLayoutParameters() {
return layoutParameters;
}

protected SvgParameters getSvgParameters() {
return svgParameters;
}

protected void setLayoutParameters(LayoutParameters layoutParameters) {
this.layoutParameters = layoutParameters;
}

protected void setSvgParameters(SvgParameters svgParameters) {
this.svgParameters = svgParameters;
}
}
98 changes: 98 additions & 0 deletions src/test/java/com/powsybl/nad/svg/EdgeInfoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* 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.svg;

import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory;
import com.powsybl.nad.AbstractTest;
import com.powsybl.nad.layout.LayoutParameters;
import com.powsybl.nad.model.BranchEdge;
import com.powsybl.nad.model.Graph;
import com.powsybl.nad.model.ThreeWtEdge;
import com.powsybl.nad.svg.iidm.NominalVoltageStyleProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* @author Florian Dupuy <florian.dupuy at rte-france.com>
*/
class EdgeInfoTest extends AbstractTest {

private String internalLabel;
private String externalLabel;

@BeforeEach
public void setup() {
setLayoutParameters(new LayoutParameters());
setSvgParameters(new SvgParameters()
.setSvgWidthAndHeightAdded(true)
.setFixedWidth(800));
}

@Override
protected StyleProvider getStyleProvider(Network network) {
return new NominalVoltageStyleProvider(network);
}

@Override
protected LabelProvider getLabelProvider(Network network) {
return new LabelProvider() {
@Override
public List<EdgeInfo> getEdgeInfos(Graph graph, BranchEdge edge, BranchEdge.Side side) {
return Collections.singletonList(new EdgeInfo("test", EdgeInfo.Direction.OUT, internalLabel, externalLabel));
}

@Override
public List<EdgeInfo> getEdgeInfos(Graph graph, ThreeWtEdge edge) {
return Collections.singletonList(new EdgeInfo("test", EdgeInfo.Direction.IN, internalLabel, externalLabel));
}

@Override
public String getArrowPathDIn() { // larger arrow
return "M-0.2 -0.1 H0.2 L0 0.1z";
}

@Override
public String getArrowPathDOut() { // thinner arrow
return "M-0.05 0.1 H0.05 L0 -0.1z";
}
};
}

@Test
void testMissingLabels() {
Network network = NetworkTestFactory.createTwoVoltageLevels();
getSvgParameters().setArrowShift(0.1);
assertEquals(toString("/edge_info_missing_label.svg"), generateSvgString(network, "/edge_info_missing_label.svg"));
}

@Test
void testPerpendicularLabels() {
Network network = NetworkTestFactory.createTwoVoltageLevels();
internalLabel = "int";
externalLabel = "ext";
getSvgParameters().setEdgeInfoAlongEdge(false)
.setArrowShift(0.5)
.setArrowLabelShift(0.25);
assertEquals(toString("/edge_info_perpendicular_label.svg"), generateSvgString(network, "/edge_info_perpendicular_label.svg"));
}

@Test
void testParallelLabels() {
Network network = ThreeWindingsTransformerNetworkFactory.create();
internalLabel = "243";
externalLabel = "145";
getSvgParameters().setArrowShift(0.6)
.setArrowLabelShift(0.2);
assertEquals(toString("/edge_info_double_labels.svg"), generateSvgString(network, "/edge_info_double_labels.svg"));
}
}
Loading