CustomStyleProvider.java
/**
* Copyright (c) 2025, 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/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.nad.svg;
import com.powsybl.nad.model.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Enables the customization of the style of NAD elements: Bus nodes, branch-edges and three-winding-transformers edges.
*
* <p>
* NAD elements'style data is defined in the CustomStyleProvider constructor's map parameters.
*
* <p>
* The busNodesStyles map is indexed by the bus ID and defines the style for the bus nodes.
* In the map, a node style is declared in a BusNodeStyles record: fill, edge and edgeWidth are the fill color, the edge color and the edge size for the node, respectively.
*
* <p>
* The edgesStyles map is indexed by the branch ID and defines the style for the edges.
* In the map, the edge style is declared in a EdgeStyles record: edge1, width1 and dash1 are the color, the size and a dash pattern for the first half edge, respectively.
* Edge2, width2 and dash2 are the color, the size and a dash pattern for the second half edge.
*
* <p>
* The threeWtsStyles map is index by the three-winding-transformer ID and defines the style for the transformer���s legs.
* In the map, the style is declared in a ThreeWtStyles record: edge1, width1, dash1, edge2, width2 and dash2, edge3, width23 and dash3,
* are the color, the size and a dash pattern for the three legs of the transformer.
*
* <p>
* Note that the edge size is a string, it can be specified in pixel (e.g, 4px).
* A dash pattern is a string with a sequence of comma and/or white space separated lengths and percentages, that specify the lengths of alternating dashes and gaps in the edge.
* Elements that do not have a style specified in the parameters will be displayed with a default style.
*
* @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
*/
public class CustomStyleProvider extends AbstractStyleProvider {
final Map<String, BusNodeStyles> busNodesStyles;
final Map<String, EdgeStyles> edgesStyles;
final Map<String, ThreeWtStyles> threeWtsStyles;
public record BusNodeStyles(String fill, String edge, String edgeWidth) {
}
public record EdgeStyles(String edge1, String width1, String dash1, String edge2, String width2,
String dash2) {
}
public record ThreeWtStyles(String edge1, String width1, String dash1, String edge2, String width2,
String dash2, String edge3, String width3, String dash3) {
}
private record EdgeStyle(String stroke, String strokeWidth, String dash) {
}
public CustomStyleProvider(Map<String, BusNodeStyles> busNodesStyles, Map<String, EdgeStyles> edgesStyles,
Map<String, ThreeWtStyles> threeWtsStyles) {
this.busNodesStyles = Objects.requireNonNull(busNodesStyles);
this.edgesStyles = Objects.requireNonNull(edgesStyles);
this.threeWtsStyles = Objects.requireNonNull(threeWtsStyles);
}
@Override
public List<String> getCssFilenames() {
return Collections.singletonList("customStyle.css");
}
@Override
public String getBusNodeStyle(BusNode busNode) {
BusNodeStyles style = busNodesStyles.get(busNode.getEquipmentId());
if (style != null) {
List<String> parts = new ArrayList<>();
if (style.fill() != null) {
parts.add(String.format("background:%s; fill:%s;", style.fill(), style.fill()));
}
if (style.edge() != null) {
parts.add(String.format("stroke:%s;", style.edge()));
parts.add(String.format("border: solid %s %s;", style.edge(), style.edgeWidth() != null ? style.edgeWidth() : "1px"));
}
if (style.edgeWidth() != null) {
parts.add(String.format("stroke-width:%s;", style.edgeWidth()));
}
return parts.isEmpty() ? null : String.join(" ", parts);
}
return null;
}
private EdgeStyle getEdgeStyle(EdgeStyles styles, BranchEdge.Side side) {
return (side == BranchEdge.Side.ONE)
? new EdgeStyle(styles.edge1(), styles.width1(), styles.dash1())
: new EdgeStyle(styles.edge2(), styles.width2(), styles.dash2());
}
private String formatEdgeStyle(EdgeStyle lineStyle) {
return Stream.of(
Optional.ofNullable(lineStyle.stroke()).map(stroke -> String.format("stroke:%s;", stroke)).orElse(null),
Optional.ofNullable(lineStyle.strokeWidth()).map(width -> String.format("stroke-width:%s;", width)).orElse(null),
Optional.ofNullable(lineStyle.dash()).map(dash -> String.format("stroke-dasharray:%s;", dash)).orElse(null)
)
.filter(Objects::nonNull)
.collect(Collectors.joining(" "));
}
private EdgeStyle getThreeWtStyle(ThreeWtStyles styles, ThreeWtEdge.Side side) {
return switch (side) {
case ONE -> new EdgeStyle(styles.edge1(), styles.width1(), styles.dash1());
case TWO -> new EdgeStyle(styles.edge2(), styles.width2(), styles.dash2());
case THREE -> new EdgeStyle(styles.edge3(), styles.width3(), styles.dash3());
};
}
@Override
public String getSideEdgeStyle(BranchEdge edge, BranchEdge.Side side) {
return Optional.ofNullable(edgesStyles.get(edge.getEquipmentId()))
.map(styles -> formatEdgeStyle(getEdgeStyle(styles, side)))
.orElse(null);
}
@Override
public String getThreeWtEdgeStyle(ThreeWtEdge threeWtEdge) {
ThreeWtEdge.Side side = threeWtEdge.getSide();
return Optional.ofNullable(threeWtsStyles.get(threeWtEdge.getEquipmentId()))
.map(styles -> formatEdgeStyle(getThreeWtStyle(styles, side)))
.orElse(null);
}
@Override
public List<String> getEdgeInfoStyleClasses(EdgeInfo info) {
List<String> styles = new LinkedList<>();
info.getDirection().ifPresent(direction -> styles.add(
CLASSES_PREFIX + (direction == EdgeInfo.Direction.OUT ? "state-out" : "state-in")));
return styles;
}
@Override
public List<String> getHighlightNodeStyleClasses(Node node) {
return List.of();
}
@Override
public List<String> getHighlightSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side) {
return List.of();
}
@Override
public List<String> getHighlightThreeWtEdgStyleClasses(ThreeWtEdge edge) {
return List.of();
}
@Override
protected boolean isDisconnected(ThreeWtEdge threeWtEdge) {
return false;
}
@Override
protected boolean isDisconnected(BranchEdge branchEdge) {
return false;
}
@Override
protected boolean isDisconnected(BranchEdge edge, BranchEdge.Side side) {
return false;
}
@Override
protected Optional<String> getBaseVoltageStyle(Edge edge) {
return Optional.empty();
}
@Override
protected Optional<String> getBaseVoltageStyle(BranchEdge edge, BranchEdge.Side side) {
return Optional.empty();
}
@Override
protected Optional<String> getBaseVoltageStyle(ThreeWtEdge threeWtEdge) {
return Optional.empty();
}
}