fix(class): Self-referential class multiplicity labels rendered multiple times

Fixes #7560 where cardinality labels (e.g. "1", "0..1") were displayed 3x
on self-referential class diagram relationships.

Root cause: The dagre layout splits self-loops into 3 edges but
structuredClone copied cardinality labels to all of them. Now each
segment only carries its relevant cardinality label. Also fix DOM
hierarchy bug in edge label creation where labels were appended to
the wrong parent element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
GhassenS
2026-04-04 23:42:18 +01:00
parent 2b938d0e55
commit fedf70cc3c
3 changed files with 7 additions and 20 deletions
+1 -12
View File
@@ -97,16 +97,10 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.startLabelLeft);
}
if (edge.startLabelRight) {
// Create the actual text element
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
const startLabelElement = await createLabel(
startEdgeLabelRight,
edge.startLabelRight,
edge.labelStyle
);
const startLabelElement = await createLabel(inner, edge.startLabelRight, edge.labelStyle);
fo = startLabelElement;
inner.node().appendChild(startLabelElement);
let slBox = startLabelElement.getBBox();
if (useHtmlLabels) {
const div = startLabelElement.children[0];
@@ -124,7 +118,6 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.startLabelRight);
}
if (edge.endLabelLeft) {
// Create the actual text element
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
const endLabelElement = await createLabel(inner, edge.endLabelLeft, edge.labelStyle);
@@ -139,8 +132,6 @@ export const insertEdgeLabel = async (elem, edge) => {
}
inner.attr('transform', computeLabelTransform(slBox, useHtmlLabels));
endEdgeLabelLeft.node().appendChild(endLabelElement);
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
@@ -148,7 +139,6 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.endLabelLeft);
}
if (edge.endLabelRight) {
// Create the actual text element
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
const endLabelElement = await createLabel(inner, edge.endLabelRight, edge.labelStyle);
@@ -163,7 +153,6 @@ export const insertEdgeLabel = async (elem, edge) => {
}
inner.attr('transform', computeLabelTransform(slBox, useHtmlLabels));
endEdgeLabelRight.node().appendChild(endLabelElement);
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
}
@@ -345,11 +345,17 @@ export const render = async (data4Layout, svg) => {
const edge2 = structuredClone(edge);
edge1.label = '';
edge1.arrowTypeEnd = 'none';
edge1.endLabelLeft = '';
edge1.id = nodeId + '-cyclic-special-1';
edgeMid.label = '';
edgeMid.startLabelRight = '';
edgeMid.endLabelLeft = '';
edgeMid.arrowTypeStart = 'none';
edgeMid.arrowTypeEnd = 'none';
edgeMid.id = nodeId + '-cyclic-special-mid';
edge2.label = '';
edge2.startLabelRight = '';
edge2.arrowTypeStart = 'none';
if (node.isGroup) {
edge1.fromCluster = nodeId;
edge2.toCluster = nodeId;
@@ -148,7 +148,6 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.startLabelLeft);
}
if (edge.startLabelRight) {
// Create the actual text element
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
const startLabelElement = await createLabel(
@@ -159,7 +158,6 @@ export const insertEdgeLabel = async (elem, edge) => {
false
);
fo = startLabelElement;
inner.node().appendChild(startLabelElement);
let slBox = startLabelElement.getBBox();
if (useHtmlLabels) {
const div = startLabelElement.children[0];
@@ -177,7 +175,6 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.startLabelRight);
}
if (edge.endLabelLeft) {
// Create the actual text element
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
const endLabelElement = await createLabel(
@@ -198,8 +195,6 @@ export const insertEdgeLabel = async (elem, edge) => {
}
inner.attr('transform', computeLabelTransform(slBox, useHtmlLabels));
endEdgeLabelLeft.node().appendChild(endLabelElement);
if (!terminalLabels.get(edge.id)) {
terminalLabels.set(edge.id, {});
}
@@ -207,10 +202,8 @@ export const insertEdgeLabel = async (elem, edge) => {
setTerminalWidth(fo, edge.endLabelLeft);
}
if (edge.endLabelRight) {
// Create the actual text element
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
const endLabelElement = await createLabel(
inner,
edge.endLabelRight,
@@ -229,7 +222,6 @@ export const insertEdgeLabel = async (elem, edge) => {
}
inner.attr('transform', computeLabelTransform(slBox, useHtmlLabels));
endEdgeLabelRight.node().appendChild(endLabelElement);
if (!terminalLabels.get(edge.id)) {
terminalLabels.set(edge.id, {});
}