Skip to content

Add legends to XY charts - #7724

Merged
aloisklink merged 7 commits into
mermaid-js:developfrom
xdumaine:xychart-legends
Jun 10, 2026
Merged

aloisklink merged 7 commits into
mermaid-js:developfrom
xdumaine:xychart-legends

Conversation

@xdumaine

@xdumaine xdumaine commented May 7, 2026 •

Copy link
Copy Markdown
Contributor

📑 Summary

Adds legends for named XY chart line and bar series. Authors can already write labels like line "avg" [...]; this change preserves those labels and renders them in a right-side legend when showLegend is enabled.

Fixes #5292.

Motivation

XY charts support multiple line and bar plots, but readers currently have no built-in way to map each plotted color to a series name. That makes examples like average, p50, and p95 latency lines hard to interpret without surrounding text or manually faked legends.

This PR makes the existing named-series syntax useful in rendered output while keeping unnamed plots unchanged.

📏 Design Decisions

  • Reuses existing plot labels from line "name" [...] and bar "name" [...]; no new diagram syntax is required.
  • Omits unnamed plots from the legend so existing charts do not gain placeholder entries.
  • Adds a ChartLegend layout component that reserves right-side space before the plot expands into remaining width.
  • Adds xyChart.showLegend, legendFontSize, legendPadding, and themeVariables.xyChart.legendTextColor for configuration/theming.
  • Documents the feature with the required (v<MERMAID_RELEASE_VERSION>+) marker and includes a minor changeset.

Screenshots

Full chart with legend:

XY chart with avg, p50, and p95 legend

Legend detail:

XY chart legend detail

Disclaimer / Methods

I used Cursor's AI coding agent with GPT 5.5. to help inspect Mermaid's XY chart and pie legend implementations, draft the implementation, update docs/config/types, and run verification. I reviewed the generated changes, corrected the implementation where needed, checked the contribution guidelines and PR template, and validated behavior locally with parser, TypeScript, lint, and focused Cypress rendering tests.

Test plan

  • pnpm --filter mermaid types:build-config

  • pnpm exec prettier --write "packages/mermaid/src/diagrams/xychart/**/*.{ts,js}" "packages/mermaid/src/themes/*.js" "packages/mermaid/src/docs/syntax/xyChart.md" "docs/syntax/xyChart.md" "cypress/integration/rendering/xychart/xyChart.spec.js" "packages/mermaid/src/schemas/config.schema.yaml" "packages/mermaid/src/config.type.ts"

  • pnpm exec vitest run packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts

  • pnpm exec vitest run packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.spec.ts packages/mermaid/src/diagrams/xychart/xychartDb.spec.ts packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts packages/mermaid/src/themes/theme-xychart.spec.js (67/67 passing)

  • pnpm exec vitest run --coverage packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.spec.ts packages/mermaid/src/diagrams/xychart/xychartDb.spec.ts packages/mermaid/src/themes/theme-xychart.spec.js

  • pnpm test:check:tsc

  • pnpm exec eslint --quiet packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.ts packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.spec.ts packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts packages/mermaid/src/diagrams/xychart/xychartDb.spec.ts packages/mermaid/src/themes/theme-xychart.spec.js cypress/integration/rendering/xychart/xyChart.spec.js

  • MERMAID_PORT=9000 pnpm run load:env -- start-server-and-test dev http://localhost:9000/ "cypress run --spec cypress/integration/rendering/xychart/xyChart.spec.js" (38/38 passing)

📋 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:.

Made with Cursor

xdumaine and others added 2 commits May 7, 2026 10:49
Preserve named line and bar series as legend entries so multi-series XY charts can identify their plotted data.

Co-authored-by: Cursor <cursoragent@cursor.com>
Follow Mermaid contribution guidance by marking the new docs section with the release token and adding a changeset.

Co-authored-by: Cursor <cursoragent@cursor.com>
@netlify

