Skip to content

Commit 3f5451c

Browse files
author
knsv
committed
Fix swimlane renderer endpoint clipping
1 parent 46279c8 commit 3f5451c

2 files changed

Lines changed: 88 additions & 4 deletions

File tree

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,13 +611,16 @@ export const insertEdge = function (
611611
const innerPoints = points.slice(1, -1);
612612
const firstInner = innerPoints[0];
613613
const lastInner = innerPoints[innerPoints.length - 1];
614+
const TOLERANCE = 0.5;
615+
const lastIsPinned =
616+
Math.abs(points[points.length - 1].x - lastInner.x) < TOLERANCE &&
617+
Math.abs(points[points.length - 1].y - lastInner.y) < TOLERANCE;
614618

615619
const newFirst = tail.intersect(firstInner);
616-
const newLast = head.intersect(lastInner);
620+
const newLast = lastIsPinned ? lastInner : head.intersect(lastInner);
617621

618622
// When the boundary intersection lands ~on the inner point, skip it to
619623
// avoid a zero-length final segment (keeps the entry/exit segment orthogonal).
620-
const TOLERANCE = 0.5;
621624
const lastIsDuplicate =
622625
Math.abs(newLast.x - lastInner.x) < TOLERANCE &&
623626
Math.abs(newLast.y - lastInner.y) < TOLERANCE;

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

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { describe, it, expect, vi } from 'vitest';
2+
import { select } from 'd3';
23

34
// Mock getConfig to control flowchart.curve
45
vi.mock('../../diagram-api/diagramAPI.js', () => ({
56
getConfig: vi.fn(() => ({
6-
flowchart: { curve: 'rounded' },
7+
layout: 'swimlane',
8+
flowchart: { curve: 'rounded', arrowMarkerAbsolute: false },
9+
state: { arrowMarkerAbsolute: false },
710
handDrawnSeed: 0,
811
})),
912
}));
1013

11-
import { resolveEdgeCurveType } from './edges.js';
14+
import { insertEdge, resolveEdgeCurveType } from './edges.js';
1215
import { computeLabelTransform } from '../labelTransform.js';
1316

1417
describe('resolveEdgeCurveType', () => {
@@ -63,3 +66,81 @@ describe('computeLabelTransform', () => {
6366
);
6467
});
6568
});
69+
70+
describe('insertEdge swimlane endpoint clipping', () => {
71+
it('honors duplicated endpoint pins instead of recomputing polygon intersections', () => {
72+
document.body.innerHTML = '';
73+
const svg = select(document.body).append('svg');
74+
const pinnedEnd = { x: -100, y: 53 };
75+
const edge = {
76+
id: 'L_Sys1_B_0',
77+
cssCompiledStyles: {},
78+
style: [],
79+
thickness: 'normal',
80+
pattern: 'solid',
81+
classes: 'flowchart-link',
82+
curve: 'rounded',
83+
look: 'neo',
84+
arrowTypeEnd: 'arrow_point',
85+
points: [
86+
{ x: 0, y: 0 },
87+
{ x: 0, y: 0 },
88+
{ x: 0, y: 20 },
89+
{ x: -100, y: 20 },
90+
pinnedEnd,
91+
{ ...pinnedEnd },
92+
],
93+
};
94+
const tail = {
95+
intersect: vi.fn((point) => point),
96+
};
97+
const head = {
98+
intersect: vi.fn(() => ({ x: -101, y: 54 })),
99+
};
100+
101+
insertEdge(svg, edge, null, 'swimlane', tail, head, 'diagram');
102+
103+
const path = svg.select('path');
104+
const renderedPoints = JSON.parse(atob(path.attr('data-points')));
105+
106+
expect(head.intersect).not.toHaveBeenCalled();
107+
expect(renderedPoints.at(-1)).toEqual(pinnedEnd);
108+
});
109+
110+
it('still clips source endpoints to the rendered shape boundary', () => {
111+
document.body.innerHTML = '';
112+
const svg = select(document.body).append('svg');
113+
const clippedStart = { x: 8, y: 12 };
114+
const edge = {
115+
id: 'L_A2_E_0',
116+
cssCompiledStyles: {},
117+
style: [],
118+
thickness: 'normal',
119+
pattern: 'solid',
120+
classes: 'flowchart-link',
121+
curve: 'rounded',
122+
look: 'neo',
123+
arrowTypeEnd: 'arrow_point',
124+
points: [
125+
{ x: 10, y: 14 },
126+
{ x: 10, y: 14 },
127+
{ x: 10, y: 60 },
128+
{ x: 90, y: 60 },
129+
],
130+
};
131+
const tail = {
132+
intersect: vi.fn(() => clippedStart),
133+
};
134+
const head = {
135+
intersect: vi.fn((point) => point),
136+
};
137+
138+
insertEdge(svg, edge, null, 'swimlane', tail, head, 'diagram');
139+
140+
const path = svg.select('path');
141+
const renderedPoints = JSON.parse(atob(path.attr('data-points')));
142+
143+
expect(tail.intersect).toHaveBeenCalledWith({ x: 10, y: 14 });
144+
expect(renderedPoints[0]).toEqual(clippedStart);
145+
});
146+
});

0 commit comments

Comments
 (0)