Skip to content

Commit 4ebdb7c

Browse files
[charts] Relax dataset type (#20294)
1 parent c02d152 commit 4ebdb7c

File tree

7 files changed

+192
-28
lines changed

7 files changed

+192
-28
lines changed

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,52 @@ describe('<BarChart />', () => {
176176
]);
177177
},
178178
);
179+
180+
it('should support dataset with missing values', async () => {
181+
const dataset = [
182+
{
183+
version: 'data-0',
184+
a1: 500,
185+
a2: 100,
186+
unusedProp: 'test',
187+
},
188+
{
189+
version: 'data-1',
190+
a1: 600,
191+
a2: 200,
192+
unusedProp: ['test'],
193+
},
194+
{
195+
version: 'data-2',
196+
// Item with missing x-values
197+
// a1: 500,
198+
a2: 200,
199+
unusedProp: { test: 'value' },
200+
},
201+
{
202+
version: 'data-3',
203+
a1: null,
204+
},
205+
{
206+
version: 'data-4',
207+
a1: undefined,
208+
},
209+
];
210+
211+
render(
212+
<BarChart
213+
dataset={dataset}
214+
xAxis={[{ dataKey: 'version' }]}
215+
series={[{ dataKey: 'a1', label: 'Series A' }]}
216+
width={500}
217+
height={300}
218+
/>,
219+
);
220+
221+
const labelX = await screen.findByText('data-3');
222+
expect(labelX).toBeVisible();
223+
224+
const labelY = await screen.findByText('600');
225+
expect(labelY).toBeVisible();
226+
});
179227
});

packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset) => {
3535
].join('\n'),
3636
);
3737
}
38+
39+
if (process.env.NODE_ENV !== 'production') {
40+
if (!data && dataset) {
41+
const dataKey = series[id].dataKey;
42+
43+
if (!dataKey) {
44+
throw new Error(
45+
[
46+
`MUI X Charts: bar series with id='${id}' has no data and no dataKey.`,
47+
'You must provide a dataKey when using the dataset prop.',
48+
].join('\n'),
49+
);
50+
}
51+
52+
dataset.forEach((entry, index) => {
53+
const value = entry[dataKey];
54+
if (value != null && typeof value !== 'number') {
55+
warnOnce(
56+
[
57+
`MUI X Charts: your dataset key "${dataKey}" is used for plotting bars, but the dataset contains the non-null non-numerical element "${value}" at index ${index}.`,
58+
'Bar plots only support numeric and null values.',
59+
].join('\n'),
60+
);
61+
}
62+
});
63+
}
64+
}
3865
});
3966

4067
const completedSeries: {
@@ -69,18 +96,8 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset) => {
6996
data: dataKey
7097
? dataset!.map((data) => {
7198
const value = data[dataKey];
72-
if (typeof value !== 'number') {
73-
if (process.env.NODE_ENV !== 'production') {
74-
if (value !== null) {
75-
warnOnce([
76-
`MUI X Charts: your dataset key "${dataKey}" is used for plotting bars, but contains nonnumerical elements.`,
77-
'Bar plots only support numbers and null values.',
78-
]);
79-
}
80-
}
81-
return null;
82-
}
83-
return value;
99+
100+
return typeof value === 'number' ? value : null;
84101
})
85102
: series[id].data!,
86103
stackedData: stackedSeries[index].map(([a, b]) => [a, b]),

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createRenderer } from '@mui/internal-test-utils/createRenderer';
22
import { describeConformance } from 'test/utils/describeConformance';
33
import { LineChart } from '@mui/x-charts/LineChart';
44
import { screen } from '@mui/internal-test-utils';
5+
import * as React from 'react';
56

67
describe('<LineChart />', () => {
78
const { render } = createRenderer();
@@ -33,4 +34,53 @@ describe('<LineChart />', () => {
3334

3435
expect(screen.getByText('No data to display')).toBeVisible();
3536
});
37+
it('should support dataset with missing values', async () => {
38+
const dataset = [
39+
{
40+
version: 'data-0',
41+
a1: 500,
42+
a2: 100,
43+
unusedProp: 'test',
44+
},
45+
{
46+
version: 'data-1',
47+
a1: 600,
48+
a2: 200,
49+
unusedProp: ['test'],
50+
},
51+
{
52+
version: 'data-2',
53+
// Item with missing x-values
54+
// a1: 500,
55+
a2: 250,
56+
unusedProp: { test: 'value' },
57+
},
58+
{
59+
version: 'data-3',
60+
a1: null,
61+
// Missing y-value,
62+
},
63+
{
64+
version: 'data-4',
65+
a1: undefined,
66+
a2: null,
67+
},
68+
];
69+
70+
render(
71+
<LineChart
72+
dataset={dataset}
73+
xAxis={[{ dataKey: 'a1' }]}
74+
series={[{ dataKey: 'a2', label: 'Series A' }]}
75+
width={500}
76+
height={300}
77+
/>,
78+
);
79+
80+
const labelX = await screen.findByText('500');
81+
expect(labelX).toBeVisible();
82+
83+
const labelY = await screen.findByText('250');
84+
expect(labelY).toBeVisible();
85+
});
3686
});

packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { defaultizeValueFormatter } from '../../internals/defaultizeValueFormatt
77
import { SeriesId } from '../../models/seriesType/common';
88
import { SeriesProcessor } from '../../internals/plugins/models';
99

10-
// For now it's a copy past of bar charts formatter, but maybe will diverge later
1110
const seriesProcessor: SeriesProcessor<'line'> = (params, dataset) => {
1211
const { seriesOrder, series } = params;
1312
const stackingGroups = getStackingGroups({ ...params, defaultStrategy: { stackOffset: 'none' } });
@@ -32,6 +31,33 @@ const seriesProcessor: SeriesProcessor<'line'> = (params, dataset) => {
3231
].join('\n'),
3332
);
3433
}
34+
35+
if (process.env.NODE_ENV !== 'production') {
36+
if (!data && dataset) {
37+
const dataKey = series[id].dataKey;
38+
39+
if (!dataKey) {
40+
throw new Error(
41+
[
42+
`MUI X Charts: line series with id='${id}' has no data and no dataKey.`,
43+
'You must provide a dataKey when using the dataset prop.',
44+
].join('\n'),
45+
);
46+
}
47+
48+
dataset.forEach((entry, index) => {
49+
const value = entry[dataKey];
50+
if (value != null && typeof value !== 'number') {
51+
warnOnce(
52+
[
53+
`MUI X Charts: your dataset key "${dataKey}" is used for plotting lines, but the dataset contains the non-null non-numerical element "${value}" at index ${index}.`,
54+
'Line plots only support numeric and null values.',
55+
].join('\n'),
56+
);
57+
}
58+
});
59+
}
60+
}
3561
});
3662

3763
const completedSeries: Record<
@@ -62,18 +88,8 @@ const seriesProcessor: SeriesProcessor<'line'> = (params, dataset) => {
6288
data: dataKey
6389
? dataset!.map((data) => {
6490
const value = data[dataKey];
65-
if (typeof value !== 'number') {
66-
if (process.env.NODE_ENV !== 'production') {
67-
if (value !== null) {
68-
warnOnce([
69-
`MUI X Charts: Your dataset key "${dataKey}" is used for plotting line, but contains nonnumerical elements.`,
70-
'Line plots only support numbers and null values.',
71-
]);
72-
}
73-
}
74-
return null;
75-
}
76-
return value;
91+
92+
return typeof value === 'number' ? value : null;
7793
})
7894
: series[id].data!,
7995
stackedData: stackedSeries[index].map(([a, b]) => [a, b]),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('<ScatterChart />', () => {
132132
expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', '(5, 5)']);
133133
});
134134

135-
it.skipIf(isJSDOM)('should support dataset with missing values', async () => {
135+
it('should support dataset with missing values', async () => {
136136
// x from 500 to 600
137137
// y from 100 to 200
138138
const dataset = [

packages/x-charts/src/ScatterChart/seriesConfig/seriesProcessor.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { warnOnce } from '@mui/x-internals/warning';
12
import { ScatterValueType } from '../../models';
23
import { SeriesProcessor } from '../../internals/plugins/models';
34

@@ -19,6 +20,38 @@ const seriesProcessor: SeriesProcessor<'scatter'> = ({ series, seriesOrder }, da
1920
);
2021
}
2122

23+
if (process.env.NODE_ENV !== 'production') {
24+
if (!seriesData.data && dataset) {
25+
// If these keys are not present, an error is thrown above
26+
const xDataKey = series[seriesId].datasetKeys!.x;
27+
const yDataKey = series[seriesId].datasetKeys!.y;
28+
29+
dataset.forEach((entry, index) => {
30+
const x = entry[xDataKey];
31+
32+
if (x != null && typeof x !== 'number') {
33+
warnOnce(
34+
[
35+
`MUI X Charts: your dataset key "${xDataKey}" is used for a scatter plot, but the dataset contains the non-null non-numerical element "${x}" at index ${index}.`,
36+
'Scatter plots only support numeric and null values.',
37+
].join('\n'),
38+
);
39+
}
40+
41+
const y = entry[yDataKey];
42+
43+
if (y != null && typeof y !== 'number') {
44+
warnOnce(
45+
[
46+
`MUI X Charts: your dataset key "${yDataKey}" is used for a scatter plot, but the dataset contains the non-null non-numerical element "${y}" at index ${index}.`,
47+
'Scatter plots only support numeric and null values.',
48+
].join('\n'),
49+
);
50+
}
51+
});
52+
}
53+
}
54+
2255
const data = !datasetKeys
2356
? (seriesData.data ?? [])
2457
: (dataset?.map((d) => {

packages/x-charts/src/models/seriesType/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,6 @@ export type ChartItemIdentifierWithData<T extends ChartSeriesType> =
123123
ChartsSeriesConfig[T]['itemIdentifierWithData'];
124124

125125
export type DatasetElementType<T> = {
126-
[key: string]: Readonly<T>;
126+
[key: string]: T;
127127
};
128-
export type DatasetType<T = number | string | Date | null | undefined> = DatasetElementType<T>[];
128+
export type DatasetType<T = unknown> = DatasetElementType<T>[];

0 commit comments

Comments
 (0)