Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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/xychart-legends.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': minor
---

feat: add legends for named XY chart line and bar series
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.

nitpick: This makes it just a little bit easier to parse the CHANGELOG.md file and release notes for changes, if you only care about some diagram type.

Suggested change
feat: add legends for named XY chart line and bar series
feat(xyChart): add legends for named line and bar series

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.

suggestion: I think making showLegend default to true is okay for this feature. 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, just in case, can you add a short line here that explains how to disable it for users that do want to disable it?

20 changes: 20 additions & 0 deletions cypress/integration/rendering/xychart/xyChart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ describe('XY Chart', () => {
{}
);
});
it('should render legends for named plots', () => {
renderGraph(
`
xychart-beta
title "An Example Chart"
x-axis ["90d", "60d", "30d", "7d", "1d", "Current"]
y-axis "Seconds" 0 --> 198.2
line "avg" [48.1, 41.5, 45.7, 72.8, 67.7, 59.9]
line "p50" [38.2, 36.8, 39.7, 54.5, 49.0, 38.4]
line "p95" [112.2, 75.3, 103.0, 177.0, 180.2, 109.4]
Comment on lines +102 to +104
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.

issue(blocking): We don't have any sceenshot tests for legends with bars instead of lines.

suggestion: Can you change one of these to use a bars instead of line?

`,
{ screenshot: false }
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.

issue(blocking): Currently, we don't have any visual regression tests of your new feature.

Can we remove this line? If we take a screenshot of it, then @argos-ci should warn us if we accidentally change how legends look, in the future.

);

cy.get('g.legend text').should('have.length', 3);
cy.get('g.legend').should('contain.text', 'avg');
cy.get('g.legend').should('contain.text', 'p50');
cy.get('g.legend').should('contain.text', 'p95');
cy.get('g.legend path').should('have.length', 3);
});
it('Decimals and negative numbers are supported', () => {
imgSnapshotTest(
`
Expand Down
30 changes: 30 additions & 0 deletions docs/syntax/xyChart.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ A line chart offers the capability to graphically depict lines.
#### Example

1. `line [2.3, 45, .98, -3.4]` it can have all valid numeric values.
2. `line "series name" [2.3, 45, .98, -3.4]` adds the line to the legend.

### Bar chart

Expand All @@ -95,6 +96,31 @@ A bar chart offers the capability to graphically depict bars.
#### Example

1. `bar [2.3, 45, .98, -3.4]` it can have all valid numeric values.
2. `bar "series name" [2.3, 45, .98, -3.4]` adds the bar to the legend.

### Legend (v\<MERMAID_RELEASE_VERSION>+)

Named line and bar plots are automatically shown in a legend. Unnamed plots are omitted from the legend.

```mermaid-example
xychart-beta
title "An Example Chart"
x-axis ["90d", "60d", "30d", "7d", "1d", "Current"]
y-axis "Seconds" 0 --> 198.2
line "avg" [48.1, 41.5, 45.7, 72.8, 67.7, 59.9]
line "p50" [38.2, 36.8, 39.7, 54.5, 49.0, 38.4]
line "p95" [112.2, 75.3, 103.0, 177.0, 180.2, 109.4]
```

```mermaid
xychart-beta
title "An Example Chart"
x-axis ["90d", "60d", "30d", "7d", "1d", "Current"]
y-axis "Seconds" 0 --> 198.2
line "avg" [48.1, 41.5, 45.7, 72.8, 67.7, 59.9]
line "p50" [38.2, 36.8, 39.7, 54.5, 49.0, 38.4]
line "p95" [112.2, 75.3, 103.0, 177.0, 180.2, 109.4]
```

#### Simplest example

Expand All @@ -114,6 +140,9 @@ xychart
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| showLegend | Legend to be shown for named plots or not | true |
| legendFontSize | Legend font size | 14 |
| legendPadding | Padding around the legend | 10 |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
Expand Down Expand Up @@ -155,6 +184,7 @@ config:
| backgroundColor | Background color of the whole chart |
| titleColor | Color of the Title text |
| dataLabelColor | Color of the Data labels (if shown) |
| legendTextColor | Color of the legend text |
| xAxisLabelColor | Color of the x-axis labels |
| xAxisTitleColor | Color of the x-axis title |
| xAxisTickColor | Color of the x-axis tick |
Expand Down
12 changes: 12 additions & 0 deletions packages/mermaid/src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,18 @@ export interface XYChartConfig extends BaseDiagramConfig {
* Should show the chart title
*/
showTitle?: boolean;
/**
* Should show a legend for named plots
*/
showLegend?: boolean;
/**
* Font size of the legend text
*/
legendFontSize?: number;
/**
* Padding around the legend
*/
legendPadding?: number;
xAxis?: XYChartAxisConfig;
yAxis?: XYChartAxisConfig;
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { describe, expect, it } from 'vitest';
import { ChartLegend } from './legend.js';
import type { XYChartConfig, XYChartData, XYChartThemeConfig } from '../interfaces.js';
import type { TextDimensionCalculator } from '../textDimensionCalculator.js';

const textDimensionCalculator: TextDimensionCalculator = {
getMaxDimension: (texts, fontSize) => ({
width: Math.max(...texts.map((text) => text.length)) * fontSize,
height: fontSize,
}),
};

const chartConfig: XYChartConfig = {
width: 700,
height: 500,
titleFontSize: 20,
titlePadding: 10,
showTitle: true,
showLegend: true,
legendFontSize: 14,
legendPadding: 10,
showDataLabel: false,
showDataLabelOutsideBar: false,
chartOrientation: 'vertical',
plotReservedSpacePercent: 50,
xAxis: {
showLabel: true,
labelFontSize: 14,
labelPadding: 5,
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
showTick: true,
tickLength: 5,
tickWidth: 2,
showAxisLine: true,
axisLineWidth: 2,
},
yAxis: {
showLabel: true,
labelFontSize: 14,
labelPadding: 5,
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
showTick: true,
tickLength: 5,
tickWidth: 2,
showAxisLine: true,
axisLineWidth: 2,
},
};

const chartThemeConfig: XYChartThemeConfig = {
backgroundColor: '#fff',
titleColor: '#111',
dataLabelColor: '#222',
legendTextColor: '#333',
xAxisLabelColor: '#444',
xAxisTitleColor: '#555',
xAxisTickColor: '#666',
xAxisLineColor: '#777',
yAxisLabelColor: '#888',
yAxisTitleColor: '#999',
yAxisTickColor: '#aaa',
yAxisLineColor: '#bbb',
plotColorPalette: '#f00,#0f0',
};

const chartData: XYChartData = {
title: 'Latency',
xAxis: {
type: 'band',
title: '',
categories: ['90d', '60d'],
},
yAxis: {
type: 'linear',
title: 'Seconds',
min: 0,
max: 100,
},
plots: [
{
type: 'line',
title: 'avg',
strokeFill: '#f00',
strokeWidth: 2,
data: [
['90d', 40],
['60d', 50],
],
},
{
type: 'bar',
title: 'p95',
fill: '#0f0',
data: [
['90d', 80],
['60d', 90],
],
},
{
type: 'line',
title: '',
strokeFill: '#00f',
strokeWidth: 2,
data: [
['90d', 30],
['60d', 35],
],
},
],
};

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.

issue: This is quite a lot of hard-coded data, which means that future changes will probably need to modify this file.

suggestion: Is there any way we can just load some default data from getConfig()/getChartThemeConfig()?

describe('ChartLegend', () => {
it('renders marker and label drawables for named line and bar plots', () => {
const legend = new ChartLegend(
textDimensionCalculator,
chartConfig,
chartData,
chartThemeConfig
);

expect(legend.calculateSpace({ width: 200, height: 200 })).toEqual({
width: 77.4,
height: 55,
});

legend.setBoundingBoxXY({ x: 100, y: 50 });
const drawables = legend.getDrawableElements();

expect(drawables).toHaveLength(3);
expect(drawables[0]).toMatchObject({
groupTexts: ['legend', 'markers'],
type: 'rect',
data: [
{
x: 110,
y: 81,
width: 10.5,
height: 10.5,
fill: '#0f0',
strokeFill: '#0f0',
strokeWidth: 0,
},
],
});
expect(drawables[1]).toMatchObject({
groupTexts: ['legend', 'markers'],
type: 'path',
data: [
{
path: 'M 110,65.25 L 120.5,65.25',
strokeFill: '#f00',
strokeWidth: 2,
},
],
});
expect(drawables[2]).toMatchObject({
groupTexts: ['legend', 'label'],
type: 'text',
data: [
{
text: 'avg',
x: 125.4,
y: 65.25,
fill: '#333',
fontSize: 14,
rotation: 0,
verticalPos: 'middle',
horizontalPos: 'left',
},
{
text: 'p95',
x: 125.4,
y: 86.25,
fill: '#333',
fontSize: 14,
rotation: 0,
verticalPos: 'middle',
horizontalPos: 'left',
},
],
});
});

it('does not render when legends are disabled or no named plots fit', () => {
const disabledLegend = new ChartLegend(
textDimensionCalculator,
{ ...chartConfig, showLegend: false },
chartData,
chartThemeConfig
);
expect(disabledLegend.calculateSpace({ width: 200, height: 200 })).toEqual({
width: 0,
height: 0,
});
expect(disabledLegend.getDrawableElements()).toEqual([]);

const crampedLegend = new ChartLegend(
textDimensionCalculator,
chartConfig,
chartData,
chartThemeConfig
);
expect(crampedLegend.calculateSpace({ width: 20, height: 20 })).toEqual({
width: 0,
height: 0,
});
expect(crampedLegend.getDrawableElements()).toEqual([]);
});
});
Loading
Loading