Skip to content

fix(xychart): auto-flip point labels to avoid line collision#7551

Open
DominicBurkart wants to merge 9 commits intomermaid-js:developfrom
DominicBurkart:fix/7549_xychart-label-line-collision
Open

fix(xychart): auto-flip point labels to avoid line collision#7551
DominicBurkart wants to merge 9 commits intomermaid-js:developfrom
DominicBurkart:fix/7549_xychart-label-line-collision

Conversation

@DominicBurkart
Copy link
Copy Markdown

@DominicBurkart DominicBurkart commented Mar 29, 2026

📑 Summary

When a line chart has steep slopes, per-point text labels placed above a data point collide with the line segment. This adds collision detection that auto-flips the label to the opposite side of the point when an intersection is detected.

Stacked on #7550 — please merge #7550 first.

📋 Tasks

  • 📖 have read the contribution guidelines
  • 💻 have added necessary unit/e2e tests.
  • 📓 have added documentation. Make sure MERMAID_RELEASE_VERSION is used for all new features.
  • 🦋 If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 29, 2026

🦋 Changeset detected

Latest commit: d1d7946

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 29, 2026

Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit d1d7946
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/69dbcb68a10a0c000835e0ba
😎 Deploy Preview https://deploy-preview-7551--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 29, 2026

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@7551

mermaid

npm i https://pkg.pr.new/mermaid@7551

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@7551

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@7551

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@7551

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@7551

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@7551

commit: d1d7946

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 29, 2026

Codecov Report

❌ Patch coverage is 0% with 194 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.32%. Comparing base (e9d4c11) to head (d1d7946).
⚠️ Report is 194 commits behind head on develop.

Files with missing lines Patch % Lines
...s/xychart/chartBuilder/components/plot/linePlot.ts 0.00% 184 Missing ⚠️
packages/mermaid/src/diagrams/xychart/xychartDb.ts 0.00% 9 Missing ⚠️
...rams/xychart/chartBuilder/components/plot/index.ts 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #7551      +/-   ##
==========================================
- Coverage     3.34%   3.32%   -0.03%     
==========================================
  Files          524     536      +12     
  Lines        55256   56430    +1174     
  Branches       795     820      +25     
==========================================
+ Hits          1850    1876      +26     
- Misses       53406   54554    +1148     
Flag Coverage Δ
unit 3.32% <0.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...id/src/diagrams/xychart/chartBuilder/interfaces.ts 10.00% <ø> (ø)
...rams/xychart/chartBuilder/components/plot/index.ts 1.56% <0.00%> (+0.11%) ⬆️
packages/mermaid/src/diagrams/xychart/xychartDb.ts 0.00% <0.00%> (ø)
...s/xychart/chartBuilder/components/plot/linePlot.ts 0.00% <0.00%> (ø)

... and 37 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@argos-ci
Copy link
Copy Markdown

argos-ci Bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 1 changed, 8 added Apr 12, 2026, 4:54 PM

Copy link
Copy Markdown
Collaborator

@knsv knsv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[sisyphus-bot]

Thanks for this work, @DominicBurkart — per-point labels with collision avoidance is a really thoughtful feature addition. The QA screenshots on #7550 are excellent, and the stacked PR model (#7550#7551#7552) is a clean way to decompose this. Let's get these across the finish line.

Note: This PR is stacked on #7550 — the diff here includes changes from both PRs. I'm reviewing the combined changeset.

What's working well

🎉 [praise] The collision detection algorithm in linePlot.ts is well-designed. The approach — compute the line segment's y-range within the label's x-range, then check for overlap — correctly handles the case where a steep line passes through the label bounding box even when neither endpoint is inside it. The incremental offset retry (1x, 2x, 3x) is a practical fallback strategy.

🎉 [praise] Excellent test coverage. 6 new parser unit tests covering labeled points, mixed labels, labels with spaces, decimal values with labels, and bar data with labels. 8 new e2e visual regression tests covering basic labels, mixed labels, horizontal orientation, multiple lines, and all three collision scenarios (steep descent, gentle slope, zigzag). This is exactly the level of coverage we want.

🎉 [praise] Clean grammar extension — dataPoint rule returning { value, label } objects is a backwards-compatible change that naturally supports mixed labeled/unlabeled points. Existing unlabeled syntax continues to work via the label: '' default.

🎉 [praise] Two appropriate changesets: minor for the feature, patch for the collision fix. Documentation uses MERMAID_RELEASE_VERSION placeholder correctly.

🎉 [praise] The separation of computeLabelPlacementVertical and computeLabelPlacementHorizontal as pure functions with clear interfaces (LabelPlacement) makes the collision logic testable in isolation if needed later.

Things to address

🟡 [important] Labels not sanitized at DB layer. In xychartDb.ts, axis titles and band categories are run through textSanitizer() (which calls sanitizeText()), but point labels are stored as raw strings from the parser. While D3's .text() method (used at xychartRenderer.ts:242) is safe (it uses textContent, not innerHTML), and DOMPurify sanitizes the final SVG output, the inconsistency means labels and axis titles could render differently for the same input (e.g., markdown formatting, entity handling). It would be great to sanitize labels the same way for consistency:

