Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c685202
feat(xychart): add per-point text labels for line charts
claude Mar 28, 2026
00bbfab
docs(xychart): add per-point text labels documentation
claude Mar 29, 2026
15b9553
docs(xychart): add changeset, documentation, and e2e tests for point …
claude Mar 29, 2026
da1b5ee
fix(xychart): auto-flip point labels to avoid collision with line
claude Mar 29, 2026
b0777fc
chore: add changeset for label-line collision fix
DominicBurkart Mar 29, 2026
b0f93a3
fix(xychart): check both label positions before flipping, add stroke …
DominicBurkart Apr 4, 2026
fc44f99
fix(xychart): prevent flipped point label from clipping x-axis
DominicBurkart Mar 29, 2026
b9074fc
feat(xychart): add per-point text labels for line charts
claude Mar 28, 2026
fcc6be3
docs(xychart): add per-point text labels documentation
claude Mar 29, 2026
f1f4d45
docs(xychart): add changeset, documentation, and e2e tests for point …
claude Mar 29, 2026
706df00
fix(xychart): sanitize point labels, document bar label scope
DominicBurkart Apr 12, 2026
bf83dd7
fix(xychart): auto-flip point labels to avoid collision with line
claude Mar 29, 2026
1366c65
chore: add changeset for label-line collision fix
DominicBurkart Mar 29, 2026
4c05206
fix(xychart): check both label positions before flipping, add stroke …
DominicBurkart Apr 4, 2026
ca720b9
fix(xychart): derive point label font size from axis label config
DominicBurkart Apr 12, 2026
d1d7946
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 12, 2026
6668773
fix(xychart): prevent flipped point label from clipping x-axis
DominicBurkart Mar 29, 2026
0ea8d7b
fix(xychart): use TextDimensionCalculatorWithFont for accurate label …
DominicBurkart Apr 12, 2026
5b183bf
Merge remote-tracking branch 'origin/fix/7549_xychart-label-axis-clip…
DominicBurkart Apr 25, 2026
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
5 changes: 5 additions & 0 deletions .changeset/add-xychart-point-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': minor
---

feat: add per-point text labels for xychart line plots
5 changes: 5 additions & 0 deletions .changeset/fix-xychart-label-axis-clipping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: prevent flipped xychart point label from clipping into x-axis area
5 changes: 5 additions & 0 deletions .changeset/fix-xychart-label-line-collision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: auto-flip xychart point labels to avoid collision with line on steep slopes
1 change: 1 addition & 0 deletions .cspell/misc-terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Buzan
circo
handDrawn
KOEPF
MMLU
neato
newbranch
validify
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ stats/

.env

