Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6369 mirror edge color on arrowhead #6371

Merged
merged 3 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vast-nails-stay.md
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 18 additions & 1 deletion cypress/integration/rendering/flowchart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ graph TD
imgSnapshotTest(
`
graph TD
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
hello --> default
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
Expand All @@ -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',
}
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,29 @@ export const addEdgeMarkers = (
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
url: string,
id: string,
diagramType: string
diagramType: string,
strokeColor?: string
) => {
if (edge.arrowTypeStart) {
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType, strokeColor);
}
if (edge.arrowTypeEnd) {
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType, strokeColor);
}
};

const arrowTypesMap = {
arrow_cross: 'cross',
arrow_point: 'point',
arrow_barb: 'barb',
arrow_circle: 'circle',
aggregation: 'aggregation',
extension: 'extension',
composition: 'composition',
dependency: 'dependency',
lollipop: 'lollipop',
requirement_arrow: 'requirement_arrow',
requirement_contains: 'requirement_contains',
arrow_cross: { type: 'cross', fill: false },
arrow_point: { type: 'point', fill: true },
arrow_barb: { type: 'barb', fill: true },
arrow_circle: { type: 'circle', fill: false },
aggregation: { type: 'aggregation', fill: false },
extension: { type: 'extension', fill: false },
composition: { type: 'composition', fill: true },
dependency: { type: 'dependency', fill: true },
lollipop: { type: 'lollipop', fill: false },
requirement_arrow: { type: 'requirement_arrow', fill: false },
requirement_contains: { type: 'requirement_contains', fill: false },
} as const;

const addEdgeMarker = (
Expand All @@ -45,15 +46,55 @@ const addEdgeMarker = (
arrowType: string,
url: 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}`);
return; // unknown arrow type, ignore
}

const endMarkerType = arrowTypeInfo.type;
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})`);
}
};
13 changes: 7 additions & 6 deletions packages/mermaid/src/rendering-util/rendering-elements/edges.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let svgPath;
let linePath = lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
let strokeColor = edgeStyles.find((style) => style.startsWith('stroke:'));

if (edge.look === 'handDrawn') {
const rc = rough.svg(elem);
Expand Down Expand Up @@ -539,18 +540,18 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
if (edge.animation) {
animationClass = ' edge-animation-' + edge.animation;
}

const pathStyle = stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles;
svgPath = elem
.append('path')
.attr('d', linePath)
.attr('id', edge.id)
.attr(
'class',
' ' +
strokeClasses +
(edge.classes ? ' ' + edge.classes : '') +
(animationClass ? animationClass : '')
' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
)
.attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles);
.attr('style', pathStyle);
strokeColor = pathStyle.match(/stroke:([^;]+)/)?.[1];
}

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

addEdgeMarkers(svgPath, edge, url, id, diagramType);
addEdgeMarkers(svgPath, edge, url, id, diagramType, strokeColor);

let paths = {};
if (pointsHasChanged) {
Expand Down
Loading