netlify Bot commented May 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 0872879
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a29947ec4c6880008d332e9
😎 Deploy Preview https://deploy-preview-7724--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@changeset-bot

changeset-bot Bot commented May 7, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0872879

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

@pkg-pr-new

pkg-pr-new Bot commented May 7, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

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

mermaid

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

@mermaid-js/layout-elk

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

@mermaid-js/layout-tidy-tree

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

@mermaid-js/mermaid-zenuml

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

@mermaid-js/parser

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

@mermaid-js/tiny

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

commit: 0872879

@codecov

codecov Bot commented May 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.29032% with 153 lines in your changes missing coverage. Please review.
✅ Project coverage is 2.81%. Comparing base (9030126) to head (0872879).
⚠️ Report is 55 commits behind head on develop.

Files with missing lines Patch % Lines
...diagrams/xychart/chartBuilder/components/legend.ts 0.83% 119 Missing ⚠️
.../src/diagrams/xychart/chartBuilder/orchestrator.ts 0.00% 22 Missing ⚠️
packages/mermaid/src/diagrams/xychart/xychartDb.ts 0.00% 2 Missing ⚠️
packages/mermaid/src/themes/theme-base.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-dark.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-forest.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-neo-dark.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-neo.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-neutral.js 0.00% 1 Missing ⚠️
packages/mermaid/src/themes/theme-redux-color.js 0.00% 1 Missing ⚠️
... and 3 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #7724      +/-   ##
==========================================
- Coverage     2.83%   2.81%   -0.02%     
==========================================
  Files          667     670       +3     
  Lines        72262   72876     +614     
  Branches       989     992       +3     
==========================================
+ Hits          2047    2051       +4     
- Misses       70215   70825     +610     
Flag Coverage Δ
unit 2.81% <1.29%> (-0.02%) ⬇️

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

Files with missing lines Coverage Δ
packages/mermaid/src/config.type.ts 100.00% <ø> (ø)
...id/src/diagrams/xychart/chartBuilder/interfaces.ts 10.00% <ø> (ø)
packages/mermaid/src/themes/theme-default.js 95.88% <100.00%> (+<0.01%) ⬆️
packages/mermaid/src/themes/theme-base.js 2.09% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-dark.js 1.90% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-forest.js 2.10% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-neo-dark.js 2.40% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-neo.js 2.49% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-neutral.js 2.64% <0.00%> (-0.01%) ⬇️
packages/mermaid/src/themes/theme-redux-color.js 2.28% <0.00%> (-0.01%) ⬇️
... and 6 more

... and 46 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

argos-ci Bot commented May 7, 2026 •

Copy link
Copy Markdown

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

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 1 added Jun 10, 2026, 5:07 PM

Add focused unit coverage for legend rendering, builder layout integration, plot title persistence, and theme defaults.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pbrolin47

Copy link
Copy Markdown
Collaborator

Hi @xdumaine. Thanks for this PR.

[sisyphus-bot]


What's working well

🎉 Comprehensive test coverage for a first-time contributor. Four new test files covering legend unit tests, chart builder integration, DB title persistence, and all 11 themes. 79/79 unit tests +
38/38 Cypress passing is the complete picture the project expects.

🎉 Clean architectural fit. ChartLegend implements the existing ChartComponent interface without any deviation. Reserving space before expanding the plot to fill remaining width is exactly the right
layout approach — same pattern as the title component.

🎉 Elegant backwards-compatible syntax. Reusing line "name" [...] (already parseable; title was previously ignored) means zero new grammar and existing unnamed charts are completely unaffected.

🎉 Correct sanitization. title: textSanitizer(title.text) in both setLineData and setBarData matches the established pattern for every other user-supplied text field in this diagram. No XSS path —
text reaches SVG exclusively via D3's .text() (sets textContent, not innerHTML).

🎉 Config schema first. Schema additions drive config.type.ts regeneration; PR body includes pnpm types:build-config in the test plan. The regenerated file matches the schema additions exactly.


