Merge develop into release/11.5.0

This commit is contained in:
Ashish Jain
2025-03-12 13:40:16 +01:00
4 changed files with 93 additions and 29 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
The arrowhead color should match the color of the edge. Creates a unique clone of the arrow marker with the appropriate color.

View File

@@ -917,4 +917,21 @@ graph TD
} }
); );
}); });
it('#6369: edge color should affect arrow head', () => {
imgSnapshotTest(
`
flowchart LR
A --> B
A --> C
C --> D
linkStyle 0 stroke:#D50000
linkStyle 2 stroke:#D50000
`,
{
flowchart: { htmlLabels: true },
securityLevel: 'loose',
}
);
});
}); });

View File

@@ -15,32 +15,33 @@ export const addEdgeMarkers = (
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>, edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
url: string, url: string,
id: string, id: string,
diagramType: string diagramType: string,
strokeColor?: string
) => { ) => {
if (edge.arrowTypeStart) { if (edge.arrowTypeStart) {
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType); addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType, strokeColor);
} }
if (edge.arrowTypeEnd) { if (edge.arrowTypeEnd) {
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType); addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType, strokeColor);
} }
}; };
const arrowTypesMap = { const arrowTypesMap = {
arrow_cross: 'cross', arrow_cross: { type: 'cross', fill: false },
arrow_point: 'point', arrow_point: { type: 'point', fill: true },
arrow_barb: 'barb', arrow_barb: { type: 'barb', fill: true },
arrow_circle: 'circle', arrow_circle: { type: 'circle', fill: false },
aggregation: 'aggregation', aggregation: { type: 'aggregation', fill: false },
extension: 'extension', extension: { type: 'extension', fill: false },
composition: 'composition', composition: { type: 'composition', fill: true },
dependency: 'dependency', dependency: { type: 'dependency', fill: true },
lollipop: 'lollipop', lollipop: { type: 'lollipop', fill: false },
only_one: 'onlyOne', only_one: { type: 'onlyOne', fill: false },
zero_or_one: 'zeroOrOne', zero_or_one: { type: 'zeroOrOne', fill: false },
one_or_more: 'oneOrMore', one_or_more: { type: 'oneOrMore', fill: false },
zero_or_more: 'zeroOrMore', zero_or_more: { type: 'zeroOrMore', fill: false },
requirement_arrow: 'requirement_arrow', requirement_arrow: { type: 'requirement_arrow', fill: false },
requirement_contains: 'requirement_contains', requirement_contains: { type: 'requirement_contains', fill: false },
} as const; } as const;
const addEdgeMarker = ( const addEdgeMarker = (
@@ -49,15 +50,55 @@ const addEdgeMarker = (
arrowType: string, arrowType: string,
url: string, url: string,
id: string, id: string,
diagramType: string diagramType: string,
strokeColor?: string
) => { ) => {
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap]; const arrowTypeInfo = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
if (!endMarkerType) { if (!arrowTypeInfo) {
log.warn(`Unknown arrow type: ${arrowType}`); log.warn(`Unknown arrow type: ${arrowType}`);
return; // unknown arrow type, ignore return; // unknown arrow type, ignore
} }
const endMarkerType = arrowTypeInfo.type;
const suffix = position === 'start' ? 'Start' : 'End'; const suffix = position === 'start' ? 'Start' : 'End';
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`); const originalMarkerId = `${id}_${diagramType}-${endMarkerType}${suffix}`;
// If stroke color is specified and non-empty, create or use a colored variant of the marker
if (strokeColor && strokeColor.trim() !== '') {
// Create a sanitized color value for use in IDs
const colorId = strokeColor.replace(/[^\dA-Za-z]/g, '_');
const coloredMarkerId = `${originalMarkerId}_${colorId}`;
// Check if the colored marker already exists
if (!document.getElementById(coloredMarkerId)) {
// Get the original marker
const originalMarker = document.getElementById(originalMarkerId);
if (originalMarker) {
// Clone the marker and create colored version
const coloredMarker = originalMarker.cloneNode(true) as Element;
coloredMarker.id = coloredMarkerId;
// Apply colors to the paths inside the marker
const paths = coloredMarker.querySelectorAll('path, circle, line');
paths.forEach((path) => {
path.setAttribute('stroke', strokeColor);
// Apply fill only to markers that should be filled
if (arrowTypeInfo.fill) {
path.setAttribute('fill', strokeColor);
}
});
// Add the new colored marker to the defs section
originalMarker.parentNode?.appendChild(coloredMarker);
}
}
// Use the colored marker
svgPath.attr(`marker-${position}`, `url(${url}#${coloredMarkerId})`);
} else {
// Always use the original marker for unstyled edges
svgPath.attr(`marker-${position}`, `url(${url}#${originalMarkerId})`);
}
}; };

View File

@@ -521,6 +521,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let svgPath; let svgPath;
let linePath = lineFunction(lineData); let linePath = lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style]; const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
let strokeColor = edgeStyles.find((style) => style.startsWith('stroke:'));
if (edge.look === 'handDrawn') { if (edge.look === 'handDrawn') {
const rc = rough.svg(elem); const rc = rough.svg(elem);
@@ -551,18 +552,18 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
if (edge.animation) { if (edge.animation) {
animationClass = ' edge-animation-' + edge.animation; animationClass = ' edge-animation-' + edge.animation;
} }
const pathStyle = stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles;
svgPath = elem svgPath = elem
.append('path') .append('path')
.attr('d', linePath) .attr('d', linePath)
.attr('id', edge.id) .attr('id', edge.id)
.attr( .attr(
'class', 'class',
' ' + ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
strokeClasses +
(edge.classes ? ' ' + edge.classes : '') +
(animationClass ? animationClass : '')
) )
.attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles); .attr('style', pathStyle);
strokeColor = pathStyle.match(/stroke:([^;]+)/)?.[1];
} }
// DEBUG code, DO NOT REMOVE // DEBUG code, DO NOT REMOVE
@@ -599,7 +600,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd); log.info('arrowTypeEnd', edge.arrowTypeEnd);
addEdgeMarkers(svgPath, edge, url, id, diagramType); addEdgeMarkers(svgPath, edge, url, id, diagramType, strokeColor);
let paths = {}; let paths = {};
if (pointsHasChanged) { if (pointsHasChanged) {