// In setLineData, after extracting labels:
const labels = data.map((d) => textSanitizer(d.label));

xychartDb.ts:164-165

🟡 [important] Hardcoded font size and offset. The label font size (12px) and offset (14px) are hardcoded constants in linePlot.ts:247-248. Other xychart text elements derive their font sizes from the chart config (e.g., xAxis.labelFontSize, titleFontSize). Worth considering making these configurable via XYChartConfig — even if that's deferred to a follow-up, it would be good to at least use the theme's data label font size rather than a magic number. The docs also call out the 12px hardcoding as a note, which is transparent, but users may expect consistency with other text sizes.

linePlot.ts:247-248

🟢 [nit] Generated docs edited directly. docs/syntax/xyChart.md is in the auto-generated directory — only packages/mermaid/src/docs/syntax/xyChart.md should be edited. The build process (pnpm --filter mermaid docs:build) will regenerate the top-level docs/. The manual edit won't cause issues but will be overwritten.

🟢 [nit] Bar data silently accepts labels. The shared dataPoint grammar rule means bar [10 "A", 20 "B"] parses successfully but labels are ignored (bar rendering doesn't use pointLabels). This is harmless but could confuse users who expect labels on bars too. Worth either documenting this limitation or parsing bar data without the label option.

🟢 [nit] CHAR_WIDTH_FACTOR = 0.7 is a rough heuristic for estimating text width. It's adequate for collision detection (where false positives just mean slightly more flipping), but it won't be accurate for all fonts, proportional characters, or CJK text. A comment noting this approximation would be helpful for future maintainers.

💡 [suggestion] The collision detection functions (lineYAtX, doesSegmentIntersectBox, etc.) are pure geometry utilities with no xychart dependency. If other diagram types ever need similar collision avoidance, these could be extracted to a shared utility. No action needed now — just noting the opportunity.

Security

No XSS or injection issues identified. Labels flow through D3's .text() method (which uses textContent, not HTML parsing) and are further protected by DOMPurify sanitization on the final SVG output. The 🟡 above about sanitizeText consistency is a defense-in-depth recommendation, not an exploitable vulnerability.

Summary

Severity Count
🔴 blocking 0
🟡 important 2
🟢 nit 3
💡 suggestion 1
🎉 praise 5

Self-check

  • At least one 🎉 praise item
  • No duplicate comments
  • Severity tally: 0 🔴 / 2 🟡 / 3 🟢 / 1 💡 / 5 🎉
  • Verdict matches criteria: COMMENT (2 🟡, no 🔴)
  • No inline comments used
  • Tone check: collaborative and constructive

claude and others added 8 commits April 12, 2026 18:21
Allow annotating individual data points on xychart line plots with
custom text labels. The syntax extends the existing data array format:

  line [540 "PaLM", 65 "LLaMA", 34 "Llama 2", 7 "Mistral"]

Each number can optionally be followed by a quoted string label.
Labels are rendered above (vertical) or to the right (horizontal)
of the data point, using the line's stroke color.

Backward-compatible: existing syntax without labels works unchanged.

Addresses mermaid-js#5326.

https://claude.ai/code/session_01CgvREhkQ3tVYCagq1mHmjV
Document the new per-point text label syntax for line charts,
including syntax examples and rendering behavior notes.

https://claude.ai/code/session_01WErEVQAYaNmC3rpbevFwMg
…labels

- Add minor changeset for the new point labels feature
- Enhance xyChart.md docs with mermaid-example block for mixed labels
  and a note about fixed font size
- Add 4 Cypress e2e snapshot tests covering: all-labeled, mixed-labels,
  horizontal orientation, and multi-line scenarios
- Add MMLU to cspell dictionary

https://claude.ai/code/session_011g3A6y2kdJnZh8YB4uJaxD
- Pass point labels through textSanitizer() in setLineData, matching
  the defense-in-depth pattern already used for axis titles/bands
- Add note to docs that labels apply to line plots only; bar syntax
  is accepted but labels are currently ignored
When a line segment has a steep slope, labels placed above a data point
can collide with the line. This adds collision detection that estimates
each label's bounding box and checks for intersection with adjacent line
segments using y-range overlap. When a collision is detected, the label
is flipped to the opposite side of the point (below for vertical charts,
left for horizontal charts).

https://claude.ai/code/session_01UCV1QCaRsRtQjRspaVrqUp
…padding

- Replace shouldFlipLabelVertical/Horizontal with computeLabelPlacement
  functions that verify both above and below positions before deciding
  where to place each point label
- Try increasing offsets (1x, 2x, 3x baseOffset) if both positions
  collide at the default offset — handles zigzag patterns
- Expand collision bounding boxes by strokeWidth/2 to account for the
  visual width of the rendered line
- Increase default labelOffset from 10 to 14 for more breathing room
- Add reviewer's exact failing example to e2e tests and demo page
@DominicBurkart DominicBurkart force-pushed the fix/7549_xychart-label-line-collision branch from b0f93a3 to ca720b9 Compare April 12, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants