Repository: powsybl/powsybl-diagram
Pull request #670: NAD custom label provider
Author: login: CBiasuzzi
Created: 2025-02-13T18:36:33Z | Merged: 2025-03-25T20:59:15Z

**Please check if the PR fulfills these requirements**
<!-- please use `'[x]'` to check the checkboxes, or submit the PR and then click the checkboxes -->
- [ ] The commit message follows our guidelines
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


**Does this PR already have an issue describing the problem?**
<!-- If so, link to this issue using `'Fixes #XXX'` and skip the rest -->



**What kind of change does this PR introduce?**
<!-- Bug fix, feature, docs update, ... -->

feature:  provides a custom implementation of the LabelProvider; it allows you to specify in a map the branch labels (and arrow directions) to display in the diagram.

**Does this PR introduce a new Powsybl Action implying to be implemented in simulators or pypowsybl?**
- [ ] Yes, the corresponding issue is [here](link)
- [ ] No

**What is the current behavior?**
<!-- You can also link to an open issue here -->



**What is the new behavior (if this is a feature change)?**




**Does this PR introduce a breaking change or deprecate an API?**
- [ ] Yes
- [ ] No

**If yes, please check if the following requirements are fulfilled**
<!-- If no breaking changes or API deprecations were introduced, delete this section -->
- [ ] The *Breaking Change* or *Deprecated* label has been added
- [ ] The migration steps are described in the following section

**What changes might users need to make in their application due to this PR? (migration steps)**
<!-- If this PR introduces a breaking change, describe the migration steps -->
<!-- The content of this section will be copied in the migration guide -->



**Other information**:
<!-- if any of the questions/checkboxes don't apply, please delete them entirely -->



Diff:
diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/svg/CustomLabelProvider.java b/network-area-diagram/src/main/java/com/powsybl/nad/svg/CustomLabelProvider.java
new file mode 100644
index 000000000..e045af37f
--- /dev/null
+++ b/network-area-diagram/src/main/java/com/powsybl/nad/svg/CustomLabelProvider.java
@@ -0,0 +1,131 @@
+/**
+ * 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 com.powsybl.nad.utils.svg.SvgUtils;
+
+import java.util.*;
+
+/**
+ * Enables the configuration of content displayed in the NAD for branches and three-winding-transformers (labels, arrows direction),
+ * and VL info boxes (voltage levels and buses labels).
+ * <p>
+ * Customizations are defined in the constructor's map parameters.
+ *
+ * <p>
+ * The branchLabels map defines what will be displayed on the branches, and it is indexed by branch ID.
+ * The custom content is declared via the BranchLabels record: side1 and side2 are the labels displayed on the edges of a branch,
+ * while middle is the label displayed halfway along the branch.
+ * Arrow1 and arrow2 determine the direction of the arrows displayed on the edges.
+ *
+ * <p>
+ * The threeWtLabels map defines what will be displayed on the three-winding-transformers legs, and it is indexed by the equipment ID.
+ * The custom content is declared via the ThreeWtLabels record: side1, side2, and side3 are the labels to be displayed on the respective transformer's legs.
+ * Similarly, arrow1, arrow2, and arrow3 determine the direction of the arrows displayed on the respective transformer's legs.
+ *
+ * <p>
+ * The busDescriptions map is indexed by the ID of the bus (in the bus view) and allows to set a bus's label, displayed in the VL info box central section.
+ *
+ * <p>
+ * VlDescriptions and vlDetails maps, indexed by the voltage level ID, define the VL related content found in the VL info boxes.
+ * VlDescriptions data will be displayed in the VL info box top section, while vlDetails will be displayed in the bottom section.
+ * For each ID, the maps contain a list of strings that will be displayed sequentially, following the implicit order of the list.
+ *
+ * @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
+ */
+public class CustomLabelProvider implements LabelProvider {
+    final Map<String, BranchLabels> branchLabels;
+    final Map<String, ThreeWtLabels> threeWtLabels;
+    final Map<String, String> busDescriptions;
+    final Map<String, List<String>> vlDescriptions;
+    final Map<String, List<String>> vlDetails;
+
+    public record BranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) {
+    }
+
+    public record ThreeWtLabels(String side1, String side2, String side3, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2, EdgeInfo.Direction arrow3) {
+    }
+
+    public CustomLabelProvider(Map<String, BranchLabels> branchLabels, Map<String, ThreeWtLabels> threeWtLabels,
+                               Map<String, String> busDescriptions, Map<String, List<String>> vlDescriptions, Map<String, List<String>> vlDetails) {
+        this.branchLabels = Objects.requireNonNull(branchLabels);
+        this.threeWtLabels = Objects.requireNonNull(threeWtLabels);
+        this.busDescriptions = Objects.requireNonNull(busDescriptions);
+        this.vlDescriptions = Objects.requireNonNull(vlDescriptions);
+        this.vlDetails = Objects.requireNonNull(vlDetails);
+    }
+
+    @Override
+    public Optional<EdgeInfo> getEdgeInfo(Graph graph, BranchEdge edge, BranchEdge.Side side) {
+        BranchLabels bl = this.branchLabels.get(edge.getEquipmentId());
+        String label = null;
+        EdgeInfo.Direction arrowDirection = null;
+        if (bl != null) {
+            label = side == BranchEdge.Side.ONE ? bl.side1 : bl.side2;
+            arrowDirection = side == BranchEdge.Side.ONE ? bl.arrow1 : bl.arrow2;
+        }
+        return Optional.of(new EdgeInfo("Custom", arrowDirection, null, label));
+    }
+
+    @Override
+    public Optional<EdgeInfo> getEdgeInfo(Graph graph, ThreeWtEdge edge) {
+        ThreeWtLabels threeWtLabels1 = threeWtLabels.get(edge.getEquipmentId());
+        ThreeWtEdge.Side edgeSide = edge.getSide();
+        String labelSide = null;
+        EdgeInfo.Direction arrowDirection = null;
+        if (threeWtLabels1 != null) {
+            switch (edgeSide) {
+                case ONE -> {
+                    labelSide = threeWtLabels1.side1;
+                    arrowDirection = threeWtLabels1.arrow1;
+                }
+                case TWO -> {
+                    labelSide = threeWtLabels1.side2;
+                    arrowDirection = threeWtLabels1.arrow2;
+                }
+                case THREE -> {
+                    labelSide = threeWtLabels1.side3;
+                    arrowDirection = threeWtLabels1.arrow3;
+                }
+            }
+        }
+        return Optional.of(new EdgeInfo("Custom", arrowDirection, null, labelSide));
+    }
+
+    @Override
+    public String getLabel(Edge edge) {
+        BranchLabels bl = branchLabels.get(edge.getEquipmentId());
+        return (bl != null) ? bl.middle : null;
+    }
+
+    @Override
+    public String getBusDescription(BusNode busNode) {
+        return busDescriptions.get(busNode.getEquipmentId());
+    }
+
+    @Override
+    public List<String> getVoltageLevelDescription(VoltageLevelNode voltageLevelNode) {
+        return vlDescriptions.getOrDefault(voltageLevelNode.getEquipmentId(), Collections.emptyList());
+    }
+
+    @Override
+    public List<String> getVoltageLevelDetails(VoltageLevelNode vlNode) {
+        return vlDetails.getOrDefault(vlNode.getEquipmentId(), Collections.emptyList());
+    }
+
+    @Override
+    public String getArrowPathDIn() {
+        return SvgUtils.ARROW_PATH_DIN;
+    }
+
+    @Override
+    public String getArrowPathDOut() {
+        return SvgUtils.ARROW_PATH_DOUT;
+    }
+}
diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/svg/iidm/DefaultLabelProvider.java b/network-area-diagram/src/main/java/com/powsybl/nad/svg/iidm/DefaultLabelProvider.java
index f06e93d98..75077a9ae 100644
--- a/network-area-diagram/src/main/java/com/powsybl/nad/svg/iidm/DefaultLabelProvider.java
+++ b/network-area-diagram/src/main/java/com/powsybl/nad/svg/iidm/DefaultLabelProvider.java
@@ -14,6 +14,7 @@
 import com.powsybl.nad.svg.LabelProvider;
 import com.powsybl.nad.svg.SvgParameters;
 import com.powsybl.nad.utils.iidm.IidmUtils;
+import com.powsybl.nad.utils.svg.SvgUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -121,11 +122,11 @@ public List<String> getVoltageLevelDetails(VoltageLevelNode vlNode) {
 
     @Override
     public String getArrowPathDIn() {
-        return "M-1 -1 H1 L0 1z";
+        return SvgUtils.ARROW_PATH_DIN;
     }
 
     @Override
     public String getArrowPathDOut() {
-        return "M-1 1 H1 L0 -1z";
+        return SvgUtils.ARROW_PATH_DOUT;
     }
 }
diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/utils/svg/SvgUtils.java b/network-area-diagram/src/main/java/com/powsybl/nad/utils/svg/SvgUtils.java
new file mode 100644
index 000000000..f96ae35c7
--- /dev/null
+++ b/network-area-diagram/src/main/java/com/powsybl/nad/utils/svg/SvgUtils.java
@@ -0,0 +1,20 @@
+/**
+ * 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/.
+ */
+package com.powsybl.nad.utils.svg;
+
+/**
+ * @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
+ */
+public final class SvgUtils {
+
+    private SvgUtils() {
+    }
+
+    public static final String ARROW_PATH_DIN = "M-1 -1 H1 L0 1z";
+
+    public static final String ARROW_PATH_DOUT = "M-1 1 H1 L0 -1z";
+}
diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/svg/CustomLabelProviderTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/svg/CustomLabelProviderTest.java
new file mode 100644
index 000000000..6e1bb044a
--- /dev/null
+++ b/network-area-diagram/src/test/java/com/powsybl/nad/svg/CustomLabelProviderTest.java
@@ -0,0 +1,110 @@
+/**
+ * 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.diagram.test.Networks;
+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.svg.iidm.TopologicalStyleProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
+ */
+class CustomLabelProviderTest extends AbstractTest {
+
+    LabelProvider labelProvider;
+
+    @BeforeEach
+    void setup() {
+        setLayoutParameters(new LayoutParameters());
+
+        //Note: SvgParameters EdgeNameDisplayed, VoltageLevelDetails, and BusLegend must be true,
+        // for the edge names, the VL descriptions plus VL details, and bus descriptions to be rendered
+        setSvgParameters(new SvgParameters()
+                .setSvgWidthAndHeightAdded(true)
+                .setFixedWidth(800)
+                .setEdgeNameDisplayed(true)
+                .setVoltageLevelDetails(true)
+                .setBusLegend(true));
+    }
+
+    @Override
+    protected StyleProvider getStyleProvider(Network network) {
+        return new TopologicalStyleProvider(network);
+    }
+
+    @Override
+    protected LabelProvider getLabelProvider(Network network) {
+        return labelProvider;
+    }
+
+    @Test
+    void testCustomLabelProvider() {
+        Network network = Networks.createNodeBreakerNetworkWithBranchStatus("TestNodeDecorators", "test");
+
+        Map<String, CustomLabelProvider.BranchLabels> branchLabels = new HashMap<>();
+        branchLabels.put("L12", new CustomLabelProvider.BranchLabels("L1_1", "L1", "L1_2", EdgeInfo.Direction.IN, EdgeInfo.Direction.IN));
+        branchLabels.put("T12", new CustomLabelProvider.BranchLabels("TWT1_1", "TWT1", "TWT1_2", null, null));
+        branchLabels.put("L11", new CustomLabelProvider.BranchLabels(null, "L2", null, EdgeInfo.Direction.IN, EdgeInfo.Direction.IN));
+        branchLabels.put("T11", new CustomLabelProvider.BranchLabels(null, "TWT2", "TWT2_2", null, EdgeInfo.Direction.OUT));
+
+        Map<String, CustomLabelProvider.ThreeWtLabels> threeWtLabels = new HashMap<>();
+
+        Map<String, String> busDescriptions = new HashMap<>();
+        busDescriptions.put("VL1_10", "VL1 10");
+        busDescriptions.put("VL2_30", "VL2 30");
+
+        Map<String, List<String>> vlDescriptions = new HashMap<>();
+        vlDescriptions.put("VL1", List.of("VL1 description1", "VL1 description2"));
+        vlDescriptions.put("VL2", List.of("VL2 description1"));
+
+        Map<String, List<String>> vlDetails = new HashMap<>();
+        vlDetails.put("VL2", List.of("VL2 details1", "VL2 details2"));
+
+        labelProvider = new CustomLabelProvider(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, vlDetails);
+
+        assertSvgEquals("/custom_label_provider.svg", network);
+    }
+
+    @Test
+    void testCustomLabelProvider3wt() {
+        Network network = ThreeWindingsTransformerNetworkFactory.create();
+
+        Map<String, CustomLabelProvider.BranchLabels> branchLabels = new HashMap<>();
+        Map<String, CustomLabelProvider.ThreeWtLabels> threeWtLabels = new HashMap<>();
+        threeWtLabels.put("3WT", new CustomLabelProvider.ThreeWtLabels("SIDE1", "SIDE2", "SIDE3", EdgeInfo.Direction.IN, EdgeInfo.Direction.OUT, EdgeInfo.Direction.IN));
+
+        Map<String, String> busDescriptions = new HashMap<>();
+        busDescriptions.put("VL_132_0", "VL1 132");
+        busDescriptions.put("VL_33_0", "VL2 33");
+        busDescriptions.put("VL_11_0", "VL2 11");
+
+        Map<String, List<String>> vlDescriptions = new HashMap<>();
+        vlDescriptions.put("VL_132", List.of("VL 132 description1", "VL1 132 description2"));
+        vlDescriptions.put("VL_33", List.of("VL 33 description1"));
+        vlDescriptions.put("VL_11", List.of("VL 11 description1"));
+
+        Map<String, List<String>> vlDetails = new HashMap<>();
+        vlDetails.put("VL_132", List.of("VL 132 details1"));
+        vlDetails.put("VL_33", List.of("VL 33 details1", "VL 33 details2"));
+        vlDetails.put("VL_11", List.of("VL 1 details1"));
+
+        labelProvider = new CustomLabelProvider(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, vlDetails);
+
+        assertSvgEquals("/custom_label_provider_3wt.svg", network);
+    }
+
+}
diff --git a/network-area-diagram/src/test/resources/custom_label_provider.svg b/network-area-diagram/src/test/resources/custom_label_provider.svg
new file mode 100644
index 000000000..86e66d18d
--- /dev/null
+++ b/network-area-diagram/src/test/resources/custom_label_provider.svg
@@ -0,0 +1,324 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="800.00" height="809.23" viewBox="-521.25 -436.10 765.45 774.28" xmlns="http://www.w3.org/2000/svg">
+    <style><![CDATA[
+.nad-branch-edges .nad-edge-path, .nad-3wt-edges .nad-edge-path {stroke: var(--nad-vl-color, lightgrey); stroke-width: 5; fill: none}
+.nad-branch-edges .nad-winding, .nad-3wt-nodes .nad-winding {stroke: var(--nad-vl-color, lightgrey); stroke-width: 5; fill: none}
+.nad-text-edges {stroke: black; stroke-width: 3; stroke-dasharray: 6,7}
+.nad-disconnected .nad-edge-path {stroke-dasharray: 10,10}
+.nad-vl-nodes .nad-busnode {fill: var(--nad-vl-color, lightgrey)}
+.nad-vl-nodes circle.nad-unknown-busnode {stroke: var(--nad-vl-color, #808080); stroke-width: 5; stroke-dasharray: 5,5; fill: none}
+.nad-hvdc-edge polyline.nad-hvdc {stroke: grey; stroke-width: 40}
+.nad-branch-edges .nad-tie-line-edge .nad-edge-path {stroke-width: 7}
+.nad-pst-arrow {stroke: #6a6a6a; stroke-width: 4; stroke-linecap: round; fill: none}
+path.nad-arrow-out:not(.nad-state-out .nad-arrow-out) {visibility: hidden}
+path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden}
+.nad-active {fill: #546e7a}
+.nad-reactive {fill: #0277bd}
+.nad-current {fill: #bd4802}
+.nad-text-background {flood-color: #90a4aeaa}
+.nad-text-nodes {font: 25px serif; fill: black; dominant-baseline: central}
+.nad-text-nodes foreignObject {overflow: visible; color: black}
+.nad-label-box {background-color: #6c6c6c20; width: max-content; padding: 10px; border-radius: 10px;}
+.nad-legend-square {width: 20px; height: 20px; background: var(--nad-vl-color, black);}
+.nad-edge-infos text, .nad-edge-label text {font: 20px serif; dominant-baseline:middle; stroke: #FFFFFFAA; stroke-width: 10; stroke-linejoin:round; paint-order: stroke}
+.nad-disconnected {--nad-vl-color: #808080}
+.nad-vl0to30-line {--nad-vl-color: #afb42b}
+.nad-vl0to30-0 {--nad-vl-color: #827717}
+.nad-vl0to30-1 {--nad-vl-color: #d4e157}
+.nad-vl0to30-2 {--nad-vl-color: #e6ee9c}
+.nad-vl0to30-3 {--nad-vl-color: #c0ca33}
+.nad-vl0to30-4 {--nad-vl-color: #f0fc83}
+.nad-vl0to30-5 {--nad-vl-color: #9e9d24}
+.nad-vl0to30-6 {--nad-vl-color: #cddc39}
+.nad-vl0to30-7 {--nad-vl-color: #dce775}
+.nad-vl0to30-8 {--nad-vl-color: #ddfc88}
+.nad-vl30to50-line {--nad-vl-color: #ef9a9a}
+.nad-vl30to50-0 {--nad-vl-color: #c2185b}
+.nad-vl30to50-1 {--nad-vl-color: #f06292}
+.nad-vl30to50-2 {--nad-vl-color: #d81b60}
+.nad-vl30to50-3 {--nad-vl-color: #ec407a}
+.nad-vl30to50-4 {--nad-vl-color: #880e4f}
+.nad-vl30to50-5 {--nad-vl-color: #ad1457}
+.nad-vl30to50-6 {--nad-vl-color: #e91e63}
+.nad-vl30to50-7 {--nad-vl-color: #f48fb1}
+.nad-vl30to50-8 {--nad-vl-color: #f8bbd0}
+.nad-vl50to70-line {--nad-vl-color: #9c27b0}
+.nad-vl50to70-0 {--nad-vl-color: #7b1fa2}
+.nad-vl50to70-1 {--nad-vl-color: #ba68c8}
+.nad-vl50to70-2 {--nad-vl-color: #512da8}
+.nad-vl50to70-3 {--nad-vl-color: #ab47bc}
+.nad-vl50to70-4 {--nad-vl-color: #e1bee7}
+.nad-vl50to70-5 {--nad-vl-color: #6a1b9a}
+.nad-vl50to70-6 {--nad-vl-color: #4a148c}
+.nad-vl50to70-7 {--nad-vl-color: #ce93d8}
+.nad-vl50to70-8 {--nad-vl-color: #9575cd}
+.nad-vl70to120-line {--nad-vl-color: #e65100}
+.nad-vl70to120-0 {--nad-vl-color: #fb8c00}
+.nad-vl70to120-1 {--nad-vl-color: #ffb74d}
+.nad-vl70to120-2 {--nad-vl-color: #f57c00}
+.nad-vl70to120-3 {--nad-vl-color: #ffa726}
+.nad-vl70to120-4 {--nad-vl-color: #ffe0b2}
+.nad-vl70to120-5 {--nad-vl-color: #ef6c00}
+.nad-vl70to120-6 {--nad-vl-color: #ff9800}
+.nad-vl70to120-7 {--nad-vl-color: #ffcc80}
+.nad-vl70to120-8 {--nad-vl-color: #fff3e0}
+.nad-vl120to180-line {--nad-vl-color: #00ACC1}
+.nad-vl120to180-0 {--nad-vl-color: #4fc3f7}
+.nad-vl120to180-1 {--nad-vl-color: #01579b}
+.nad-vl120to180-2 {--nad-vl-color: #b3e5fc}
+.nad-vl120to180-3 {--nad-vl-color: #039be5}
+.nad-vl120to180-4 {--nad-vl-color: #81d4fa}
+.nad-vl120to180-5 {--nad-vl-color: #0288d1}
+.nad-vl120to180-6 {--nad-vl-color: #29b6f6}
+.nad-vl120to180-7 {--nad-vl-color: #0277bd}
+.nad-vl120to180-8 {--nad-vl-color: #03a9f4}
+.nad-vl180to300-line {--nad-vl-color: #2e7d32}
+.nad-vl180to300-0 {--nad-vl-color: #81c784}
+.nad-vl180to300-1 {--nad-vl-color: #558b2f}
+.nad-vl180to300-2 {--nad-vl-color: #c8e6c9}
+.nad-vl180to300-3 {--nad-vl-color: #43a047}
+.nad-vl180to300-4 {--nad-vl-color: #a5d6a7}
+.nad-vl180to300-5 {--nad-vl-color: #388e3c}
+.nad-vl180to300-6 {--nad-vl-color: #66bb6a}
+.nad-vl180to300-7 {--nad-vl-color: #1b5e20}
+.nad-vl180to300-8 {--nad-vl-color: #4caf50}
+.nad-vl300to500-line {--nad-vl-color: #d32f2f}
+.nad-vl300to500-0 {--nad-vl-color: #ef5350}
+.nad-vl300to500-1 {--nad-vl-color: #ef9a9a}
+.nad-vl300to500-2 {--nad-vl-color: #b71c1c}
+.nad-vl300to500-3 {--nad-vl-color: #e57373}
+.nad-vl300to500-4 {--nad-vl-color: #e53935}
+.nad-vl300to500-5 {--nad-vl-color: #ff8a80}
+.nad-vl300to500-6 {--nad-vl-color: #f44336}
+.nad-vl300to500-7 {--nad-vl-color: #ffcdd2}
+.nad-vl300to500-8 {--nad-vl-color: #c62828}
+.nad-branch-edges .nad-overload .nad-edge-path {animation: line-blink 3s infinite}
+.nad-vl-nodes .nad-overvoltage {animation: node-over-blink 3s infinite}
+.nad-vl-nodes .nad-undervoltage {animation: node-under-blink 3s infinite}
+
+@keyframes line-blink {
+  0%, 80%, 100% {stroke: var(--nad-vl-color, black); stroke-width: 5}
+  40% {stroke: #FFEB3B; stroke-width: 15}
+}
+@keyframes node-over-blink {
+  0%, 80%, 100% {stroke: white; stroke-width: 0}
+  40% {stroke: #ff5722; stroke-width: 15}
+}
+@keyframes node-under-blink {
+  0%, 80%, 100% {stroke: white; stroke-width: 0}
+  40% {stroke: #00BCD4; stroke-width: 15}
+}
+]]></style>
+    <g class="nad-vl-nodes">
+        <g transform="translate(-55.80,-196.10)" id="0">
+            <circle r="27.50" id="1" class="nad-vl300to500-0 nad-busnode"/>
+        </g>
+        <g transform="translate(-321.25,57.51)" id="2">
+            <circle r="27.50" id="3" class="nad-vl300to500-0 nad-busnode"/>
+        </g>
+    </g>
+    <g class="nad-branch-edges">
+        <g id="4">
+            <g id="4.1" class="nad-vl300to500-line">
+                <path class="nad-edge-path" d="M-74.80,-215.99 L-111.07,-253.95 C-138.70,-282.87 -123.08,-303.22 -84.21,-312.69"/>
+                <g class="nad-edge-infos" transform="translate(-111.07,-253.95)">
+                    <g class="nad-state-in">
+                        <g transform="rotate(-43.69)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                    </g>
+                </g>
+            </g>
+            <g id="4.2" class="nad-vl300to500-line">
+                <path class="nad-edge-path" d="M-48.08,-222.50 L-33.34,-272.89 C-22.11,-311.28 -45.35,-322.16 -84.21,-312.69"/>
+                <g class="nad-edge-infos" transform="translate(-33.34,-272.89)">
+                    <g class="nad-state-in">
+                        <g transform="rotate(16.31)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                    </g>
+                </g>
+            </g>
+            <g>
+                <g class="nad-edge-label" transform="translate(-84.21,-312.69)">
+                    <text transform="rotate(-13.69)" x="0.00" style="text-anchor:middle">L2</text>
+                </g>
+            </g>
+        </g>
+        <g id="5">
+            <g id="5.1" class="nad-vl300to500-line">
+                <polyline class="nad-edge-path" points="-82.52,-189.59 -133.53,-177.16 -216.16,-98.22"/>
+                <g class="nad-edge-infos" transform="translate(-155.22,-156.44)">
+                    <g class="nad-state-in">
+                        <g transform="rotate(-133.69)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                        <text transform="rotate(-43.69)" x="-19.00" style="text-anchor:end">L1_1</text>
+                    </g>
+                </g>
+            </g>
+            <g id="5.2" class="nad-vl300to500-line">
+                <polyline class="nad-edge-path" points="-313.53,31.12 -298.79,-19.27 -216.16,-98.22"/>
+                <g class="nad-edge-infos" transform="translate(-277.10,-39.99)">
+                    <g class="nad-state-in">
+                        <g transform="rotate(46.31)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                        <text transform="rotate(-43.69)" x="19.00">L1_2</text>
+                    </g>
+                </g>
+            </g>
+            <g>
+                <g class="nad-edge-label" transform="translate(-216.16,-98.22)">
+                    <text transform="rotate(-43.69)" x="0.00" style="text-anchor:middle">L1</text>
+                </g>
+            </g>
+        </g>
+        <g id="6">
+            <g id="6.1" class="nad-vl300to500-line">
+                <path class="nad-edge-path" d="M-29.09,-202.61 L21.92,-215.04 C60.79,-224.51 70.60,-200.80 67.79,-191.21"/>
+                <g class="nad-edge-infos" transform="translate(21.92,-215.04)">
+                    <g>
+                        <g transform="rotate(76.31)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                    </g>
+                </g>
+            </g>
+            <g id="6.2" class="nad-vl300to500-line">
+                <path class="nad-edge-path" d="M-36.81,-176.22 L-0.54,-138.26 C27.09,-109.34 48.14,-124.02 50.95,-133.62"/>
+                <g class="nad-edge-infos" transform="translate(-0.54,-138.26)">
+                    <g class="nad-state-out">
+                        <g transform="rotate(136.31)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                        <text transform="rotate(46.31)" x="19.00">TWT2_2</text>
+                    </g>
+                </g>
+            </g>
+            <g>
+                <circle class="nad-vl300to500-line nad-winding" cx="62.18" cy="-172.01" r="20.00"/>
+                <circle class="nad-vl300to500-line nad-winding" cx="56.56" cy="-152.81" r="20.00"/>
+                <g class="nad-edge-label" transform="translate(59.37,-162.41)">
+                    <text transform="rotate(-73.69)" x="0.00" style="text-anchor:middle">TWT2</text>
+                </g>
+            </g>
+        </g>
+        <g id="7">
+            <g id="7.1" class="nad-vl300to500-line">
+                <polyline class="nad-edge-path" points="-63.52,-169.71 -78.26,-119.32 -139.20,-61.10"/>
+                <g class="nad-edge-infos" transform="translate(-99.96,-98.60)">
+                    <g>
+                        <g transform="rotate(-133.69)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                        <text transform="rotate(-43.69)" x="-19.00" style="text-anchor:end">TWT1_1</text>
+                    </g>
+                </g>
+            </g>
+            <g id="7.2" class="nad-vl300to500-line">
+                <polyline class="nad-edge-path" points="-294.53,51.00 -243.53,38.57 -182.59,-19.65"/>
+                <g class="nad-edge-infos" transform="translate(-221.83,17.85)">
+                    <g>
+                        <g transform="rotate(46.31)">
+                            <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                            <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                        </g>
+                        <text transform="rotate(-43.69)" x="19.00">TWT1_2</text>
+                    </g>
+                </g>
+            </g>
+            <g>
+                <circle class="nad-vl300to500-line nad-winding" cx="-153.66" cy="-47.28" r="20.00"/>
+                <circle class="nad-vl300to500-line nad-winding" cx="-168.12" cy="-33.47" r="20.00"/>
+                <g class="nad-edge-label" transform="translate(-160.89,-40.37)">
+                    <text transform="rotate(-43.69)" x="0.00" style="text-anchor:middle">TWT1</text>
+                </g>
+            </g>
+        </g>
+    </g>
+    <g class="nad-3wt-edges">
+        <g id="9" class="nad-vl300to500-line">
+            <polyline class="nad-edge-path" points="-50.52,-169.12 31.80,115.04"/>
+            <g class="nad-edge-infos" transform="translate(-41.47,-137.90)">
+                <g>
+                    <g transform="rotate(163.84)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                </g>
+            </g>
+        </g>
+        <g id="10" class="nad-vl300to500-line">
+            <polyline class="nad-edge-path" points="-50.52,-169.12 18.67,168.89"/>
+            <g class="nad-edge-infos" transform="translate(-44.00,-137.28)">
+                <g>
+                    <g transform="rotate(168.43)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                </g>
+            </g>
+        </g>
+        <g id="11" class="nad-vl300to500-line">
+            <polyline class="nad-edge-path" points="-294.53,64.03 -21.40,130.60"/>
+            <g class="nad-edge-infos" transform="translate(-262.96,71.72)">
+                <g>
+                    <g transform="rotate(103.70)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+    <g class="nad-3wt-nodes">
+        <g id="8" transform="translate(9.69,138.18)">
+            <circle class="nad-vl300to500-line nad-winding" cx="8.29" cy="-8.68" r="20.00"/>
+            <circle class="nad-vl300to500-line nad-winding" cx="3.37" cy="11.52" r="20.00"/>
+            <circle class="nad-vl300to500-line nad-winding" cx="-11.66" cy="-2.84" r="20.00"/>
+        </g>
+    </g>
+    <g class="nad-text-edges">
+        <polyline id="0-textedge" points="-26.14,-200.55 44.20,-211.10"/>
+        <polyline id="2-textedge" points="-291.58,53.06 -221.25,42.51"/>
+    </g>
+    <g class="nad-text-nodes">
+        <foreignObject id="0-textnode" y="-236.10" x="44.20" height="1" width="1">
+            <div xmlns="http://www.w3.org/1999/xhtml" class="nad-label-box">
+                <div>VL1 description1</div>
+                <div>VL1 description2</div>
+                <table>
+                    <tr>
+                        <td>
+                            <div class="nad-vl300to500-0 nad-legend-square"/>
+                        </td>
+                        <td>VL1 10</td>
+                    </tr>
+                </table>
+            </div>
+        </foreignObject>
+        <foreignObject id="2-textnode" y="17.51" x="-221.25" height="1" width="1">
+            <div xmlns="http://www.w3.org/1999/xhtml" class="nad-label-box">
+                <div>VL2 description1</div>
+                <table>
+                    <tr>
+                        <td>
+                            <div class="nad-vl300to500-0 nad-legend-square"/>
+                        </td>
+                        <td>VL2 30</td>
+                    </tr>
+                </table>
+                <div>VL2 details1</div>
+                <div>VL2 details2</div>
+            </div>
+        </foreignObject>
+    </g>
+</svg>
diff --git a/network-area-diagram/src/test/resources/custom_label_provider_3wt.svg b/network-area-diagram/src/test/resources/custom_label_provider_3wt.svg
new file mode 100644
index 000000000..6e659b8d2
--- /dev/null
+++ b/network-area-diagram/src/test/resources/custom_label_provider_3wt.svg
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="800.00" height="881.16" viewBox="-571.52 -753.76 1051.74 1158.44" xmlns="http://www.w3.org/2000/svg">
+    <style><![CDATA[
+.nad-branch-edges .nad-edge-path, .nad-3wt-edges .nad-edge-path {stroke: var(--nad-vl-color, lightgrey); stroke-width: 5; fill: none}
+.nad-branch-edges .nad-winding, .nad-3wt-nodes .nad-winding {stroke: var(--nad-vl-color, lightgrey); stroke-width: 5; fill: none}
+.nad-text-edges {stroke: black; stroke-width: 3; stroke-dasharray: 6,7}
+.nad-disconnected .nad-edge-path {stroke-dasharray: 10,10}
+.nad-vl-nodes .nad-busnode {fill: var(--nad-vl-color, lightgrey)}
+.nad-vl-nodes circle.nad-unknown-busnode {stroke: var(--nad-vl-color, #808080); stroke-width: 5; stroke-dasharray: 5,5; fill: none}
+.nad-hvdc-edge polyline.nad-hvdc {stroke: grey; stroke-width: 40}
+.nad-branch-edges .nad-tie-line-edge .nad-edge-path {stroke-width: 7}
+.nad-pst-arrow {stroke: #6a6a6a; stroke-width: 4; stroke-linecap: round; fill: none}
+path.nad-arrow-out:not(.nad-state-out .nad-arrow-out) {visibility: hidden}
+path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden}
+.nad-active {fill: #546e7a}
+.nad-reactive {fill: #0277bd}
+.nad-current {fill: #bd4802}
+.nad-text-background {flood-color: #90a4aeaa}
+.nad-text-nodes {font: 25px serif; fill: black; dominant-baseline: central}
+.nad-text-nodes foreignObject {overflow: visible; color: black}
+.nad-label-box {background-color: #6c6c6c20; width: max-content; padding: 10px; border-radius: 10px;}
+.nad-legend-square {width: 20px; height: 20px; background: var(--nad-vl-color, black);}
+.nad-edge-infos text, .nad-edge-label text {font: 20px serif; dominant-baseline:middle; stroke: #FFFFFFAA; stroke-width: 10; stroke-linejoin:round; paint-order: stroke}
+.nad-disconnected {--nad-vl-color: #808080}
+.nad-vl0to30-line {--nad-vl-color: #afb42b}
+.nad-vl0to30-0 {--nad-vl-color: #827717}
+.nad-vl0to30-1 {--nad-vl-color: #d4e157}
+.nad-vl0to30-2 {--nad-vl-color: #e6ee9c}
+.nad-vl0to30-3 {--nad-vl-color: #c0ca33}
+.nad-vl0to30-4 {--nad-vl-color: #f0fc83}
+.nad-vl0to30-5 {--nad-vl-color: #9e9d24}
+.nad-vl0to30-6 {--nad-vl-color: #cddc39}
+.nad-vl0to30-7 {--nad-vl-color: #dce775}
+.nad-vl0to30-8 {--nad-vl-color: #ddfc88}
+.nad-vl30to50-line {--nad-vl-color: #ef9a9a}
+.nad-vl30to50-0 {--nad-vl-color: #c2185b}
+.nad-vl30to50-1 {--nad-vl-color: #f06292}
+.nad-vl30to50-2 {--nad-vl-color: #d81b60}
+.nad-vl30to50-3 {--nad-vl-color: #ec407a}
+.nad-vl30to50-4 {--nad-vl-color: #880e4f}
+.nad-vl30to50-5 {--nad-vl-color: #ad1457}
+.nad-vl30to50-6 {--nad-vl-color: #e91e63}
+.nad-vl30to50-7 {--nad-vl-color: #f48fb1}
+.nad-vl30to50-8 {--nad-vl-color: #f8bbd0}
+.nad-vl50to70-line {--nad-vl-color: #9c27b0}
+.nad-vl50to70-0 {--nad-vl-color: #7b1fa2}
+.nad-vl50to70-1 {--nad-vl-color: #ba68c8}
+.nad-vl50to70-2 {--nad-vl-color: #512da8}
+.nad-vl50to70-3 {--nad-vl-color: #ab47bc}
+.nad-vl50to70-4 {--nad-vl-color: #e1bee7}
+.nad-vl50to70-5 {--nad-vl-color: #6a1b9a}
+.nad-vl50to70-6 {--nad-vl-color: #4a148c}
+.nad-vl50to70-7 {--nad-vl-color: #ce93d8}
+.nad-vl50to70-8 {--nad-vl-color: #9575cd}
+.nad-vl70to120-line {--nad-vl-color: #e65100}
+.nad-vl70to120-0 {--nad-vl-color: #fb8c00}
+.nad-vl70to120-1 {--nad-vl-color: #ffb74d}
+.nad-vl70to120-2 {--nad-vl-color: #f57c00}
+.nad-vl70to120-3 {--nad-vl-color: #ffa726}
+.nad-vl70to120-4 {--nad-vl-color: #ffe0b2}
+.nad-vl70to120-5 {--nad-vl-color: #ef6c00}
+.nad-vl70to120-6 {--nad-vl-color: #ff9800}
+.nad-vl70to120-7 {--nad-vl-color: #ffcc80}
+.nad-vl70to120-8 {--nad-vl-color: #fff3e0}
+.nad-vl120to180-line {--nad-vl-color: #00ACC1}
+.nad-vl120to180-0 {--nad-vl-color: #4fc3f7}
+.nad-vl120to180-1 {--nad-vl-color: #01579b}
+.nad-vl120to180-2 {--nad-vl-color: #b3e5fc}
+.nad-vl120to180-3 {--nad-vl-color: #039be5}
+.nad-vl120to180-4 {--nad-vl-color: #81d4fa}
+.nad-vl120to180-5 {--nad-vl-color: #0288d1}
+.nad-vl120to180-6 {--nad-vl-color: #29b6f6}
+.nad-vl120to180-7 {--nad-vl-color: #0277bd}
+.nad-vl120to180-8 {--nad-vl-color: #03a9f4}
+.nad-vl180to300-line {--nad-vl-color: #2e7d32}
+.nad-vl180to300-0 {--nad-vl-color: #81c784}
+.nad-vl180to300-1 {--nad-vl-color: #558b2f}
+.nad-vl180to300-2 {--nad-vl-color: #c8e6c9}
+.nad-vl180to300-3 {--nad-vl-color: #43a047}
+.nad-vl180to300-4 {--nad-vl-color: #a5d6a7}
+.nad-vl180to300-5 {--nad-vl-color: #388e3c}
+.nad-vl180to300-6 {--nad-vl-color: #66bb6a}
+.nad-vl180to300-7 {--nad-vl-color: #1b5e20}
+.nad-vl180to300-8 {--nad-vl-color: #4caf50}
+.nad-vl300to500-line {--nad-vl-color: #d32f2f}
+.nad-vl300to500-0 {--nad-vl-color: #ef5350}
+.nad-vl300to500-1 {--nad-vl-color: #ef9a9a}
+.nad-vl300to500-2 {--nad-vl-color: #b71c1c}
+.nad-vl300to500-3 {--nad-vl-color: #e57373}
+.nad-vl300to500-4 {--nad-vl-color: #e53935}
+.nad-vl300to500-5 {--nad-vl-color: #ff8a80}
+.nad-vl300to500-6 {--nad-vl-color: #f44336}
+.nad-vl300to500-7 {--nad-vl-color: #ffcdd2}
+.nad-vl300to500-8 {--nad-vl-color: #c62828}
+.nad-branch-edges .nad-overload .nad-edge-path {animation: line-blink 3s infinite}
+.nad-vl-nodes .nad-overvoltage {animation: node-over-blink 3s infinite}
+.nad-vl-nodes .nad-undervoltage {animation: node-under-blink 3s infinite}
+
+@keyframes line-blink {
+  0%, 80%, 100% {stroke: var(--nad-vl-color, black); stroke-width: 5}
+  40% {stroke: #FFEB3B; stroke-width: 15}
+}
+@keyframes node-over-blink {
+  0%, 80%, 100% {stroke: white; stroke-width: 0}
+  40% {stroke: #ff5722; stroke-width: 15}
+}
+@keyframes node-under-blink {
+  0%, 80%, 100% {stroke: white; stroke-width: 0}
+  40% {stroke: #00BCD4; stroke-width: 15}
+}
+]]></style>
+    <g class="nad-vl-nodes">
+        <g transform="translate(75.04,-513.76)" id="0">
+            <circle r="27.50" id="1" class="nad-vl0to30-0 nad-busnode"/>
+        </g>
+        <g transform="translate(-371.52,47.69)" id="2">
+            <circle r="27.50" id="3" class="nad-vl120to180-0 nad-busnode"/>
+        </g>
+        <g transform="translate(180.23,204.68)" id="4">
+            <circle r="27.50" id="5" class="nad-vl30to50-0 nad-busnode"/>
+        </g>
+    </g>
+    <g class="nad-branch-edges"></g>
+    <g class="nad-3wt-edges">
+        <g id="7" class="nad-vl0to30-line">
+            <polyline class="nad-edge-path" points="68.27,-487.11 -14.91,-159.64"/>
+            <g class="nad-edge-infos" transform="translate(60.27,-455.61)">
+                <g class="nad-state-in">
+                    <g transform="rotate(-165.75)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                    <text transform="rotate(-75.75)" x="-19.00" style="text-anchor:end">SIDE3</text>
+                </g>
+            </g>
+        </g>
+        <g id="8" class="nad-vl120to180-line">
+            <polyline class="nad-edge-path" points="-346.98,35.28 -53.59,-119.94"/>
+            <g class="nad-edge-infos" transform="translate(-318.25,20.09)">
+                <g class="nad-state-in">
+                    <g transform="rotate(62.12)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                    <text transform="rotate(-27.88)" x="19.00">SIDE1</text>
+                </g>
+            </g>
+        </g>
+        <g id="9" class="nad-vl30to50-line">
+            <polyline class="nad-edge-path" points="165.92,181.19 0.13,-106.29"/>
+            <g class="nad-edge-infos" transform="translate(149.68,153.04)">
+                <g class="nad-state-out">
+                    <g transform="rotate(-29.97)">
+                        <path class="nad-arrow-in" transform="scale(10.00)" d="M-1 -1 H1 L0 1z"/>
+                        <path class="nad-arrow-out" transform="scale(10.00)" d="M-1 1 H1 L0 -1z"/>
+                    </g>
+                    <text transform="rotate(-299.97)" x="-19.00" style="text-anchor:end">SIDE2</text>
+                </g>
+            </g>
+        </g>
+    </g>
+    <g class="nad-3wt-nodes">
+        <g id="6" transform="translate(-22.79,-128.62)">
+            <circle class="nad-vl0to30-line nad-winding" cx="2.95" cy="-11.63" r="20.00"/>
+            <circle class="nad-vl120to180-line nad-winding" cx="-11.55" cy="3.26" r="20.00"/>
+            <circle class="nad-vl30to50-line nad-winding" cx="8.60" cy="8.37" r="20.00"/>
+        </g>
+    </g>
+    <g class="nad-text-edges">
+        <polyline id="0-textedge" points="104.71,-518.21 175.04,-528.76"/>
+        <polyline id="2-textedge" points="-341.85,43.24 -271.52,32.69"/>
+        <polyline id="4-textedge" points="209.89,200.23 280.23,189.68"/>
+    </g>
+    <g class="nad-text-nodes">
+        <foreignObject id="0-textnode" y="-553.76" x="175.04" height="1" width="1">
+            <div xmlns="http://www.w3.org/1999/xhtml" class="nad-label-box">
+                <div>VL 11 description1</div>
+                <table>
+                    <tr>
+                        <td>
+                            <div class="nad-vl0to30-0 nad-legend-square"/>
+                        </td>
+                        <td>VL2 11</td>
+                    </tr>
+                </table>
+                <div>VL 1 details1</div>
+            </div>
+        </foreignObject>
+        <foreignObject id="2-textnode" y="7.69" x="-271.52" height="1" width="1">
+            <div xmlns="http://www.w3.org/1999/xhtml" class="nad-label-box">
+                <div>VL 132 description1</div>
+                <div>VL1 132 description2</div>
+                <table>
+                    <tr>
+                        <td>
+                            <div class="nad-vl120to180-0 nad-legend-square"/>
+                        </td>
+                        <td>VL1 132</td>
+                    </tr>
+                </table>
+                <div>VL 132 details1</div>
+            </div>
+        </foreignObject>
+        <foreignObject id="4-textnode" y="164.68" x="280.23" height="1" width="1">
+            <div xmlns="http://www.w3.org/1999/xhtml" class="nad-label-box">
+                <div>VL 33 description1</div>
+                <table>
+                    <tr>
+                        <td>
+                            <div class="nad-vl30to50-0 nad-legend-square"/>
+                        </td>
+                        <td>VL2 33</td>
+                    </tr>
+                </table>
+                <div>VL 33 details1</div>
+                <div>VL 33 details2</div>
+            </div>
+        </foreignObject>
+    </g>
+</svg>


Reviews:
user: login: flo-dup
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-25T11:48:25Z
user: login: flo-dup
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-25T20:58:22Z

Review comments:
diff_hunk: @@ -0,0 +1,107 @@
+/**
+ * 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 com.powsybl.nad.utils.svg.SvgUtils;
+
+import java.util.*;
+
+/**
+ * @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
+ */
+public class CustomLabelProvider implements LabelProvider {
+    final Map<String, CustomBranchLabels> branchLabels;
+    final Map<String, CustomThreeWtLabels> threeWtLabels;
+    final Map<String, String> busDescriptions;
+    final Map<String, List<String>> vlDescriptions;
+    final Map<String, List<String>> vlDetails;
+
+    public record CustomBranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) {
path: network-area-diagram/src/main/java/com/powsybl/nad/svg/CustomLabelProvider.java
user: login: flo-dup
body: As you're within the CustomLabelProvider, you can remove the Custom prefix (same for the two other records).
```suggestion
    public record BranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) {
```
created_at: 2025-03-25T11:46:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,107 @@
+/**
+ * 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 com.powsybl.nad.utils.svg.SvgUtils;
+
+import java.util.*;
+
+/**
+ * @author Christian Biasuzzi {@literal <christian.biasuzzi at soft.it>}
+ */
+public class CustomLabelProvider implements LabelProvider {
+    final Map<String, CustomBranchLabels> branchLabels;
+    final Map<String, CustomThreeWtLabels> threeWtLabels;
+    final Map<String, String> busDescriptions;
+    final Map<String, List<String>> vlDescriptions;
+    final Map<String, List<String>> vlDetails;
+
+    public record CustomBranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) {
+    }
+
+    public record CustomThreeWtLabels(String side1, String side2, String side3, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2, EdgeInfo.Direction arrow3) {
+    }
+
+    public CustomLabelProvider(Map<String, CustomBranchLabels> branchLabels, Map<String, CustomThreeWtLabels> threeWtLabels,
path: network-area-diagram/src/main/java/com/powsybl/nad/svg/CustomLabelProvider.java
user: login: flo-dup
body: Could you add a basic javadoc, just to tell the users what's expected in each maps, in particular that it's indexed based on the branch id / the id of the bus (of the busview!) / the voltage level id
created_at: 2025-03-25T11:47:59Z
author_association: COLLABORATOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-25T19:50:35Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=com.powsybl%3Apowsybl-diagram&pullRequest=670) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=com.powsybl%3Apowsybl-diagram&pullRequest=670&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=com.powsybl%3Apowsybl-diagram&pullRequest=670&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=com.powsybl%3Apowsybl-diagram&pullRequest=670&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [94.4% Coverage on New Code](https://sonarcloud.io/component_measures?id=com.powsybl%3Apowsybl-diagram&pullRequest=670&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=com.powsybl%3Apowsybl-diagram&pullRequest=670&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=com.powsybl%3Apowsybl-diagram&pullRequest=670)


author_association: NONE