Skip to content

Commit 1d6a55a

Browse files
Merge pull request #6371 from mermaid-js/6369-mirror-edge-color-on-arrowhead
6369 mirror edge color on arrowhead
2 parents bc2bd3d + 4d25cab commit 1d6a55a

File tree

4 files changed

+89
-25
lines changed

4 files changed

+89
-25
lines changed

.changeset/vast-nails-stay.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mermaid': minor
3+
---
4+
5+
The arrowhead color should match the color of the edge. Creates a unique clone of the arrow marker with the appropriate color.

cypress/integration/rendering/flowchart.spec.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ graph TD
895895
imgSnapshotTest(
896896
`
897897
graph TD
898-
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
898+
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
899899
hello --> default
900900
`,
901901
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
@@ -917,4 +917,21 @@ graph TD
917917
}
918918
);
919919
});
920+
it('#6369: edge color should affect arrow head', () => {
921+
imgSnapshotTest(
922+
`
923+
flowchart LR
924+
A --> B
925+
A --> C
926+
C --> D
927+
928+
linkStyle 0 stroke:#D50000
929+
linkStyle 2 stroke:#D50000
930+
`,
931+
{
932+
flowchart: { htmlLabels: true },
933+
securityLevel: 'loose',
934+
}
935+
);
936+
});
920937
});

packages/mermaid/src/rendering-util/rendering-elements/edgeMarker.ts

+59-18
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,29 @@ export const addEdgeMarkers = (
1515
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
1616
url: string,
1717
id: string,
18-
diagramType: string
18+
diagramType: string,
19+
strokeColor?: string
1920
) => {
2021
if (edge.arrowTypeStart) {
21-
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
22+
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType, strokeColor);
2223
}
2324
if (edge.arrowTypeEnd) {
24-
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
25+
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType, strokeColor);
2526
}
2627
};
2728

2829
const arrowTypesMap = {
29-
arrow_cross: 'cross',
30-
arrow_point: 'point',
31-
arrow_barb: 'barb',
32-
arrow_circle: 'circle',
33-
aggregation: 'aggregation',
34-
extension: 'extension',
35-
composition: 'composition',
36-
dependency: 'dependency',
37-
lollipop: 'lollipop',
38-
requirement_arrow: 'requirement_arrow',
39-
requirement_contains: 'requirement_contains',
30+
arrow_cross: { type: 'cross', fill: false },
31+
arrow_point: { type: 'point', fill: true },
32+
arrow_barb: { type: 'barb', fill: true },
33+
arrow_circle: { type: 'circle', fill: false },
34+
aggregation: { type: 'aggregation', fill: false },
35+
extension: { type: 'extension', fill: false },
36+
composition: { type: 'composition', fill: true },
37+
dependency: { type: 'dependency', fill: true },
38+
lollipop: { type: 'lollipop', fill: false },
39+
requirement_arrow: { type: 'requirement_arrow', fill: false },
40+
requirement_contains: { type: 'requirement_contains', fill: false },
4041
} as const;
4142

4243
const addEdgeMarker = (
@@ -45,15 +46,55 @@ const addEdgeMarker = (
4546
arrowType: string,
4647
url: string,
4748
id: string,
48-
diagramType: string
49+
diagramType: string,
50+
strokeColor?: string
4951
) => {
50-
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
52+
const arrowTypeInfo = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
5153

52-
if (!endMarkerType) {
54+
if (!arrowTypeInfo) {
5355
log.warn(`Unknown arrow type: ${arrowType}`);
5456
return; // unknown arrow type, ignore
5557
}
5658

59+
const endMarkerType = arrowTypeInfo.type;
5760
const suffix = position === 'start' ? 'Start' : 'End';
58-
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
61+
const originalMarkerId = `${id}_${diagramType}-${endMarkerType}${suffix}`;
62+
63+
// If stroke color is specified and non-empty, create or use a colored variant of the marker
64+
if (strokeColor && strokeColor.trim() !== '') {
65+
// Create a sanitized color value for use in IDs
66+
const colorId = strokeColor.replace(/[^\dA-Za-z]/g, '_');
67+
const coloredMarkerId = `${originalMarkerId}_${colorId}`;
68+
69+
// Check if the colored marker already exists
70+
if (!document.getElementById(coloredMarkerId)) {
71+
// Get the original marker
72+
const originalMarker = document.getElementById(originalMarkerId);
73+
if (originalMarker) {
74+
// Clone the marker and create colored version
75+
const coloredMarker = originalMarker.cloneNode(true) as Element;
76+
coloredMarker.id = coloredMarkerId;
77+
78+
// Apply colors to the paths inside the marker
79+
const paths = coloredMarker.querySelectorAll('path, circle, line');
80+
paths.forEach((path) => {
81+
path.setAttribute('stroke', strokeColor);
82+
83+
// Apply fill only to markers that should be filled
84+
if (arrowTypeInfo.fill) {
85+
path.setAttribute('fill', strokeColor);
86+
}
87+
});
88+
89+
// Add the new colored marker to the defs section
90+
originalMarker.parentNode?.appendChild(coloredMarker);
91+
}
92+
}
93+
94+
// Use the colored marker
95+
svgPath.attr(`marker-${position}`, `url(${url}#${coloredMarkerId})`);
96+
} else {
97+
// Always use the original marker for unstyled edges
98+
svgPath.attr(`marker-${position}`, `url(${url}#${originalMarkerId})`);
99+
}
59100
};

packages/mermaid/src/rendering-util/rendering-elements/edges.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
509509
let svgPath;
510510
let linePath = lineFunction(lineData);
511511
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
512+
let strokeColor = edgeStyles.find((style) => style.startsWith('stroke:'));
512513

513514
if (edge.look === 'handDrawn') {
514515
const rc = rough.svg(elem);
@@ -539,18 +540,18 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
539540
if (edge.animation) {
540541
animationClass = ' edge-animation-' + edge.animation;
541542
}
543+
544+
const pathStyle = stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles;
542545
svgPath = elem
543546
.append('path')
544547
.attr('d', linePath)
545548
.attr('id', edge.id)
546549
.attr(
547550
'class',
548-
' ' +
549-
strokeClasses +
550-
(edge.classes ? ' ' + edge.classes : '') +
551-
(animationClass ? animationClass : '')
551+
' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
552552
)
553-
.attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles);
553+
.attr('style', pathStyle);
554+
strokeColor = pathStyle.match(/stroke:([^;]+)/)?.[1];
554555
}
555556

556557
// DEBUG code, DO NOT REMOVE
@@ -587,7 +588,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
587588
log.info('arrowTypeStart', edge.arrowTypeStart);
588589
log.info('arrowTypeEnd', edge.arrowTypeEnd);
589590

590-
addEdgeMarkers(svgPath, edge, url, id, diagramType);
591+
addEdgeMarkers(svgPath, edge, url, id, diagramType, strokeColor);
591592

592593
let paths = {};
593594
if (pointsHasChanged) {

0 commit comments

Comments
 (0)