demos/mermaid.esm.mjs
demos/dev/**
!/demos/dev/example.html
!/demos/dev/reload.js
Expand Down
103 changes: 103 additions & 0 deletions cypress/integration/rendering/xyChart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -879,4 +879,107 @@ describe('XY Chart', () => {
});
});
});

it('should render a line chart with point labels', () => {
imgSnapshotTest(
`
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
`,
{}
);
});

it('should render a line chart with mixed labels (some points labeled, some not)', () => {
imgSnapshotTest(
`
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25 "Launch", 45, 72, 90 "Target Hit"]
`,
{}
);
});

it('should render a horizontal line chart with point labels', () => {
imgSnapshotTest(
`
xychart horizontal
title "Model Sizes"
x-axis ["Model A", "Model B", "Model C"]
y-axis "Parameters" 0 --> 100
line [20 "Small", 50 "Medium", 90 "Large"]
`,
{}
);
});

it('should render multiple lines where only one has labels', () => {
imgSnapshotTest(
`
xychart
title "Comparison"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Value" 0 --> 100
line [20, 40, 60, 80]
line [30 "Start", 50, 70, 95 "Peak"]
`,
{}
);
});

it('should render reviewer example: label at 80 "target hit" does not collide with line', () => {
imgSnapshotTest(
`
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25, 45, 80 "target hit", 90]
`,
{}
);
});

describe('Point label collision avoidance', () => {
it('should flip labels below line when steep descent causes collision', () => {
imgSnapshotTest(
`
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
`,
{}
);
});
it('should keep labels above line when no collision on gentle slope', () => {
imgSnapshotTest(
`
xychart
x-axis ["A", "B", "C"]
y-axis 0 --> 100
line [40 "Start", 50 "Mid", 45 "End"]
`,
{}
);
});
it('should handle mixed collision: some labels above, some below', () => {
imgSnapshotTest(
`
xychart
x-axis ["P1", "P2", "P3", "P4"]
y-axis 0 --> 500
line [400 "High", 50 "Low", 450 "Peak", 30 "Bottom"]
`,
{}
);
});
});
});
67 changes: 67 additions & 0 deletions demos/xychart.html
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,73 @@ <h1>XY Charts demos with all theme config</h1>
</pre>
<hr />

<hr />
<h1>Line chart with point labels</h1>
<pre class="mermaid">
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
</pre>

<hr />
<h1>Line chart with mixed labels (some points labeled, some not)</h1>
<pre class="mermaid">
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25 "Launch", 45, 72, 90 "Target Hit"]
</pre>

<hr />
<h1>Reviewer's failing example: label at 80 "target hit"</h1>
<pre class="mermaid">
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25, 45, 80 "target hit", 90]
</pre>

<hr />
<h1>Label collision avoidance: steep descent</h1>
<pre class="mermaid">
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
</pre>

<hr />
<h1>Label collision avoidance: zigzag pattern</h1>
<pre class="mermaid">
xychart
x-axis ["P1", "P2", "P3", "P4"]
y-axis 0 --> 500
line [400 "High", 50 "Low", 450 "Peak", 30 "Bottom"]
</pre>

<hr />
<h1>Axis bounds: label near bottom should not clip x-axis</h1>
<pre class="mermaid">
xychart
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [95 "Top", 5 "Bottom", 92 "Near Top", 8 "Near Bottom"]
</pre>

<hr />
<h1>Axis bounds: horizontal β€” label near right should not clip y-axis</h1>
<pre class="mermaid">
xychart horizontal
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [5 "Start", 95 "End", 8 "Low", 92 "High"]
</pre>

<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
Expand Down
43 changes: 43 additions & 0 deletions docs/syntax/xyChart.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,49 @@ xychart
bar [12,2,20,25,17,24]
```

## Per-point text labels for line charts (v\<MERMAID_RELEASE_VERSION>+)

Each data point in a `line` can optionally include a quoted string label after the numeric value. Labels render above points in vertical orientation, or to the right in horizontal orientation, using the line's stroke color.

```mermaid-example
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
```

```mermaid
xychart
title "Smallest AI models scoring above 60% on MMLU"
x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]
y-axis "Parameters (B)" 0 --> 600
line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]
```

Labels are optional per point β€” you can mix labeled and unlabeled values:

```mermaid-example
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25 "Launch", 45, 72, 90 "Target Hit"]
```

```mermaid
xychart
title "Quarterly Performance"
x-axis [Q1, Q2, Q3, Q4]
y-axis "Revenue ($M)" 0 --> 100
line [25 "Launch", 45, 72, 90 "Target Hit"]
```

Existing syntax without labels continues to work unchanged.

> **Note**
> Point labels use a fixed font size of 12px. In vertical charts, labels appear above each point. In horizontal charts, labels appear to the right. Labels are currently supported on `line` plots only; the syntax is accepted on `bar` plots but labels are ignored.

## Example on config and theme

```mermaid-example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Axis extends ChartComponent {
getTickDistance(): number;
recalculateOuterPaddingToDrawBar(): void;
setRange(range: [number, number]): void;
getRange(): [number, number];
}

export function getAxis(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import type {
XYChartThemeConfig,
XYChartConfig,
} from '../../interfaces.js';
import type { SVGGroup } from '../../../../../diagram-api/types.js';
import type { Axis } from '../axis/index.js';
import type { ChartComponent } from '../../interfaces.js';
import { TextDimensionCalculatorWithFont } from '../../textDimensionCalculator.js';
import { LinePlot } from './linePlot.js';
import { BarPlot } from './barPlot.js';

Expand All @@ -20,18 +22,21 @@ export class BasePlot implements Plot {
private boundingRect: BoundingRect;
private xAxis?: Axis;
private yAxis?: Axis;
private textDimensionCalculator: TextDimensionCalculatorWithFont;

constructor(
private chartConfig: XYChartConfig,
private chartData: XYChartData,
private chartThemeConfig: XYChartThemeConfig
private chartThemeConfig: XYChartThemeConfig,
tmpSVGGroup: SVGGroup
) {
this.boundingRect = {
x: 0,
y: 0,
width: 0,
height: 0,
};
this.textDimensionCalculator = new TextDimensionCalculatorWithFont(tmpSVGGroup);
}
setAxes(xAxis: Axis, yAxis: Axis) {
this.xAxis = xAxis;
Expand Down Expand Up @@ -63,8 +68,9 @@ export class BasePlot implements Plot {
plot,
this.xAxis,
this.yAxis,
this.chartConfig.chartOrientation,
i
this.chartConfig,
i,
this.textDimensionCalculator
);
drawableElem.push(...linePlot.getDrawableElement());
}
Expand All @@ -91,7 +97,8 @@ export class BasePlot implements Plot {
export function getPlotComponent(
chartConfig: XYChartConfig,
chartData: XYChartData,
chartThemeConfig: XYChartThemeConfig
chartThemeConfig: XYChartThemeConfig,
tmpSVGGroup: SVGGroup
): Plot {
return new BasePlot(chartConfig, chartData, chartThemeConfig);
return new BasePlot(chartConfig, chartData, chartThemeConfig, tmpSVGGroup);
}
Loading
Loading