Skip to content
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
9 changes: 9 additions & 0 deletions .changeset/rounded-edge-curves.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'mermaid': minor
---

fix: replace smooth curve edges with rounded right-angle edges

The default flowchart edge curve changes from `basis` (smooth splines) to `rounded` (right-angle segments with rounded corners). This fixes ELK layout edges that were curving instead of routing at right angles (#7213) and applies consistently across all diagram types using the shared rendering pipeline.

To restore the previous smooth curve behavior, set `flowchart.curve: 'basis'` in your config.
29 changes: 29 additions & 0 deletions cypress/integration/rendering/flowchart-elk.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,4 +1106,33 @@ describe('Title and arrow styling #4813', () => {
expect(edges[3].getAttribute('class')).to.contain('edge-thickness-invisible');
});
});

it('7213: should render ELK edges with right angles not curves', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph G1
N00
N11
N12
N13
end
subgraph G2
N21
N22
end
N00 --- N01 & N02 & N03 & N04 & N05
N00 --- N11 & N12 & N13 & N22
N11 --- N22
N11 --- N22
N11 --- N22
N11 --- N22
N11 --- N22
`,
{}
);
});
});
13 changes: 13 additions & 0 deletions cypress/integration/rendering/flowchart-v2.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,19 @@ end
});
});

it('7213: should render edges with rounded curve (right angles with rounded corners)', () => {
imgSnapshotTest(
`flowchart TD
A[Start] --> B{Decision}
B -->|Yes| C[Process 1]
B -->|No| D[Process 2]
C --> E[End]
D --> E
`,
{ flowchart: { curve: 'rounded' } }
);
});

it('6617: Per Link Curve Styling using edge Ids', () => {
imgSnapshotTest(
`flowchart TD
Expand Down
3 changes: 2 additions & 1 deletion packages/mermaid/src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
| 'natural'
| 'step'
| 'stepAfter'
| 'stepBefore';
| 'stepBefore'
| 'rounded';
/**
* Represents the padding between the labels and the shape
*
Expand Down
34 changes: 25 additions & 9 deletions packages/mermaid/src/rendering-util/rendering-elements/edges.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts';
import { isLabelStyle, styles2String } from './shapes/handDrawnShapeStyles.js';

/**
* Resolve the effective curve type for an edge.
* If edge.curve is a string (e.g. 'rounded', 'linear'), use it directly.
* Otherwise (undefined, null, or a D3 CurveFactory function), fall back to config.
* @param {*} edgeCurve - The edge.curve value (string, function, or undefined/null)
* @returns {string|undefined} - The resolved curve type string
*/
export const resolveEdgeCurveType = (edgeCurve) => {
return typeof edgeCurve === 'string' ? edgeCurve : getConfig()?.flowchart?.curve;
};

export const edgeLabels = new Map();
export const terminalLabels = new Map();

Expand Down Expand Up @@ -507,6 +518,7 @@ const fixCorners = function (lineData) {
}
return newLineData;
};

const generateDashArray = (len, oValueS, oValueE) => {
const middleLength = len - oValueS - oValueE;
const dashLength = 2; // Length of each dash
Expand Down Expand Up @@ -582,10 +594,15 @@ export const insertEdge = function (
}

let lineData = points.filter((p) => !Number.isNaN(p.y));
lineData = fixCorners(lineData);
let curve = curveBasis;
curve = curveLinear;
switch (edge.curve) {
// Resolve curve type: use edge.curve if it's a string, otherwise fall back to config default
const edgeCurveType = resolveEdgeCurveType(edge.curve);
// Apply fixCorners for non-rounded curves to pre-round right-angle corners
// (rounded curve type uses generateRoundedPath instead)
if (edgeCurveType !== 'rounded') {
lineData = fixCorners(lineData);
}
let curve = curveLinear;
switch (edgeCurveType) {
case 'linear':
curve = curveLinear;
break;
Expand Down Expand Up @@ -622,14 +639,13 @@ export const insertEdge = function (
case 'stepBefore':
curve = curveStepBefore;
break;
case 'rounded':
curve = curveLinear;
break;
default:
curve = curveBasis;
}

// if (edge.curve) {
// curve = edge.curve;
// }

const { x, y } = getLineFunctionsWithOffset(edge);
const lineFunction = line().x(x).y(y).curve(curve);

Expand Down Expand Up @@ -662,7 +678,7 @@ export const insertEdge = function (
}
let svgPath;
let linePath =
edge.curve === 'rounded'
edgeCurveType === 'rounded'
? generateRoundedPath(applyMarkerOffsetsToPoints(lineData, edge), 5)
: lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, it, expect, vi } from 'vitest';

// Mock getConfig to control flowchart.curve
vi.mock('../../diagram-api/diagramAPI.js', () => ({
getConfig: vi.fn(() => ({
flowchart: { curve: 'rounded' },
handDrawnSeed: 0,
})),
}));

import { resolveEdgeCurveType } from './edges.js';

describe('resolveEdgeCurveType', () => {
it('should return edge.curve when it is a string', () => {
expect(resolveEdgeCurveType('linear')).toBe('linear');
expect(resolveEdgeCurveType('basis')).toBe('basis');
expect(resolveEdgeCurveType('rounded')).toBe('rounded');
expect(resolveEdgeCurveType('cardinal')).toBe('cardinal');
});

it('should fall back to config flowchart.curve when edge.curve is undefined', () => {
// When edge.curve is undefined, should resolve from config (which is mocked as 'rounded')
expect(resolveEdgeCurveType(undefined)).toBe('rounded');
});

it('should fall back to config flowchart.curve when edge.curve is not a string (D3 function)', () => {
// Class diagrams and other non-flowchart types may pass a D3 CurveFactory function
// eslint-disable-next-line @typescript-eslint/no-empty-function
const fakeCurveFactory = () => {};
expect(resolveEdgeCurveType(fakeCurveFactory)).toBe('rounded');
});

it('should fall back to config flowchart.curve when edge.curve is null', () => {
expect(resolveEdgeCurveType(null)).toBe('rounded');
});
});
3 changes: 2 additions & 1 deletion packages/mermaid/src/schemas/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2155,8 +2155,9 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
'step',
'stepAfter',
'stepBefore',
'rounded',
]
default: 'basis'
default: 'rounded'
padding:
description: |
Represents the padding between the labels and the shape
Expand Down
Loading