Skip to content

Commit a1a66fc

Browse files
[charts] Add option to show marks only on the first/last values (mui#22645)
1 parent 3549efa commit a1a66fc

9 files changed

Lines changed: 237 additions & 99 deletions

File tree

docs/data/charts/lines/LineMarkShape.js

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,20 @@ import MenuItem from '@mui/material/MenuItem';
66
import { LineChart } from '@mui/x-charts/LineChart';
77
import { dataset } from '../dataset/weather';
88

9+
const marksMapping = {
10+
true: true,
11+
false: false,
12+
start: 'start',
13+
end: 'end',
14+
'({index})=>index%2===0': ({ index }) => index % 2 === 0,
15+
};
16+
17+
const marksOptions = ['true', 'false', 'start', 'end', '({index})=>index%2===0'];
18+
919
const shapes = ['circle', 'square', 'diamond', 'cross', 'star', 'triangle', 'wye'];
1020

1121
export default function LineMarkShape() {
22+
const [marks, setMarks] = React.useState('true');
1223
const [shape, setShape] = React.useState('circle');
1324

1425
return (
@@ -17,19 +28,34 @@ export default function LineMarkShape() {
1728
spacing={1}
1829
sx={{ width: '100%', padding: 2 }}
1930
>
20-
<TextField
21-
select
22-
label="shape"
23-
value={shape}
24-
onChange={(event) => setShape(event.target.value)}
25-
sx={{ minWidth: 150 }}
26-
>
27-
{shapes.map((option) => (
28-
<MenuItem key={option} value={option}>
29-
{option}
30-
</MenuItem>
31-
))}
32-
</TextField>
31+
<Stack direction={{ xs: 'row', md: 'column' }} spacing={1}>
32+
<TextField
33+
select
34+
label="marks"
35+
value={marks}
36+
onChange={(event) => setMarks(event.target.value)}
37+
sx={{ minWidth: 150 }}
38+
>
39+
{marksOptions.map((option) => (
40+
<MenuItem key={option} value={option}>
41+
{option}
42+
</MenuItem>
43+
))}
44+
</TextField>
45+
<TextField
46+
select
47+
label="shape"
48+
value={shape}
49+
onChange={(event) => setShape(event.target.value)}
50+
sx={{ minWidth: 150 }}
51+
>
52+
{shapes.map((option) => (
53+
<MenuItem key={option} value={option}>
54+
{option}
55+
</MenuItem>
56+
))}
57+
</TextField>
58+
</Stack>
3359
<Box sx={{ flexGrow: 1 }}>
3460
<LineChart
3561
height={250}
@@ -39,7 +65,7 @@ export default function LineMarkShape() {
3965
dataKey: 'london',
4066
label: 'London precipitation (mm)',
4167
curve: 'natural',
42-
showMark: true,
68+
showMark: marksMapping[marks],
4369
shape,
4470
},
4571
]}

docs/data/charts/lines/LineMarkShape.tsx

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ import { dataset } from '../dataset/weather';
88

99
type Shape = 'circle' | 'square' | 'diamond' | 'cross' | 'star' | 'triangle' | 'wye';
1010

11+
const marksMapping = {
12+
true: true,
13+
false: false,
14+
start: 'start',
15+
end: 'end',
16+
'({index})=>index%2===0': ({ index }: { index: number }) => index % 2 === 0,
17+
} as const;
18+
19+
const marksOptions: (keyof typeof marksMapping)[] = [
20+
'true',
21+
'false',
22+
'start',
23+
'end',
24+
'({index})=>index%2===0',
25+
];
26+
1127
const shapes: Shape[] = [
1228
'circle',
1329
'square',
@@ -19,6 +35,7 @@ const shapes: Shape[] = [
1935
];
2036

2137
export default function LineMarkShape() {
38+
const [marks, setMarks] = React.useState<keyof typeof marksMapping>('true');
2239
const [shape, setShape] = React.useState<Shape>('circle');
2340

2441
return (
@@ -27,19 +44,36 @@ export default function LineMarkShape() {
2744
spacing={1}
2845
sx={{ width: '100%', padding: 2 }}
2946
>
30-
<TextField
31-
select
32-
label="shape"
33-
value={shape}
34-
onChange={(event) => setShape(event.target.value as Shape)}
35-
sx={{ minWidth: 150 }}
36-
>
37-
{shapes.map((option) => (
38-
<MenuItem key={option} value={option}>
39-
{option}
40-
</MenuItem>
41-
))}
42-
</TextField>
47+
<Stack direction={{ xs: 'row', md: 'column' }} spacing={1}>
48+
<TextField
49+
select
50+
label="marks"
51+
value={marks}
52+
onChange={(event) =>
53+
setMarks(event.target.value as keyof typeof marksMapping)
54+
}
55+
sx={{ minWidth: 150 }}
56+
>
57+
{marksOptions.map((option) => (
58+
<MenuItem key={option} value={option}>
59+
{option}
60+
</MenuItem>
61+
))}
62+
</TextField>
63+
<TextField
64+
select
65+
label="shape"
66+
value={shape}
67+
onChange={(event) => setShape(event.target.value as Shape)}
68+
sx={{ minWidth: 150 }}
69+
>
70+
{shapes.map((option) => (
71+
<MenuItem key={option} value={option}>
72+
{option}
73+
</MenuItem>
74+
))}
75+
</TextField>
76+
</Stack>
4377
<Box sx={{ flexGrow: 1 }}>
4478
<LineChart
4579
height={250}
@@ -49,7 +83,7 @@ export default function LineMarkShape() {
4983
dataKey: 'london',
5084
label: 'London precipitation (mm)',
5185
curve: 'natural',
52-
showMark: true,
86+
showMark: marksMapping[marks],
5387
shape,
5488
},
5589
]}

docs/data/charts/lines/lines.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ Set the series `area` property to `true` to fill the area under the line.
5151

5252
### Marks
5353

54-
Add `showMark: true` to display marks.
54+
Add `showMark: true` to display marks for all values.
55+
56+
It also accept a values `'start'` or `'end'` to display a mark on the first (or last) value of the series
5557

5658
To modify the mark, use the property `shape`.
5759
It accepts 7 shapes: `'circle'`, `'square'`, `'diamond'`, `'cross'`, `'star'`, `'triangle'`, and `'wye'`.
5860

61+
:::info
62+
The `'start' | 'end'` options render the highlight marks instead of the regular marks.
63+
As a result, while the pointer highlights a value, the start/end mark is replaced by the highlighted one.
64+
:::
65+
5966
{{"demo": "LineMarkShape.js", "bg": "outline"}}
6067

6168
### Log scale
@@ -180,6 +187,13 @@ This uses the same curve interpolation as the rendered line (for example, `monot
180187

181188
{{"demo": "LinePointerInteraction.js"}}
182189

190+
### Highlight
191+
192+
The line highlight display marks on top of the lines and marks.
193+
Even if you set `showMark: false`.
194+
195+
You can turn off this behavior with the `disableHighlight` series property or the `disableLineItemHighlight` prop on the line chart.
196+
183197
## Styling
184198

185199
### Grid
@@ -244,16 +258,13 @@ Do not use `baseline` with stacked areas; the result will not match expectations
244258

245259
### Optimization
246260

247-
Use the `showMark` series property to show mark elements.
248-
It accepts a boolean or a callback.
249-
The demo below uses a callback to show a mark only every two data points.
261+
Avoid displaying [marks](#marks) for large dataset.
250262

251-
When a value is highlighted, a mark is drawn for that value.
252-
If the chart already shows marks (`showMark={true}`), the highlight mark is drawn on top.
263+
Either pass `'start'` or `'end'` to `showMark` to limit the number of element rendered on screen while keeping a mark for accessibility.
253264

254-
You can turn off this behavior with the `disableHighlight` series property or the `disableLineItemHighlight` prop on the line chart.
265+
Or you can display a subset of the marks by providing a callback to `showMarks`.
255266

256-
The demo shows one mark for every value with an even index.
267+
This demo shows one mark for values with even index.
257268
The highlighted point always shows a mark, whether its index is even or odd.
258269

259270
{{"demo": "MarkOptimization.js"}}

docs/pages/x/api/charts/line-series.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
},
2626
"labelMarkType": { "type": { "description": "ChartsLabelMarkType" } },
2727
"shape": { "type": { "description": "MarkShape" } },
28-
"showMark": { "type": { "description": "boolean | ((params: ShowMarkParams) =&gt; boolean)" } },
28+
"showMark": {
29+
"type": {
30+
"description": "boolean | 'start' | 'end' | ((params: ShowMarkParams) =&gt; boolean)"
31+
}
32+
},
2933
"stack": { "type": { "description": "string" } },
3034
"stackOffset": { "type": { "description": "StackOffsetType" }, "default": "'none'" },
3135
"stackOrder": { "type": { "description": "StackOrderType" }, "default": "'none'" },

docs/translations/api-docs/charts/line-series.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"description": "The shape of the mark elements.<br />Using &#39;circle&#39; renders a <code>&amp;lt;circle /&amp;gt;</code> element, while all other options render a <code>&amp;lt;path /&amp;gt;</code> instead. The path causes a small decrease in performance.<br />By default, each series has a different shape, cycling through <code>&#39;circle&#39;</code>, <code>&#39;square&#39;</code>, <code>&#39;diamond&#39;</code>, <code>&#39;cross&#39;</code>, <code>&#39;star&#39;</code>, <code>&#39;triangle&#39;</code>, <code>&#39;wye&#39;</code>.<br />If there are more than 7 series, the shapes will repeat."
3838
},
3939
"showMark": {
40-
"description": "Define which items of the series should display a mark.<br />If can be a boolean that applies to all items.<br />Or a callback that gets some item properties and returns true if the item should be displayed."
40+
"description": "Define which items of the series should display a mark.<br />It can be a boolean that applies to all items.<br />It can be <code>&#39;start&#39;</code> or <code>&#39;end&#39;</code> to only display a mark on the first or last item. Such marks reuse<br />the line highlight element, so they are replaced by the highlighted item when the pointer<br />highlights a value.<br />Or a callback that gets some item properties and returns true if the item should be displayed."
4141
},
4242
"stack": {
4343
"description": "The key that identifies the stacking group.<br />Series with the same <code>stack</code> property will be stacked together."

packages/x-charts/src/LineChart/LineChart.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,34 @@ describe('<LineChart />', () => {
168168
});
169169
});
170170

171+
describe('showMark', () => {
172+
it('should not render highlight in stead of regular marks when set to "start"', () => {
173+
render(
174+
<LineChart
175+
{...config}
176+
series={[{ dataKey: 'v1', id: 's1', showMark: 'start' }]}
177+
xAxis={[{ scaleType: 'band', dataKey: 'x' }]}
178+
/>,
179+
);
180+
181+
expect(document.querySelectorAll(`.${lineClasses.mark}`)).to.have.length(0);
182+
expect(document.querySelectorAll(`.${lineClasses.highlight}`)).to.have.length(1);
183+
});
184+
185+
it('should not render highlight in stead of regular marks when set to "end"', () => {
186+
render(
187+
<LineChart
188+
{...config}
189+
series={[{ dataKey: 'v1', id: 's1', showMark: 'end' }]}
190+
xAxis={[{ scaleType: 'band', dataKey: 'x' }]}
191+
/>,
192+
);
193+
194+
expect(document.querySelectorAll(`.${lineClasses.mark}`)).to.have.length(0);
195+
expect(document.querySelectorAll(`.${lineClasses.highlight}`)).to.have.length(1);
196+
});
197+
});
198+
171199
describe('classes', () => {
172200
it('should apply MuiLineChart classes to area elements', () => {
173201
render(

0 commit comments

Comments
 (0)