DefaultSVGWriter.java
/**
* Copyright (c) 2019, 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.sld.svg;
import com.powsybl.sld.layout.LayoutParameters;
import com.powsybl.sld.library.AnchorPoint;
import com.powsybl.sld.library.Component;
import com.powsybl.sld.library.ComponentLibrary;
import com.powsybl.sld.library.ComponentSize;
import com.powsybl.sld.model.cells.Cell;
import com.powsybl.sld.model.coordinate.Direction;
import com.powsybl.sld.model.coordinate.Orientation;
import com.powsybl.sld.model.coordinate.Point;
import com.powsybl.sld.model.coordinate.Side;
import com.powsybl.sld.model.graphs.*;
import com.powsybl.sld.model.nodes.Node;
import com.powsybl.sld.model.nodes.*;
import com.powsybl.sld.model.nodes.Node.NodeType;
import com.powsybl.sld.model.nodes.feeders.FeederWithSides;
import com.powsybl.sld.svg.GraphMetadata.FeederInfoMetadata;
import com.powsybl.sld.svg.styles.StyleClassConstants;
import com.powsybl.sld.svg.styles.StyleProvider;
import com.powsybl.sld.util.DomUtil;
import com.powsybl.sld.util.IdUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Precision;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static com.powsybl.sld.library.ComponentTypeName.*;
import static com.powsybl.sld.model.coordinate.Direction.*;
import static com.powsybl.sld.util.IdUtil.escapeClassName;
import static com.powsybl.sld.util.IdUtil.escapeId;
/**
* @author Benoit Jeanson {@literal <benoit.jeanson at rte-france.com>}
* @author Nicolas Duchene
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
* @author Franck Lecuyer {@literal <franck.lecuyer at rte-france.com>}
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class DefaultSVGWriter implements SVGWriter {
private static final String SVG_NAMESPACE = "http://www.w3.org/2000/svg";
private static final String SVG_QUALIFIED_NAME = "svg";
protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultSVGWriter.class);
protected static final String GROUP = "g";
protected static final String CLASS = "class";
protected static final String STYLE = "style";
protected static final String TRANSFORM = "transform";
protected static final String TRANSLATE = "translate";
protected static final String ROTATE = "rotate";
protected static final String SCALE = "scale";
protected static final double LABEL_OFFSET = 5d;
protected static final String POLYLINE = "polyline";
protected static final String POINTS = "points";
protected static final String TEXT_ANCHOR = "text-anchor";
protected static final String MIDDLE = "middle";
protected static final int CIRCLE_RADIUS_NODE_INFOS_SIZE = 10;
protected static final String WIDTH = "width";
protected static final String HEIGHT = "height";
protected final ComponentLibrary componentLibrary;
protected final LayoutParameters layoutParameters;
protected final SvgParameters svgParameters;
public DefaultSVGWriter(ComponentLibrary componentLibrary, LayoutParameters layoutParameters, SvgParameters svgParameters) {
this.componentLibrary = Objects.requireNonNull(componentLibrary);
this.layoutParameters = Objects.requireNonNull(layoutParameters);
this.svgParameters = svgParameters;
}
/**
* Create the SVGDocument corresponding to the graph
*
* @param graph zone, voltage level or substation graph
* @param writer writer for the SVG content
*/
@Override
public GraphMetadata write(Graph graph, LabelProvider labelProvider, StyleProvider styleProvider, Writer writer) {
DOMImplementation domImpl = DomUtil.getDocumentBuilder().getDOMImplementation();
Document document = domImpl.createDocument(SVG_NAMESPACE, SVG_QUALIFIED_NAME, null);
setDocumentSize(graph, document);
Set<String> listUsedComponentSVG = new HashSet<>();
addStyle(document, styleProvider, labelProvider, graph, listUsedComponentSVG);
if (graph instanceof BaseGraph) {
((BaseGraph) graph).getMultiTermNodes().forEach(n -> listUsedComponentSVG.add(n.getComponentType()));
}
createDefsSVGComponents(document, listUsedComponentSVG);
addFrame(document);
GraphMetadata metadata = writeGraph(graph, document, labelProvider, styleProvider);
DomUtil.transformDocument(document, writer);
return metadata;
}
private void setDocumentSize(Graph graph, Document document) {
document.getDocumentElement().setAttribute("viewBox", "0 0 " + getDiagramWidth(graph, layoutParameters) + " " + getDiagramHeight(graph, layoutParameters));
if (svgParameters.isSvgWidthAndHeightAdded()) {
document.getDocumentElement().setAttribute(WIDTH, Double.toString(getDiagramWidth(graph, layoutParameters)));
document.getDocumentElement().setAttribute(HEIGHT, Double.toString(getDiagramHeight(graph, layoutParameters)));
}
}
private double getDiagramWidth(Graph graph, LayoutParameters layoutParameters) {
return graph.getWidth() + layoutParameters.getDiagramPadding().getLeft() + layoutParameters.getDiagramPadding().getRight();
}
private double getDiagramHeight(Graph graph, LayoutParameters layoutParameters) {
double height = graph.getHeight() + layoutParameters.getDiagramPadding().getTop() + layoutParameters.getDiagramPadding().getBottom();
if (graph instanceof VoltageLevelGraph && svgParameters.isBusesLegendAdded()) {
height += 6 * CIRCLE_RADIUS_NODE_INFOS_SIZE;
}
return height;
}
protected void addStyle(Document document, StyleProvider styleProvider, LabelProvider labelProvider,
Graph graph, Set<String> listUsedComponentSVG) {
graph.getAllNodesStream().forEach(n -> {
listUsedComponentSVG.add(n.getComponentType());
List<LabelProvider.NodeDecorator> nodeDecorators = labelProvider.getNodeDecorators(n, graph.getDirection(n));
if (nodeDecorators != null) {
nodeDecorators.forEach(nodeDecorator -> listUsedComponentSVG.add(nodeDecorator.getType()));
}
});
Element style = document.createElement(STYLE);
switch (svgParameters.getCssLocation()) {
case INSERTED_IN_SVG:
List<URL> urls = styleProvider.getCssUrls();
urls.addAll(componentLibrary.getCssUrls());
style.appendChild(getCdataSection(document, urls));
document.adoptNode(style);
document.getDocumentElement().appendChild(style);
break;
case EXTERNAL_IMPORTED:
styleProvider.getCssFilenames().forEach(name -> addStyleImportTextNode(document, style, name));
componentLibrary.getCssFilenames().forEach(name -> addStyleImportTextNode(document, style, name));
document.adoptNode(style);
document.getDocumentElement().appendChild(style);
break;
case EXTERNAL_NO_IMPORT:
// Nothing to do
break;
default:
throw new AssertionError("Unexpected CSS location: " + svgParameters.getCssLocation());
}
}
private org.w3c.dom.Node addStyleImportTextNode(Document document, Element style, String name) {
return style.appendChild(document.createTextNode("@import url(" + name + ");"));
}
private CDATASection getCdataSection(Document document, List<URL> cssUrls) {
StringBuilder styleSheetBuilder = new StringBuilder();
for (URL cssUrl : cssUrls) {
try {
styleSheetBuilder.append(new String(IOUtils.toByteArray(cssUrl), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException("Can't read css file " + cssUrl.getPath(), e);
}
}
String graphStyle = "\n" + styleSheetBuilder + "\n";
String cssStr = graphStyle
.replace("\r\n", "\n") // workaround for https://bugs.openjdk.java.net/browse/JDK-8133452
.replace("\r", "\n");
return document.createCDATASection(cssStr);
}
private void addFrame(Document document) {
Element rect = document.createElement("rect");
rect.setAttribute(WIDTH, "100%");
rect.setAttribute(HEIGHT, "100%");
rect.setAttribute(CLASS, StyleClassConstants.FRAME_CLASS);
document.adoptNode(rect);
document.getDocumentElement().appendChild(rect);
}
/**
* Create the SVGDocument corresponding to the graph
*/
protected GraphMetadata writeGraph(Graph graph, Document document, LabelProvider initProvider, StyleProvider styleProvider) {
GraphMetadata metadata = new GraphMetadata(layoutParameters, svgParameters);
Element root = document.createElement(GROUP);
drawGrid(graph, document, metadata, root);
if (graph instanceof VoltageLevelGraph) {
drawVoltageLevel((VoltageLevelGraph) graph, root, metadata, initProvider, styleProvider);
} else if (graph instanceof SubstationGraph) {
drawSubstation((SubstationGraph) graph, root, metadata, initProvider, styleProvider);
} else if (graph instanceof ZoneGraph) {
drawZone((ZoneGraph) graph, root, metadata, initProvider, styleProvider);
}
document.adoptNode(root);
document.getDocumentElement().appendChild(root);
return metadata;
}
private void drawGrid(Graph graph, Document document, GraphMetadata metadata, Element root) {
if (svgParameters.isShowGrid()) {
for (VoltageLevelGraph vlGraph : graph.getVoltageLevels()) {
if (vlGraph.isPositionNodeBusesCalculated()) {
drawGrid(vlGraph, document, metadata, root);
}
}
}
}
protected void drawVoltageLevel(VoltageLevelGraph graph,
Element root,
GraphMetadata metadata,
LabelProvider initProvider,
StyleProvider styleProvider) {
if (!graph.isForVoltageLevelDiagram()) {
drawGraphLabel(root, graph, metadata);
}
Set<Node> remainingNodesToDraw = graph.getNodeSet();
Set<Edge> remainingEdgesToDraw = graph.getEdgeSet();
drawBuses(root, graph, metadata, initProvider, styleProvider, remainingNodesToDraw);
graph.getCellStream().forEach(cell ->
drawCell(root, graph, cell, metadata, initProvider, styleProvider,
remainingEdgesToDraw, remainingNodesToDraw));
drawEdges(root, graph, metadata, initProvider, styleProvider, remainingEdgesToDraw);
drawNodes(root, graph, graph.getCoord(), metadata, initProvider, styleProvider, remainingNodesToDraw);
// Drawing the snake lines before multi-terminal nodes to hide the 3WT connections
drawSnakeLines(root, graph, metadata, styleProvider);
// Drawing the nodes outside the voltageLevel graphs (multi-terminal nodes)
drawNodes(root, graph, new Point(0, 0), metadata, initProvider, styleProvider, graph.getMultiTermNodes());
if (graph.isForVoltageLevelDiagram() && svgParameters.isBusesLegendAdded()) {
drawBusesLegend(root, graph, metadata, initProvider, styleProvider);
}
}
private void drawCell(Element root, VoltageLevelGraph graph, Cell cell,
GraphMetadata metadata, LabelProvider initProvider, StyleProvider styleProvider,
Set<Edge> remainingEdgesToDraw, Set<Node> remainingNodesToDraw) {
// To avoid overlapping lines over the switches, first, we draw all nodes except the switch nodes and bus connections,
// then we draw all the edges, and finally we draw the switch nodes and bus connections
String prefixId = metadata.getSvgParameters().getPrefixId();
String cellId = IdUtil.escapeId(prefixId + cell.getId());
Element g = root.getOwnerDocument().createElement(GROUP);
g.setAttribute("id", cellId);
g.setAttribute(CLASS, String.join(" ", styleProvider.getCellStyles(cell)));
List<Node> cellNodes = cell.getNodes();
List<Node> nodesToDraw = cellNodes.stream().filter(n -> !(n instanceof BusNode)).collect(Collectors.toList());
Collection<Edge> edgesToDraw = nodesToDraw.stream().flatMap(n -> n.getAdjacentEdges().stream())
.filter(e -> cellNodes.contains(e.getNode1()) && cellNodes.contains(e.getNode2()))
.collect(Collectors.toCollection(LinkedHashSet::new));
drawEdges(g, graph, metadata, initProvider, styleProvider, edgesToDraw);
drawNodes(g, graph, graph.getCoord(), metadata, initProvider, styleProvider, nodesToDraw);
remainingEdgesToDraw.removeAll(edgesToDraw);
remainingNodesToDraw.removeAll(nodesToDraw);
root.appendChild(g);
}
protected void drawSubstation(SubstationGraph graph,
Element root,
GraphMetadata metadata,
LabelProvider initProvider,
StyleProvider styleProvider) {
// Drawing the voltageLevel graphs
for (VoltageLevelGraph vlGraph : graph.getVoltageLevels()) {
drawVoltageLevel(vlGraph, root, metadata, initProvider, styleProvider);
}
// Drawing the snake lines before multi-terminal nodes to hide the 3WT connections
drawSnakeLines(root, graph, metadata, styleProvider);
// Drawing the nodes outside the voltageLevel graphs (multi-terminal nodes)
drawNodes(root, graph, new Point(0, 0), metadata, initProvider, styleProvider, graph.getMultiTermNodes());
}
/*
* Drawing the grid lines (if required)
*/
protected void drawGrid(VoltageLevelGraph graph, Document document, GraphMetadata metadata, Element root) {
int maxH = graph.getMaxH();
int maxV = graph.getMaxV();
Element gridRoot = document.createElement(GROUP);
String prefixId = metadata.getSvgParameters().getPrefixId();
String gridId = prefixId + "GRID_" + graph.getVoltageLevelInfos().getId();
gridRoot.setAttribute("id", gridId);
gridRoot.setAttribute(CLASS, StyleClassConstants.GRID_STYLE_CLASS);
// vertical lines
for (int iCell = 0; iCell < maxH / 2 + 1; iCell++) {
drawGridVerticalLine(document, graph, maxV, graph.getX() + iCell * layoutParameters.getCellWidth(), gridRoot);
}
// TOP - Horizontal lines
if (graph.getExternCellHeight(TOP) > 0.) {
// StackHeight
drawGridHorizontalLine(document, graph, maxH, graph.getY() + graph.getFirstBusY() - layoutParameters.getStackHeight(), gridRoot);
// internCellHeight
drawGridHorizontalLine(document, graph, maxH, graph.getY() + graph.getFirstBusY() - layoutParameters.getInternCellHeight(), gridRoot);
// FeederSpan
drawGridHorizontalLine(document, graph, maxH, graph.getY() + layoutParameters.getFeederSpan(), gridRoot);
}
// BOTTOM - Horizontal lines
if (graph.getExternCellHeight(BOTTOM) > 0.) {
// StackHeight
drawGridHorizontalLine(document, graph, maxH, graph.getY() + graph.getFirstBusY() + layoutParameters.getStackHeight() + layoutParameters.getVerticalSpaceBus() * maxV, gridRoot);
// internCellHeight
drawGridHorizontalLine(document, graph, maxH, graph.getY() + graph.getFirstBusY() + layoutParameters.getInternCellHeight() + layoutParameters.getVerticalSpaceBus() * maxV, gridRoot);
// FeederSpan
drawGridHorizontalLine(document, graph, maxH, graph.getY() + graph.getFirstBusY() + graph.getExternCellHeight(BOTTOM) - layoutParameters.getFeederSpan() + layoutParameters.getVerticalSpaceBus() * maxV, gridRoot);
}
metadata.addNodeMetadata(new GraphMetadata.NodeMetadata(null, gridId,
graph.getVoltageLevelInfos().getId(),
null,
null,
false,
UNDEFINED,
false,
null,
Collections.emptyList()));
root.appendChild(gridRoot);
}
protected void drawGridHorizontalLine(Document document, VoltageLevelGraph graph, int maxH, double y, Element root) {
drawGridLine(document, graph.getX(), y, maxH / 2. * layoutParameters.getCellWidth() + graph.getX(), y, root);
}
protected void drawGridVerticalLine(Document document, VoltageLevelGraph graph, int maxV, double x, Element root) {
drawGridLine(document,
x, graph.getY() + graph.getFirstBusY() - graph.getExternCellHeight(TOP),
x, graph.getY() + graph.getFirstBusY() + graph.getExternCellHeight(BOTTOM)
+ layoutParameters.getVerticalSpaceBus() * maxV, root);
}
protected void drawGridLine(Document document, double x1, double y1, double x2, double y2, Element root) {
Element line = document.createElement("line");
line.setAttribute("x1", Double.toString(x1));
line.setAttribute("x2", Double.toString(x2));
line.setAttribute("y1", Double.toString(y1));
line.setAttribute("y2", Double.toString(y2));
root.appendChild(line);
}
/*
* Drawing the voltageLevel graph nodes
*/
protected void drawBuses(Element root,
VoltageLevelGraph graph,
GraphMetadata metadata,
LabelProvider initProvider,
StyleProvider styleProvider,
Set<Node> remainingNodesToDraw) {
String prefixId = metadata.getSvgParameters().getPrefixId();
for (BusNode busNode : graph.getNodeBuses()) {
String nodeId = IdUtil.escapeId(prefixId + busNode.getId());
Element g = root.getOwnerDocument().createElement(GROUP);
g.setAttribute("id", nodeId);
g.setAttribute(CLASS, String.join(" ", styleProvider.getNodeStyles(graph, busNode, componentLibrary, svgParameters.isShowInternalNodes())));
drawBus(graph, busNode, g);
List<LabelProvider.NodeLabel> nodeLabels = initProvider.getNodeLabels(busNode, graph.getDirection(busNode));
drawNodeLabel(prefixId, g, busNode, nodeLabels);
drawNodeDecorators(prefixId, g, graph, busNode, initProvider, styleProvider);
insertBusInfo(prefixId, g, busNode, metadata, initProvider, styleProvider);
root.appendChild(g);
metadata.addNodeMetadata(
new GraphMetadata.NodeMetadata(null, nodeId, graph.getVoltageLevelInfos().getId(), null, BUSBAR_SECTION,
false, UNDEFINED, false, busNode.getEquipmentId(), createNodeLabelMetadata(prefixId, busNode, nodeLabels)));
if (metadata.getComponentMetadata(BUSBAR_SECTION) == null) {
metadata.addComponent(new Component(BUSBAR_SECTION,
null, null,
componentLibrary.getComponentStyleClass(BUSBAR_SECTION).orElse(null),
componentLibrary.getTransformations(BUSBAR_SECTION), null));
}
remainingNodesToDraw.remove(busNode);
}
}
protected List<GraphMetadata.NodeLabelMetadata> createNodeLabelMetadata(String prefixId, Node node, List<LabelProvider.NodeLabel> nodeLabels) {
List<GraphMetadata.NodeLabelMetadata> labelsMetadata = new ArrayList<>();
for (LabelProvider.NodeLabel nodeLabel : nodeLabels) {
LabelPosition labelPosition = nodeLabel.getPosition();
String svgId = getNodeLabelId(prefixId, node, labelPosition);
labelsMetadata.add(new GraphMetadata.NodeLabelMetadata(svgId, labelPosition.getPositionName(), nodeLabel.getUserDefinedId()));
}
return labelsMetadata;
}
/*
* Drawing the voltageLevel graph nodes
*/
protected void drawNodes(Element root,
BaseGraph graph,
Point shift,
GraphMetadata metadata,
LabelProvider labelProvider,
StyleProvider styleProvider,
Collection<? extends Node> nodes) {
String prefixId = metadata.getSvgParameters().getPrefixId();
for (Node node : nodes) {
String nodeEscapedId = IdUtil.escapeId(prefixId + node.getId());
Element g = root.getOwnerDocument().createElement(GROUP);
g.setAttribute("id", nodeEscapedId);
g.setAttribute(CLASS, String.join(" ", styleProvider.getNodeStyles(graph.getVoltageLevelGraph(node), node, componentLibrary, svgParameters.isShowInternalNodes())));
incorporateComponents(prefixId, graph, node, shift, g, labelProvider, styleProvider);
List<LabelProvider.NodeLabel> nodeLabels = labelProvider.getNodeLabels(node, graph.getDirection(node));
drawNodeLabel(prefixId, g, node, nodeLabels);
drawNodeDecorators(prefixId, g, graph, node, labelProvider, styleProvider);
root.appendChild(g);
Direction direction = node instanceof FeederNode ? graph.getDirection(node) : Direction.UNDEFINED;
setMetadata(metadata, node, nodeEscapedId, graph, direction, nodeLabels);
}
}
protected void setMetadata(GraphMetadata metadata, Node node, String nodeEscapedId, BaseGraph graph, Direction direction, List<LabelProvider.NodeLabel> nodeLabels) {
String nextVId = null;
if (node instanceof FeederNode && ((FeederNode) node).getFeeder() instanceof FeederWithSides) {
FeederWithSides feederWs = (FeederWithSides) ((FeederNode) node).getFeeder();
VoltageLevelInfos otherSideVoltageLevelInfos = feederWs.getOtherSideVoltageLevelInfos();
if (otherSideVoltageLevelInfos != null) {
nextVId = otherSideVoltageLevelInfos.getId();
}
}
String prefixId = metadata.getSvgParameters().getPrefixId();
String vId = graph instanceof VoltageLevelGraph ? ((VoltageLevelGraph) graph).getVoltageLevelInfos().getId() : "";
boolean isOpen = node.getType() == NodeType.SWITCH && ((SwitchNode) node).isOpen();
metadata.addNodeMetadata(
new GraphMetadata.NodeMetadata(getUnescapedId(node), nodeEscapedId, vId, nextVId, node.getComponentType(), isOpen, direction, false,
node instanceof EquipmentNode ? ((EquipmentNode) node).getEquipmentId() : null,
createNodeLabelMetadata(prefixId, node, nodeLabels)));
addInfoComponentMetadata(metadata, node.getComponentType());
}
private String getUnescapedId(Node node) {
String unescapedId = null;
if (node.getComponentType().equals(VSC_CONVERTER_STATION) ||
node.getComponentType().equals(LCC_CONVERTER_STATION)) {
unescapedId = node.getId();
}
return unescapedId;
}
protected void drawNodeLabel(String prefixId, Element g, Node node, List<LabelProvider.NodeLabel> nodeLabels) {
for (LabelProvider.NodeLabel nodeLabel : nodeLabels) {
LabelPosition labelPosition = nodeLabel.getPosition();
Element label = createLabelElement(nodeLabel.getLabel(), labelPosition.getdX(), labelPosition.getdY(), labelPosition.getShiftAngle(), g);
String svgId = getNodeLabelId(prefixId, node, labelPosition);
label.setAttribute("id", svgId);
if (labelPosition.isCentered()) {
label.setAttribute(TEXT_ANCHOR, MIDDLE);
}
g.appendChild(label);
}
}
protected void drawNodeDecorators(String prefixId, Element root, Graph graph, Node node, LabelProvider labelProvider,
StyleProvider styleProvider) {
for (LabelProvider.NodeDecorator nodeDecorator : labelProvider.getNodeDecorators(node, graph.getDirection(node))) {
Element g = root.getOwnerDocument().createElement(GROUP);
g.setAttribute(CLASS, String.join(" ", styleProvider.getNodeDecoratorStyles(nodeDecorator, node, componentLibrary)));
insertDecoratorSVGIntoDocumentSVG(prefixId, nodeDecorator, g, graph, node, styleProvider);
root.appendChild(g);
}
}
/*
* Drawing the graph label
*/
protected void drawGraphLabel(Element root, VoltageLevelGraph graph, GraphMetadata metadata) {
// drawing the label of the voltageLevel
String idLabelVoltageLevel = metadata.getSvgParameters().getPrefixId() + "LABEL_VL_" + graph.getVoltageLevelInfos().getId();
Element gLabel = root.getOwnerDocument().createElement(GROUP);
gLabel.setAttribute("id", idLabelVoltageLevel);
double yPos = graph.getY() - 20.;
String graphName = svgParameters.isUseName() ? graph.getVoltageLevelInfos().getName() : graph.getVoltageLevelInfos().getId();
Element label = createLabelElement(graphName, graph.getX(), yPos, 0, gLabel);
label.setAttribute(CLASS, StyleClassConstants.GRAPH_LABEL_STYLE_CLASS);
gLabel.appendChild(label);
root.appendChild(gLabel);
metadata.addNodeMetadata(new GraphMetadata.NodeMetadata(null, idLabelVoltageLevel,
graph.getVoltageLevelInfos().getId(),
null,
null,
false,
UNDEFINED,
true,
null,
Collections.emptyList()));
}
/*
* Drawing the voltageLevel graph busbar sections
*/
protected void drawBus(VoltageLevelGraph graph, BusNode node, Element g) {
Element line = g.getOwnerDocument().createElement("line");
line.setAttribute("x1", "0");
line.setAttribute("y1", "0");
if (node.getOrientation().isHorizontal()) {
line.setAttribute("x2", String.valueOf(node.getPxWidth()));
line.setAttribute("y2", "0");
} else {
line.setAttribute("x2", "0");
line.setAttribute("y2", String.valueOf(node.getPxWidth()));
}
g.appendChild(line);
g.setAttribute(TRANSFORM, String.format("%s(%s,%s)", TRANSLATE, graph.getX() + node.getX(), graph.getY() + node.getY()));
}
/*
* Create a label text element at the given position
*/
protected Element createLabelElement(String str, double xShift, double yShift, int shiftAngle, Element g) {
Element label = g.getOwnerDocument().createElement("text");
label.setAttribute("x", String.valueOf(xShift));
label.setAttribute("y", String.valueOf(yShift));
if (shiftAngle != 0) {
label.setAttribute(TRANSFORM, ROTATE + "(" + shiftAngle + "," + 0 + "," + 0 + ")");
}
label.setAttribute(CLASS, StyleClassConstants.LABEL_STYLE_CLASS);
Text text = g.getOwnerDocument().createTextNode(str);
label.appendChild(text);
return label;
}
protected void incorporateComponents(String prefixId, Graph graph, Node node, Point shift, Element g,
LabelProvider labelProvider, StyleProvider styleProvider) {
String componentType = node.getComponentType();
transformComponent(node, shift, g);
if (componentLibrary.getSvgElements(componentType) != null) {
insertComponentSVGIntoDocumentSVG(prefixId, componentType, g, graph, node, labelProvider, styleProvider);
}
}
protected void insertComponentSVGIntoDocumentSVG(String prefixId, String componentType, Element g, Graph graph, Node node,
LabelProvider labelProvider, StyleProvider styleProvider) {
BiConsumer<Element, String> elementAttributesSetter
= (elt, subComponent) -> setComponentAttributes(prefixId, g, graph, node, styleProvider, elt, componentType, subComponent);
String tooltipContent = svgParameters.isTooltipEnabled() ? labelProvider.getTooltip(node) : null;
insertSVGIntoDocumentSVG(componentType, g, tooltipContent, elementAttributesSetter);
}
protected void insertFeederInfoSVGIntoDocumentSVG(FeederInfo feederInfo, String prefixId, Element g, double angle) {
BiConsumer<Element, String> elementAttributesSetter
= (e, subComponent) -> setInfoAttributes(feederInfo.getComponentType(), prefixId, g, e, subComponent, angle);
insertSVGIntoDocumentSVG(feederInfo.getComponentType(), g, null, elementAttributesSetter);
}
protected void insertBusInfoSVGIntoDocumentSVG(BusInfo busInfo, String prefixId, Element g) {
BiConsumer<Element, String> elementAttributesSetter
= (e, subComponent) -> setInfoAttributes(busInfo.getComponentType(), prefixId, g, e, subComponent, 0.);
insertSVGIntoDocumentSVG(busInfo.getComponentType(), g, null, elementAttributesSetter);
}
private void setInfoAttributes(String infoType, String prefixId, Element g, Element e, String subComponent, double angle) {
replaceId(g, e, prefixId);
componentLibrary.getSubComponentStyleClass(infoType, subComponent).ifPresent(style -> e.setAttribute(CLASS, style));
if (Math.abs(angle) > 0) {
ComponentSize componentSize = componentLibrary.getSize(infoType);
double cx = componentSize.getWidth() / 2;
double cy = componentSize.getHeight() / 2;
e.setAttribute(TRANSFORM, ROTATE + "(" + angle + "," + cx + "," + cy + ")");
}
}
protected void insertDecoratorSVGIntoDocumentSVG(String prefixId, LabelProvider.NodeDecorator nodeDecorator,
Element g, Graph graph, Node node, StyleProvider styleProvider) {
BiConsumer<Element, String> elementAttributesSetter
= (elt, subComponent) -> setDecoratorAttributes(prefixId, g, graph, node, nodeDecorator, styleProvider, elt, subComponent);
String nodeDecoratorType = nodeDecorator.getType();
insertSVGIntoDocumentSVG(nodeDecoratorType, g, null, elementAttributesSetter);
}
protected void insertSVGIntoDocumentSVG(String componentType, Element g, String tooltip,
BiConsumer<Element, String> elementAttributesSetter) {
addToolTip(g, tooltip);
Map<String, List<Element>> subComponents = componentLibrary.getSvgElements(componentType);
subComponents.forEach(svgParameters.isAvoidSVGComponentsDuplication() ?
(subComponentName, svgSubComponent) -> insertSubcomponentReference(g, elementAttributesSetter, componentType, subComponentName, subComponents.size()) :
(subComponentName, svgSubComponent) -> insertDuplicatedSubcomponent(g, elementAttributesSetter, subComponentName, svgSubComponent)
);
}
private void insertDuplicatedSubcomponent(Element g, BiConsumer<Element, String> elementAttributesSetter, String subComponentName, List<Element> svgSubComponent) {
svgSubComponent.forEach(e -> {
Element clonedElement = (Element) e.cloneNode(true);
setAttributesAndInsertElement(g, elementAttributesSetter, subComponentName, clonedElement);
});
}
private void insertSubcomponentReference(Element g, BiConsumer<Element, String> elementAttributesSetter, String componentType, String subComponentName, int nbSubComponents) {
// Adding <use> markup to reuse the svg defined in the <defs> part
Element eltUse = g.getOwnerDocument().createElement("use");
eltUse.setAttribute("href", "#" + getHRefValue(nbSubComponents, componentType, subComponentName));
setAttributesAndInsertElement(g, elementAttributesSetter, subComponentName, eltUse);
}
private static String getHRefValue(int nbSubComponents, String componentType, String subComponentName) {
return nbSubComponents > 1 ? componentType + "-" + subComponentName : componentType;
}
private void setAttributesAndInsertElement(Element g, BiConsumer<Element, String> elementAttributesSetter, String subComponentName, Element element) {
elementAttributesSetter.accept(element, subComponentName);
g.getOwnerDocument().adoptNode(element);
g.appendChild(element);
}
private void addToolTip(Element g, String tooltip) {
if (!StringUtils.isEmpty(tooltip)) {
Document doc = g.getOwnerDocument();
Element title = doc.createElement("title");
title.appendChild(doc.createTextNode(tooltip));
g.appendChild(title);
}
}
private void setComponentAttributes(String prefixId, Element g, Graph graph, Node node, StyleProvider styleProvider,
Element elt, String componentType, String subComponent) {
replaceId(g, elt, prefixId);
ComponentSize size = componentLibrary.getSize(componentType);
// Checking if svg component is allowed to be transformed (rotate or flip)
// (ex : disconnector in SVG component library not allowed to rotate)
Orientation nodeOrientation = node.getOrientation();
Component.Transformation transformation = componentLibrary.getTransformations(node.getComponentType()).get(nodeOrientation);
if (transformation != null) {
switch (transformation) {
case ROTATION: {
elt.setAttribute(TRANSFORM, ROTATE + "(" + nodeOrientation.toRotationAngle() + "," + size.getWidth() / 2 + "," + size.getHeight() / 2 + ")");
break;
}
case FLIP: {
if (nodeOrientation.isVertical()) {
elt.setAttribute(TRANSFORM, SCALE + "(1, -1)" + " " + TRANSLATE + "(0, " + -size.getHeight() + ")");
} else {
elt.setAttribute(TRANSFORM, SCALE + "(-1, 1)" + " " + TRANSLATE + "(" + -size.getWidth() + ", 0)");
}
break;
}
case NONE:
default: {
// No transformation
}
}
}
List<String> subComponentStyles = styleProvider.getNodeSubcomponentStyles(graph, node, subComponent);
componentLibrary.getSubComponentStyleClass(componentType, subComponent).ifPresent(subComponentStyles::add);
if (!subComponentStyles.isEmpty()) {
elt.setAttribute(CLASS, String.join(" ", subComponentStyles));
}
}
private void setDecoratorAttributes(String prefixId, Element g, Graph graph, Node node, LabelProvider.NodeDecorator nodeDecorator,
StyleProvider styleProvider, Element elt, String subComponentName) {
replaceId(g, elt, prefixId);
ComponentSize decoratorSize = componentLibrary.getSize(nodeDecorator.getType());
LabelPosition decoratorPosition = nodeDecorator.getPosition();
elt.setAttribute(TRANSFORM, getTransformStringDecorator(node, decoratorPosition, decoratorSize));
List<String> svgNodeSubcomponentStyles = styleProvider.getNodeSubcomponentStyles(graph, node, subComponentName);
componentLibrary.getSubComponentStyleClass(nodeDecorator.getType(), subComponentName).ifPresent(svgNodeSubcomponentStyles::add);
if (!svgNodeSubcomponentStyles.isEmpty()) {
elt.setAttribute(CLASS, String.join(" ", svgNodeSubcomponentStyles));
}
}
/**
* Ensures uniqueness of ids by adding prefixId and node id before id of elt (if existing)
*
* @param g XML element for the node
* @param elt XML element being duplicated
* @param prefixId prefix string
*/
private void replaceId(Element g, Element elt, String prefixId) {
org.w3c.dom.Node nodeId = elt.getAttributes().getNamedItem("id");
// the id is set only if elt had already an id
if (nodeId != null) {
String nodeIdValue = nodeId.getTextContent();
String gIdValue = StringUtils.removeStart(g.getAttribute("id"), prefixId);
nodeId.setTextContent(prefixId + gIdValue + "_" + nodeIdValue);
}
}
private String getTransformStringDecorator(Node node, LabelPosition decoratorPosition, ComponentSize decoratorSize) {
ComponentSize componentSize = componentLibrary.getSize(node.getComponentType());
double dX = componentSize.getWidth() / 2 + decoratorPosition.getdX();
double dY = componentSize.getHeight() / 2 + decoratorPosition.getdY();
if (decoratorPosition.isCentered()) {
dX -= decoratorSize.getWidth() / 2;
dY -= decoratorSize.getHeight() / 2;
}
return TRANSLATE + "(" + dX + "," + dY + ")";
}
protected void transformComponent(Node node, Point shift, Element g) {
double[] translate = getNodeTranslate(node, shift);
g.setAttribute(TRANSFORM, TRANSLATE + "(" + translate[0] + "," + translate[1] + ")");
}
private double[] getNodeTranslate(Node node, Point shift) {
ComponentSize componentSize = componentLibrary.getSize(node.getComponentType());
double translateX = node.getX() + shift.getX() - componentSize.getWidth() / 2;
double translateY = node.getY() + shift.getY() - componentSize.getHeight() / 2;
return new double[] {translateX, translateY};
}
protected void transformFeederInfo(List<Point> points, ComponentSize componentSize, double shift, Element g) {
Point pointA = points.get(0);
Point pointB = points.get(1);
double distancePoints = pointA.distance(pointB);
// Case of wires with non-direct straight lines: if wire distance between first 2 points is too small to display
// the feeder info, checks if the distance between the 2nd and the 3rd points is big enough
if (points.size() > 2 && distancePoints < 3 * componentSize.getHeight()) {
double distancePoints23 = points.get(1).distance(points.get(2));
if (distancePoints23 > 3 * componentSize.getHeight()) {
distancePoints = distancePoints23;
pointA = points.get(1);
pointB = points.get(2);
}
}
if (distancePoints > 0) {
double dx = pointB.getX() - pointA.getX();
double dy = pointB.getY() - pointA.getY();
// Calculate cos and sin of the angle between the wire line and the abscisse
double cosAngle = dx / distancePoints;
double sinAngle = dy / distancePoints;
double x = pointA.getX() + cosAngle * (svgParameters.getFeederInfosOuterMargin() + shift);
double y = pointA.getY() + sinAngle * (svgParameters.getFeederInfosOuterMargin() + shift);
double feederInfoRotationAngle = Math.atan(dy / dx) - Math.PI / 2;
if (feederInfoRotationAngle < -Math.PI / 2) {
feederInfoRotationAngle += Math.PI;
}
g.setAttribute(TRANSFORM, getTransformString(x, y, feederInfoRotationAngle, componentSize));
}
}
private String getTransformString(double centerPosX, double centerPosY, double angle, ComponentSize componentSize) {
if (angle == 0) {
double translateX = centerPosX - componentSize.getWidth() / 2;
double translateY = centerPosY - componentSize.getHeight() / 2;
return TRANSLATE + "(" + translateX + "," + translateY + ")";
} else {
double[] matrix = getTransformMatrix(componentSize.getWidth(), componentSize.getHeight(), angle,
centerPosX, centerPosY);
return transformMatrixToString(matrix, 4);
}
}
private double[] getTransformMatrix(double width, double height, double angle,
double centerPosX, double centerPosY) {
double cosRo = Math.cos(angle);
double sinRo = Math.sin(angle);
double cdx = width / 2;
double cdy = height / 2;
double e1 = centerPosX - cdx * cosRo + cdy * sinRo;
double f1 = centerPosY - cdx * sinRo - cdy * cosRo;
return new double[]{+cosRo, sinRo, -sinRo, cosRo, e1, f1};
}
private static String transformMatrixToString(double[] matrix, int precision) {
double[] matrix2 = new double[matrix.length];
for (int i = 0; i < matrix.length; i++) {
matrix2[i] = Precision.round(matrix[i], precision);
}
return "matrix("
+ matrix2[0] + "," + matrix2[1] + ","
+ matrix2[2] + "," + matrix2[3] + ","
+ matrix2[4] + "," + matrix2[5] + ")";
}
protected void insertFeederInfos(String prefixId,
List<Point> points,
Element root,
VoltageLevelGraph graph,
FeederNode feederNode,
GraphMetadata metadata,
LabelProvider labelProvider,
StyleProvider styleProvider) {
if (points.isEmpty()) {
points.add(graph.getShiftedPoint(feederNode));
points.add(graph.getShiftedPoint(feederNode));
}
double shiftFeederInfo = 0;
for (FeederInfo feederInfo : labelProvider.getFeederInfos(feederNode)) {
drawFeederInfo(prefixId, feederNode, points, root, feederInfo, shiftFeederInfo, metadata, styleProvider);
addInfoComponentMetadata(metadata, feederInfo.getComponentType());
double height = componentLibrary.getSize(feederInfo.getComponentType()).getHeight();
shiftFeederInfo += svgParameters.getFeederInfosIntraMargin() + height;
}
}
private void addInfoComponentMetadata(GraphMetadata metadata, String componentType) {
if (metadata.getComponentMetadata(componentType) == null) {
metadata.addComponent(new Component(componentType,
componentLibrary.getAnchorPoints(componentType),
componentLibrary.getSize(componentType),
componentLibrary.getComponentStyleClass(componentType).orElse(null),
componentLibrary.getTransformations(componentType), null));
}
}
private void drawFeederInfo(String prefixId, FeederNode feederNode, List<Point> points, Element root,
FeederInfo feederInfo, double shift, GraphMetadata metadata,
StyleProvider styleProvider) {
Element g = root.getOwnerDocument().createElement(GROUP);
ComponentSize size = componentLibrary.getSize(feederInfo.getComponentType());
double shX = size.getWidth() + LABEL_OFFSET;
double shY = size.getHeight() / 2;
List<String> styles = new ArrayList<>(3);
componentLibrary.getComponentStyleClass(feederInfo.getComponentType()).ifPresent(styles::add);
transformFeederInfo(points, size, shift, g);
String svgId = escapeId(feederNode.getId()) + "_" + feederInfo.getComponentType();
g.setAttribute("id", svgId);
String componentType = feederInfo.getComponentType();
String side = feederNode.getFeeder() instanceof FeederWithSides ? ((FeederWithSides) feederNode.getFeeder()).getSide().name() : null;
metadata.addFeederInfoMetadata(new FeederInfoMetadata(svgId, feederNode.getEquipmentId(), side, componentType, feederInfo.getUserDefinedId()));
// we draw the feeder info
double rotationAngle = points.get(0).getY() > points.get(1).getY() ? 180 : 0;
insertFeederInfoSVGIntoDocumentSVG(feederInfo, prefixId, g, rotationAngle);
styles.addAll(styleProvider.getFeederInfoStyles(feederInfo));
// we draw the right label only if present
feederInfo.getRightLabel().ifPresent(s -> {
Element labelRight = createLabelElement(s, shX, shY, 0, g);
g.appendChild(labelRight);
});
// we draw the left label only if present
feederInfo.getLeftLabel().ifPresent(s -> {
Element labelLeft = createLabelElement(s, -LABEL_OFFSET, shY, 0, g);
labelLeft.setAttribute(STYLE, "text-anchor:end");
g.appendChild(labelLeft);
});
g.setAttribute(CLASS, String.join(" ", styles));
root.appendChild(g);
}
protected void insertBusInfo(String prefixId, Element root, BusNode busNode,
GraphMetadata metadata, LabelProvider labelProvider, StyleProvider styleProvider) {
Optional<BusInfo> busInfo = labelProvider.getBusInfo(busNode);
busInfo.ifPresent(info -> {
drawBusInfo(prefixId, busNode, root, info, styleProvider, metadata);
addInfoComponentMetadata(metadata, busInfo.get().getComponentType());
});
}
private void drawBusInfo(String prefixId, BusNode busNode, Element root, BusInfo busInfo,
StyleProvider styleProvider, GraphMetadata metadata) {
Element g = root.getOwnerDocument().createElement(GROUP);
// Position
ComponentSize size = componentLibrary.getSize(busInfo.getComponentType());
double shiftX = svgParameters.getBusInfoMargin();
double dy = -size.getHeight() / 2;
double dx = busInfo.getAnchor() == Side.RIGHT ? busNode.getPxWidth() - shiftX - size.getWidth() : shiftX;
g.setAttribute(TRANSFORM, TRANSLATE + "(" + dx + "," + dy + ")");
// Styles
List<String> styles = new ArrayList<>();
componentLibrary.getComponentStyleClass(busInfo.getComponentType()).ifPresent(styles::add);
styles.addAll(styleProvider.getBusInfoStyle(busInfo));
g.setAttribute(CLASS, String.join(" ", styles));
// Identity
String svgId = escapeId(busNode.getId()) + "_" + busInfo.getComponentType();
g.setAttribute("id", svgId);
// Metadata
metadata.addBusInfoMetadata(new GraphMetadata.BusInfoMetadata(svgId, busNode.getId(), busInfo.getUserDefinedId()));
// Append indicator to SVG
insertBusInfoSVGIntoDocumentSVG(busInfo, prefixId, g);
double shY = size.getHeight() + LABEL_OFFSET;
// We draw the bottom label only if present
busInfo.getBottomLabel().ifPresent(s -> {
Element labelBottom = createLabelElement(s, 0, shY, 0, g);
g.appendChild(labelBottom);
});
// We draw the top label only if present
busInfo.getTopLabel().ifPresent(s -> {
Element labelTop = createLabelElement(s, 0, -LABEL_OFFSET, 0, g);
g.appendChild(labelTop);
});
root.appendChild(g);
}
/**
* For global unicity in all type of container (voltage level, substation, zone), we prefix with the container Id and
* we rely on the fact that node ids are unique inside a voltage level. We also prepend with a custom prefix id to
* allow multiple diagrams unicity.
*/
private static String getWireId(String prefixId, String containerId, Edge edge) {
return escapeClassName(prefixId + "_" + containerId + "_" + edge.getNode1().getId() + "_" + edge.getNode2().getId());
}
private static String getNodeLabelId(String prefixId, Node node, LabelPosition labelPosition) {
return prefixId + node.getId() + "_" + labelPosition.getPositionName();
}
/*
* Drawing the voltageLevel graph edges
*/
protected void drawEdges(Element root, VoltageLevelGraph graph, GraphMetadata metadata,
LabelProvider initProvider, StyleProvider styleProvider, Collection<Edge> edges) {
String voltageLevelId = graph.getVoltageLevelInfos().getId();
String prefixId = metadata.getSvgParameters().getPrefixId();
for (Edge edge : edges) {
String wireId = getWireId(prefixId, voltageLevelId, edge);
List<Point> pol = new ArrayList<>();
if (!edge.isZeroLength()) {
// Determine points of the polyline
Point shift = graph.getCoord();
pol = WireConnection.searchBestAnchorPoints(componentLibrary, graph, edge.getNode1(), edge.getNode2())
.calculatePolylinePoints(edge.getNode1(), edge.getNode2(), svgParameters.isDrawStraightWires(), shift);
if (!pol.isEmpty()) {
Element g = root.getOwnerDocument().createElement(GROUP);
g.setAttribute("id", wireId);
List<String> wireStyles = styleProvider.getEdgeStyles(graph, edge);
g.setAttribute(CLASS, String.join(" ", wireStyles));
Element polyline = root.getOwnerDocument().createElement(POLYLINE);
polyline.setAttribute(POINTS, pointsListToString(pol));
g.appendChild(polyline);
root.appendChild(g);
}
}
metadata.addWireMetadata(new GraphMetadata.WireMetadata(wireId,
escapeId(edge.getNode1().getId()),
escapeId(edge.getNode2().getId()),
svgParameters.isDrawStraightWires(),
false));
if (edge.getNode1() instanceof FeederNode) {
if (!(edge.getNode2() instanceof FeederNode)) {
insertFeederInfos(prefixId, pol, root, graph, (FeederNode) edge.getNode1(), metadata, initProvider, styleProvider);
}
} else if (edge.getNode2() instanceof FeederNode) {
Collections.reverse(pol);
insertFeederInfos(prefixId, pol, root, graph, (FeederNode) edge.getNode2(), metadata, initProvider, styleProvider);
}
}
}
/*
* Drawing the zone graph edges (snakelines between station diagram)
*/
protected void drawSnakeLines(Element root, ZoneGraph graph,
GraphMetadata metadata, StyleProvider styleProvider) {
for (BranchEdge edge : graph.getLineEdges()) {
drawSnakeLines(graph, edge, root, metadata, styleProvider);
}
}
/*
* Drawing the substation graph edges (snakelines between voltageLevel diagram)
*/
protected void drawSnakeLines(Element root, BaseGraph graph,
GraphMetadata metadata, StyleProvider styleProvider) {
for (BranchEdge edge : graph.getLineEdges()) {
drawSnakeLines(graph, edge, root, metadata, styleProvider);
}
for (BranchEdge edge : graph.getTwtEdges()) {
drawSnakeLines(graph, edge, root, metadata, styleProvider);
}
}
private void drawSnakeLines(Graph graph, BranchEdge edge, Element root, GraphMetadata metadata, StyleProvider styleProvider) {
Element g = root.getOwnerDocument().createElement(GROUP);
String snakeLineId = escapeId(metadata.getSvgParameters().getPrefixId() + edge.getId());
g.setAttribute("id", snakeLineId);
List<String> wireStyles = styleProvider.getEdgeStyles(graph, edge);
g.setAttribute(CLASS, String.join(" ", wireStyles));
root.appendChild(g);
// Get the points of the snakeLine, already calculated during the layout application
List<Point> pol = edge.getSnakeLine();
if (!pol.isEmpty() && graph.getVoltageLevelGraph(edge.getNode2()) == null) {
// Note that edge.getNode2() might be outside the voltageLevelGraph (multiTermNode between voltage levels),
// whereas edge.getNode1() is supposed to always be a FeederNode in a voltageLevelGraph
// Snakeline between two feeder nodes, no need to adapt
adaptCoordSnakeLine(edge, pol, graph);
}
Element polyline = root.getOwnerDocument().createElement(POLYLINE);
polyline.setAttribute(POINTS, pointsListToString(pol));
g.appendChild(polyline);
metadata.addWireMetadata(new GraphMetadata.WireMetadata(snakeLineId,
escapeId(edge.getNode1().getId()),
escapeId(edge.getNode2().getId()),
svgParameters.isDrawStraightWires(),
true));
}
/*
* Adaptation of the previously calculated snakeLine points, in order to use the anchor points
* if a node is outside any graph
*/
private void adaptCoordSnakeLine(BranchEdge edge, List<Point> pol, Graph graph) {
// Getting the right polyline point from where we need to compute the best anchor point
Point multiTermPoint = pol.get(pol.size() - 1);
Point pointBeforeNode = pol.get(Math.max(pol.size() - 2, 0));
AnchorPoint bestAnchorPoint = WireConnection.getBestAnchorPoint(componentLibrary, graph, edge.getNode2(), pointBeforeNode);
if (multiTermPoint.getX() == pointBeforeNode.getX()) {
pointBeforeNode.shiftX(bestAnchorPoint.getX()); // vertical line remains vertical
} else if (multiTermPoint.getY() == pointBeforeNode.getY()) {
pointBeforeNode.shiftY(bestAnchorPoint.getY()); // horizontal line remains horizontal
}
multiTermPoint.shift(bestAnchorPoint);
}
protected String pointsListToString(List<Point> polyline) {
return polyline.stream()
.map(pt -> pt.getX() + "," + pt.getY())
.collect(Collectors.joining(","));
}
/**
* Creation of the defs area for the SVG components
*/
protected void createDefsSVGComponents(Document document, Set<String> listUsedComponentSVG) {
if (svgParameters.isAvoidSVGComponentsDuplication()) {
// adding also arrows
listUsedComponentSVG.add(ARROW_ACTIVE);
listUsedComponentSVG.add(ARROW_REACTIVE);
Element defs = document.createElement("defs");
listUsedComponentSVG.forEach(c -> {
Map<String, List<Element>> subComponents = componentLibrary.getSvgElements(c);
if (subComponents != null) {
Element group = document.createElement(GROUP);
group.setAttribute("id", c);
insertSVGComponentIntoDefsArea(c, group, subComponents);
defs.getOwnerDocument().adoptNode(group);
defs.appendChild(group);
}
});
document.adoptNode(defs);
document.getDocumentElement().appendChild(defs);
}
}
protected void insertSVGComponentIntoDefsArea(String componentType, Element group, Map<String, List<Element>> subComponents) {
for (Map.Entry<String, List<Element>> subComponent : subComponents.entrySet()) {
if (subComponents.size() > 1) {
Element subComponentGroup = group.getOwnerDocument().createElement("g");
subComponentGroup.setAttribute("id", getHRefValue(subComponents.size(), componentType, subComponent.getKey()));
addSvgSubComponentsToElement(subComponent.getValue(), subComponentGroup);
group.getOwnerDocument().adoptNode(subComponentGroup);
group.appendChild(subComponentGroup);
} else {
addSvgSubComponentsToElement(subComponent.getValue(), group);
}
}
}
private void addSvgSubComponentsToElement(List<Element> subComponentElements, Element group) {
for (Element subComponentElement : subComponentElements) {
org.w3c.dom.Node n = subComponentElement.cloneNode(true);
group.getOwnerDocument().adoptNode(n);
group.appendChild(n);
}
}
private void drawZone(ZoneGraph graph,
Element root,
GraphMetadata metadata,
LabelProvider initProvider,
StyleProvider styleProvider) {
for (SubstationGraph sGraph : graph.getSubstations()) {
drawSubstation(sGraph, root, metadata, initProvider, styleProvider);
}
drawSnakeLines(root, graph, metadata, styleProvider);
}
/*
* Drawing the voltageLevel nodes infos
*/
private void drawBusLegendInfo(BusLegendInfo busLegendInfo, double xShift, double yShift,
Element g, String idNode, List<String> styles) {
Element circle = g.getOwnerDocument().createElement("circle");
// colored circle
circle.setAttribute("id", idNode + "_circle");
circle.setAttribute("cx", String.valueOf(xShift));
circle.setAttribute("cy", String.valueOf(yShift));
circle.setAttribute("r", String.valueOf(CIRCLE_RADIUS_NODE_INFOS_SIZE));
circle.setAttribute("stroke-width", String.valueOf(CIRCLE_RADIUS_NODE_INFOS_SIZE));
circle.setAttribute(CLASS, String.join(" ", styles));
g.appendChild(circle);
// legend nodes
double padding = 2.5;
for (BusLegendInfo.Caption caption : busLegendInfo.captions()) {
Element label = g.getOwnerDocument().createElement("text");
label.setAttribute("id", idNode + "_" + caption.type());
label.setAttribute("x", String.valueOf(xShift - CIRCLE_RADIUS_NODE_INFOS_SIZE));
label.setAttribute("y", String.valueOf(yShift + padding * CIRCLE_RADIUS_NODE_INFOS_SIZE));
label.setAttribute(CLASS, StyleClassConstants.BUS_LEGEND_INFO);
Text textNode = g.getOwnerDocument().createTextNode(caption.label());
label.appendChild(textNode);
g.appendChild(label);
padding += 1.5;
}
}
private void drawBusesLegend(Element root, VoltageLevelGraph graph,
GraphMetadata metadata, LabelProvider labelProvider, StyleProvider styleProvider) {
Element nodesInfosNode = root.getOwnerDocument().createElement(GROUP);
root.appendChild(nodesInfosNode);
nodesInfosNode.setAttribute(CLASS, StyleClassConstants.LEGEND);
double xInitPos = layoutParameters.getDiagramPadding().getLeft() + CIRCLE_RADIUS_NODE_INFOS_SIZE;
double yPos = graph.getY() - layoutParameters.getVoltageLevelPadding().getTop() + graph.getHeight() + CIRCLE_RADIUS_NODE_INFOS_SIZE;
double xShift = graph.getX() + xInitPos;
for (BusLegendInfo busLegendInfo : labelProvider.getBusLegendInfos(graph)) {
String idNode = metadata.getSvgParameters().getPrefixId() + "NODE_" + busLegendInfo.busId();
Element gNode = nodesInfosNode.getOwnerDocument().createElement(GROUP);
gNode.setAttribute("id", idNode);
List<String> styles = styleProvider.getBusStyles(busLegendInfo.busId(), graph);
drawBusLegendInfo(busLegendInfo, xShift, yPos, gNode, idNode, styles);
nodesInfosNode.appendChild(gNode);
metadata.addBusLegendInfoMetadata(new GraphMetadata.BusLegendInfoMetadata(idNode));
xShift += 2 * CIRCLE_RADIUS_NODE_INFOS_SIZE + 50;
}
}
}