Skip to content

Commit b085228

Browse files
committed
fix(xychart): use proper segment-box intersection instead of sampling
The previous 3-point sampling approach missed cases where a steep line segment crossed through the label bounding box between sample points (e.g., valley points in a zigzag). Now computes the segment's y-range within the box's x-range and checks for overlap, which correctly detects all crossing scenarios. https://claude.ai/code/session_01UCV1QCaRsRtQjRspaVrqUp
1 parent 4343c2e commit b085228

File tree

3 files changed

+36
-9
lines changed

3 files changed

+36
-9
lines changed

packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/linePlot.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ function lineYAtX(segStart: [number, number], segEnd: [number, number], x: numbe
2525

2626
/**
2727
* Check if a line segment passes through an axis-aligned bounding box.
28+
*
29+
* Computes the y-range of the segment within the box's x-range and checks
30+
* if it overlaps with the box's y-range. This correctly detects the case
31+
* where a steep line enters from above and exits below the box (or vice
32+
* versa) even if neither endpoint nor any single sample point falls inside.
2833
*/
2934
function doesSegmentIntersectBox(
3035
segStart: [number, number],
@@ -34,21 +39,43 @@ function doesSegmentIntersectBox(
3439
boxTop: number,
3540
boxBottom: number
3641
): boolean {
37-
// Sample the segment's y at left edge, center, and right edge of the box
38-
const sampleXs = [boxLeft, (boxLeft + boxRight) / 2, boxRight];
39-
for (const sx of sampleXs) {
40-
const y = lineYAtX(segStart, segEnd, sx);
41-
if (y !== null && y >= boxTop && y <= boxBottom) {
42-
return true;
43-
}
44-
}
4542
// Check if either endpoint is inside the box
4643
for (const [ex, ey] of [segStart, segEnd]) {
4744
if (ex >= boxLeft && ex <= boxRight && ey >= boxTop && ey <= boxBottom) {
4845
return true;
4946
}
5047
}
51-
return false;
48+
49+
// Compute the y-values of the line at the box's left and right x-edges
50+
const yAtLeft = lineYAtX(segStart, segEnd, boxLeft);
51+
const yAtRight = lineYAtX(segStart, segEnd, boxRight);
52+
53+
// Collect valid y-values (where segment overlaps box's x-range)
54+
const ys: number[] = [];
55+
if (yAtLeft !== null) {
56+
ys.push(yAtLeft);
57+
}
58+
if (yAtRight !== null) {
59+
ys.push(yAtRight);
60+
}
61+
62+
// Also include segment endpoints that fall within the box's x-range
63+
for (const [ex, ey] of [segStart, segEnd]) {
64+
if (ex >= boxLeft && ex <= boxRight) {
65+
ys.push(ey);
66+
}
67+
}
68+
69+
if (ys.length === 0) {
70+
return false;
71+
}
72+
73+
// The segment's y-range within the box's x-range
74+
const segMinY = Math.min(...ys);
75+
const segMaxY = Math.max(...ys);
76+
77+
// Check if the segment's y-range overlaps with the box's y-range
78+
return segMaxY >= boxTop && segMinY <= boxBottom;
5279
}
5380

5481
/**
-12 Bytes
Loading
165 Bytes
Loading

0 commit comments

Comments
 (0)