Things to address

🟡 [important] showLegend: true by default changes rendering for existing documents

config.schema.yaml:
showLegend:
description: Should show a legend for named plots
type: boolean
default: true

Any existing diagram that already uses line "name" [...] or bar "name" [...] syntax — including documents on GitHub, GitLab, Obsidian — will now automatically render a legend box, changing their
layout dimensions and visual appearance.

CLAUDE.md is explicit: "Mermaid is rendered server-side by GitHub/GitLab — rendering changes affect millions of existing documents. Treat visual output changes as potentially breaking."

The counter-argument (acknowledged in the PR description) is that users who wrote line "avg" [...] expected the name to mean something — they were already opting in to the feature by using the
named-series syntax. That logic is sound, and it means unnamed charts are truly unaffected.

I would like some additional input from other maintainers if we should go for showLegend: true as default or flip to false. @aloisklink , @knsv ?

The existing Cypress imgSnapshotTest cases don't use named series, so no baseline failures are expected regardless.

🟢 [nit] Cypress legend test uses screenshot: false only

cypress/integration/rendering/xychart/xyChart.spec.js:
it('should render legends for named plots', () => {
renderGraph(..., { screenshot: false });
cy.get('g.legend text').should('have.length', 3);
// ...

The test correctly verifies DOM structure (element counts and text content). But without an imgSnapshotTest call, visual regressions — wrong marker size, misaligned text, wrong color — won't be
caught in Argos / the snapshot pipeline. An additional imgSnapshotTest case with the same three-series diagram would add meaningful regression coverage. Low priority since the unit tests cover
positioning math, but worth noting.


Security

No XSS or injection issues. Full trace:

  1. line "name" → JISON calls setLineData({ text: 'name', type: 'text' }, [...]) with '' for unnamed plots
  2. textSanitizer(title.text) → sanitizeText(text.trim(), config) → DOMPurify-backed sanitization, matching all other text in this diagram
  3. plot.title flows to DrawableElem type: 'text'
  4. xyChartRenderer.ts:242 — d3.text((data) => data.text) sets .textContent, never .innerHTML
  5. Legend marker geometry (path strings, x/y/width/height) is derived entirely from layout arithmetic on numeric config values — no user input

getPlotColorFromPalette() supplies fill/strokeFill values set as D3 .attr('fill', ...) / .attr('stroke', ...) — the existing risk surface, no new exposure.


Other checks

  • Diagram isolation ✓ — legend.ts imports only from interfaces.js, textDimensionCalculator.js, and diagram-api/types.js. No cross-diagram imports.
  • Changeset ✓ — minor bump with feat: prefix. Correct for a new user-facing feature.
  • No console.log ✓
  • TypeScript ✓ — title: string added as required field on LinePlotData/BarPlotData, and the only creation site (xychartDb.ts) populates it in both setLineData and setBarData. No existing code
    creates these types directly.
  • MERMAID_RELEASE_VERSION ✓ — Added in commit 2/3 to the new Legend section heading.
  • Unnamed-plot filter ✓ — filter((plot) => plot.title) correctly excludes title: '' (falsy), matching the described behavior.
  • Legend visibility fallback ✓ — When legend dimensions exceed available space, visiblePlots is reset to [] and calculateSpace returns {0, 0}. Silent but correct behavior; space is returned to the
    plot.
  • Both orientations covered ✓ — Vertical and horizontal layout paths both call legend.calculateSpace() and legend.setBoundingBoxXY().

@xdumaine

xdumaine commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Great call out re: defaulting the legend to true. However, based on the end of that block (quoted below) I'm going to wait for feedback before flipping that. I'm happy to go either way and glad to make that change once there's consensus. Thank you for the review!

I would like some additional input from other maintainers if we should go for showLegend: true as default or flip to false. @aloisklink , @knsv ?

@xdumaine

Copy link
Copy Markdown
Contributor Author

@aloisklink @knsv any preference? I'm glad to update it per your guidance. Would love to get this landed.

@aloisklink aloisklink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work on this, @xdumaine! I'm really like this!

I think keeping showLegend: true by default is fine. Most people would probably be happy to see this, and it's not going to significantly negatively affect this XY charts for other users.

But on the off chance someone doesn't like it, could you maybe add a short instruction on how to disable this in the changeset?

I've done a quick review of your code as well, and I think the main blocking issue I spotted was the lack of visual regression E2E tests (the current @argos-ci changes are all flakiness that should be gone the next time you push), but please see my comments for more information!


I'm going to ping @subhash-halder, the original author of XY charts, in case they want to review this (and also so they can see how awesome this feature is!).

Comment thread .changeset/xychart-legends.md Outdated
Comment thread cypress/integration/rendering/xychart/xyChart.spec.js Outdated
Comment thread .changeset/xychart-legends.md Outdated
Comment thread packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.ts Outdated
Comment thread packages/mermaid/src/diagrams/xychart/chartBuilder/index.spec.ts Outdated
Comment thread packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts
Comment thread packages/mermaid/src/themes/theme-xychart.spec.js Outdated
Comment thread packages/mermaid/src/themes/theme-xychart.spec.js Outdated
xdumaine and others added 3 commits May 22, 2026 11:46
Add visual regression coverage for line and bar legend markers, simplify legend layout helpers, and trim hard-coded test fixtures per reviewer guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve the xychartDb spec conflict by keeping upstream data length validation coverage and the legend title preservation test.

Co-authored-by: Cursor <cursoragent@cursor.com>
@xdumaine
xdumaine requested a review from aloisklink June 3, 2026 12:38
@xdumaine

xdumaine commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@aloisklink polite bump please and thank you

@aloisklink aloisklink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me! Thanks for the reminder.

I've got one nitpick (useless type cast, very common with LLM-generated code), but I'll apply that myself so we can merge the PR now!

As a heads up, we've already cut off the release/11.16.0 branch, so this probably won't land in the next release, but only the release after that.

Comment thread packages/mermaid/src/diagrams/xychart/chartBuilder/components/legend.spec.ts Outdated
@aloisklink
aloisklink enabled auto-merge June 10, 2026 16:45
@aloisklink
aloisklink added this pull request to the merge queue Jun 10, 2026
Merged via the queue into mermaid-js:develop with commit cd749ff Jun 10, 2026
30 of 33 checks passed
@mermaid-bot

mermaid-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

@xdumaine, Thank you for the contribution!
You are now eligible for a year of Premium account on MermaidChart.
Sign up with your GitHub account to activate.

ark-65 added a commit to zhongmiao-org/mermaid-markdown-bridge that referenced this pull request Aug 21, 2026
Upstream release: https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.17.0

Release notes:
### Minor Changes

-   [#7842](mermaid-js/mermaid#7842) [`3670b4e`](mermaid-js/mermaid@3670b4e) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(c4): render C4 elements through the unified shape system, using the new person shape

-   [#7812](mermaid-js/mermaid#7812) [`cdfc0ea`](mermaid-js/mermaid@cdfc0ea) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(class): route `classDiagram` to the unified (v2) renderer by default

    Set `class: { defaultRenderer: 'dagre-d3' }` in the config to restore the legacy renderer.

-   [#7785](mermaid-js/mermaid#7785) [`c45cde9`](mermaid-js/mermaid@c45cde9) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(flowchart): add collapsible flowchart subgraphs via `subgraphId@{ view: collapsed }`

-   [#7828](mermaid-js/mermaid#7828) [`8eb3afc`](mermaid-js/mermaid@8eb3afc) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(elk): add `elk.keepEntryNodeOnTop` config option to keep a recursive flow's entry node on top

-   [#7803](mermaid-js/mermaid#7803) [`74e44eb`](mermaid-js/mermaid@74e44eb) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(elk): add `elk.nodePlacementAlignment` config option

-   [#7792](mermaid-js/mermaid#7792) [`ea55b31`](mermaid-js/mermaid@ea55b31) Thanks [@RodrigojndSantos](https://github.com/RodrigojndSantos)! - feat(er): add subgraph support to ER diagrams.

-   [#7970](mermaid-js/mermaid#7970) [`a2c0fb6`](mermaid-js/mermaid@a2c0fb6) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(flowchart): add `folder`, `bucket`, `console` (terminal window) and `browser` shapes

-   [#7842](mermaid-js/mermaid#7842) [`ae3e115`](mermaid-js/mermaid@ae3e115) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(flowchart): add `person` shape (circular head above a rounded body), usable in flowcharts via `A@{ shape: person }`

-   [#7724](mermaid-js/mermaid#7724) [`0fd7a9f`](mermaid-js/mermaid@0fd7a9f) Thanks [@xdumaine](https://github.com/xdumaine)! - feat(xyChart): add legends for named line and bar series

### Patch Changes

-   [#7847](mermaid-js/mermaid#7847) [`215fe89`](mermaid-js/mermaid@215fe89) Thanks [@filipsajdak](https://github.com/filipsajdak)! - fix(c4): named attributes such as `$tags`, `$link` and `$sprite` are no longer clobbered to undefined when they arrive in an earlier positional slot of Person/System/Container/Component/Boundary/Rel statements.

-   [#7871](mermaid-js/mermaid#7871) [`8d874c4`](mermaid-js/mermaid@8d874c4) Thanks [@knsv-bot](https://github.com/knsv-bot)! - fix(flowchart): stop dagre layout from spamming `warn`-level logs on every node/edge/cluster

-   [#8071](mermaid-js/mermaid#8071) [`b3d1f63`](mermaid-js/mermaid@b3d1f63) Thanks [@pbrolin47](https://github.com/pbrolin47)! - fix(block): sibling blocks overlapping in block diagrams when one has a label wider than 200px

-   [#7870](mermaid-js/mermaid#7870) [`71b8843`](mermaid-js/mermaid@71b8843) Thanks [@knsv-bot](https://github.com/knsv-bot)! - fix: a `RangeError: Invalid array length` crash when rendering certain edges.

-   [#7924](mermaid-js/mermaid#7924) [`9cbef5d`](mermaid-js/mermaid@9cbef5d) Thanks [@nightt5879](https://github.com/nightt5879)! - fix(treeView): icons disappearing after strict security sanitization.

-   [#7850](mermaid-js/mermaid#7850) [`a34cbf0`](mermaid-js/mermaid@a34cbf0) Thanks [@aloisklink](https://github.com/aloisklink)! - fix(block): allow classdefs to update text color

-   [#7937](mermaid-js/mermaid#7937) [`f9cbe1e`](mermaid-js/mermaid@f9cbe1e) Thanks [@filipsajdak](https://github.com/filipsajdak)! - fix(dagre): let a diagram's own nodeSpacing/rankSpacing take effect in the unified dagre layout

-   [#8005](mermaid-js/mermaid#8005) [`90eeece`](mermaid-js/mermaid@90eeece) Thanks [@pbrolin47](https://github.com/pbrolin47)! - fix(flowchart): reverts the behavior change from #7672 (fix/4648-directions), since arrows between subgraphs are broken

-   [#7951](mermaid-js/mermaid#7951) [`afa2f80`](mermaid-js/mermaid@afa2f80) Thanks [@aloisklink](https://github.com/aloisklink)! - perf: use `fastdom` to batch DOM measurements (up to 25% speedup)

-   Updated dependencies \[[`e848423`](mermaid-js/mermaid@e848423)]:
    -   @mermaid-js/parser@1.2.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: give me a 98K <240642031+inhuman-0@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Graph: XY XY Chart Type: Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XYChart not allowing legends

3 participants