Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[charts] Allow dataset to be used with ScatterChart #14915

Merged
merged 8 commits into from
Oct 15, 2024
Merged
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
104 changes: 104 additions & 0 deletions docs/data/charts/scatter/ScatterDataset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import { ScatterChart } from '@mui/x-charts/ScatterChart';
import { axisClasses } from '@mui/x-charts/ChartsAxis';

const dataset = [
{
version: 'data-0',
a1: 329.39,
a2: 391.29,
b1: 443.28,
b2: 153.9,
},
{
version: 'data-1',
a1: 96.94,
a2: 139.6,
b1: 110.5,
b2: 217.8,
},
{
version: 'data-2',
a1: 336.35,
a2: 282.34,
b1: 175.23,
b2: 286.32,
},
{
version: 'data-3',
a1: 159.44,
a2: 384.85,
b1: 195.97,
b2: 325.12,
},
{
version: 'data-4',
a1: 188.86,
a2: 182.27,
b1: 351.77,
b2: 144.58,
},
{
version: 'data-5',
a1: 143.86,
a2: 360.22,
b1: 43.253,
b2: 146.51,
},
{
version: 'data-6',
a1: 202.02,
a2: 209.5,
b1: 376.34,
b2: 309.69,
},
{
version: 'data-7',
a1: 384.41,
a2: 258.93,
b1: 31.514,
b2: 236.38,
},
{
version: 'data-8',
a1: 256.76,
a2: 70.571,
b1: 231.31,
b2: 440.72,
},
{
version: 'data-9',
a1: 143.79,
a2: 419.02,
b1: 108.04,
b2: 20.29,
},
];

const chartSetting = {
yAxis: [
{
label: 'rainfall (mm)',
},
],
sx: {
[`.${axisClasses.left} .${axisClasses.label}`]: {
transform: 'translate(-20px, 0)',
},
},
width: 500,
height: 300,
};

export default function ScatterDataset() {
return (
<ScatterChart
dataset={dataset}
series={[
{ datasetKeys: { id: 'version', x: 'a1', y: 'a2' }, label: 'Series A' },
{ datasetKeys: { id: 'version', x: 'b1', y: 'b2' }, label: 'Series B' },
]}
{...chartSetting}
/>
);
}
104 changes: 104 additions & 0 deletions docs/data/charts/scatter/ScatterDataset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import { ScatterChart } from '@mui/x-charts/ScatterChart';
import { axisClasses } from '@mui/x-charts/ChartsAxis';

const dataset = [
{
version: 'data-0',
a1: 329.39,
a2: 391.29,
b1: 443.28,
b2: 153.9,
},
{
version: 'data-1',
a1: 96.94,
a2: 139.6,
b1: 110.5,
b2: 217.8,
},
{
version: 'data-2',
a1: 336.35,
a2: 282.34,
b1: 175.23,
b2: 286.32,
},
{
version: 'data-3',
a1: 159.44,
a2: 384.85,
b1: 195.97,
b2: 325.12,
},
{
version: 'data-4',
a1: 188.86,
a2: 182.27,
b1: 351.77,
b2: 144.58,
},
{
version: 'data-5',
a1: 143.86,
a2: 360.22,
b1: 43.253,
b2: 146.51,
},
{
version: 'data-6',
a1: 202.02,
a2: 209.5,
b1: 376.34,
b2: 309.69,
},
{
version: 'data-7',
a1: 384.41,
a2: 258.93,
b1: 31.514,
b2: 236.38,
},
{
version: 'data-8',
a1: 256.76,
a2: 70.571,
b1: 231.31,
b2: 440.72,
},
{
version: 'data-9',
a1: 143.79,
a2: 419.02,
b1: 108.04,
b2: 20.29,
},
];

const chartSetting = {
yAxis: [
{
label: 'rainfall (mm)',
},
],
sx: {
[`.${axisClasses.left} .${axisClasses.label}`]: {
transform: 'translate(-20px, 0)',
},
},
width: 500,
height: 300,
};

export default function ScatterDataset() {
return (
<ScatterChart
dataset={dataset}
series={[
{ datasetKeys: { id: 'version', x: 'a1', y: 'a2' }, label: 'Series A' },
{ datasetKeys: { id: 'version', x: 'b1', y: 'b2' }, label: 'Series B' },
]}
{...chartSetting}
/>
);
}
8 changes: 8 additions & 0 deletions docs/data/charts/scatter/ScatterDataset.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<ScatterChart
dataset={dataset}
series={[
{ datasetKeys: { id: 'version', x: 'a1', y: 'a2' }, label: 'Series A' },
{ datasetKeys: { id: 'version', x: 'b1', y: 'b2' }, label: 'Series B' },
]}
{...chartSetting}
/>
12 changes: 12 additions & 0 deletions docs/data/charts/scatter/scatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ Those objects require `x`, `y`, and `id` properties.

{{"demo": "BasicScatter.js"}}

### Using a dataset

If your data is stored in an array of objects, you can use the `dataset` helper prop.
It accepts an array of objects such as `dataset={[{a: 1, b: 32, c: 873}, {a: 2, b: 41, c: 182}, ...]}`.

You can reuse this data when defining the series.
The scatter series work a bit differently than in other charts.
You need to specify the `datasetKeys` properties which is an object that requires `x`, `y`, and `id` keys.
With an optional `z` key if needed.

{{"demo": "ScatterDataset.js"}}

## Interaction

Since scatter elements can be small, interactions do not require hovering exactly over an element.
Expand Down
2 changes: 1 addition & 1 deletion docs/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
7 changes: 6 additions & 1 deletion docs/pages/x/api/charts/scatter-series-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
"name": "ScatterSeriesType",
"imports": ["import { ScatterSeriesType } from '@mui/x-charts'"],
"properties": {
"data": { "type": { "description": "ScatterValueType[]" }, "required": true },
"type": { "type": { "description": "'scatter'" }, "required": true },
"color": { "type": { "description": "string" } },
"data": { "type": { "description": "ScatterValueType[]" } },
"datasetKeys": {
"type": {
"description": "{<br /> /**<br /> * The key used to retrieve data from the dataset for the X axis.<br /> */<br /> x: string<br /> /**<br /> * The key used to retrieve data from the dataset for the Y axis.<br /> */<br /> y: string<br /> /**<br /> * The key used to retrieve data from the dataset for the Z axis.<br /> */<br /> z?: string<br /> /**<br /> * The key used to retrieve data from the dataset for the id.<br /> */<br /> id: string<br />}"
}
},
"disableHover": { "type": { "description": "boolean" }, "default": "false" },
"highlightScope": { "type": { "description": "Partial&lt;HighlightScope&gt;" } },
"id": { "type": { "description": "SeriesId" } },
Expand Down
5 changes: 4 additions & 1 deletion docs/translations/api-docs/charts/scatter-series-type.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"interfaceDescription": "",
"propertiesDescriptions": {
"data": { "description": "" },
"type": { "description": "" },
"color": { "description": "" },
"data": { "description": "" },
"datasetKeys": {
"description": "The keys used to retrieve data from the dataset.<br /><br />When this prop is provided, all of <code>x</code>, <code>y</code>, and <code>id</code> must be provided.<br />While <code>z</code> is optional."
},
"disableHover": {
"description": "If true, the interaction will not use element hover for this series."
},
Expand Down
8 changes: 4 additions & 4 deletions packages/x-charts/src/ScatterChart/extremums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => {
seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey,
});

const seriesMinMax = series[seriesId].data.reduce<ExtremumGetterResult>(
const seriesMinMax = series[seriesId].data?.reduce<ExtremumGetterResult>(
(accSeries, d, dataIndex) => {
if (filter && !filter(d, dataIndex)) {
return accSeries;
Expand All @@ -36,7 +36,7 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => {
},
[Infinity, -Infinity],
);
return mergeMinMax(acc, seriesMinMax);
return mergeMinMax(acc, seriesMinMax ?? [Infinity, -Infinity]);
},
[Infinity, -Infinity],
);
Expand All @@ -59,7 +59,7 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => {
seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey,
});

const seriesMinMax = series[seriesId].data.reduce<ExtremumGetterResult>(
const seriesMinMax = series[seriesId].data?.reduce<ExtremumGetterResult>(
(accSeries, d, dataIndex) => {
if (filter && !filter(d, dataIndex)) {
return accSeries;
Expand All @@ -68,7 +68,7 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => {
},
[Infinity, -Infinity],
);
return mergeMinMax(acc, seriesMinMax);
return mergeMinMax(acc, seriesMinMax ?? [Infinity, -Infinity]);
},
[Infinity, -Infinity],
);
Expand Down
45 changes: 42 additions & 3 deletions packages/x-charts/src/ScatterChart/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter';
import { SeriesFormatter } from '../context/PluginProvider/SeriesFormatter.types';
import { ScatterValueType } from '../models';

const formatter: SeriesFormatter<'scatter'> = ({ series, seriesOrder }, dataset) => {
const completeSeries = Object.fromEntries(
Object.entries(series).map(([seriesId, seriesData]) => {
const datasetKeys = seriesData?.datasetKeys;

const missingKeys = (['x', 'y', 'id'] as const).filter(
(key) => typeof datasetKeys?.[key] !== 'string',
);

if (seriesData?.datasetKeys && missingKeys.length > 0) {
throw new Error(
[
`MUI X: scatter series with id='${seriesId}' has incomplete datasetKeys.`,
`Properties ${missingKeys.map((key) => `"${key}"`).join(', ')} are missing.`,
].join('\n'),
);
}

const data = !datasetKeys
? (seriesData.data ?? [])
: (dataset?.map((d) => {
return {
x: d[datasetKeys.x],
y: d[datasetKeys.y],
z: datasetKeys.z && d[datasetKeys.z],
id: d[datasetKeys.id],
} as ScatterValueType;
}) ?? []);

return [
seriesId,
{
...seriesData,
data,
valueFormatter: seriesData.valueFormatter ?? ((v) => `(${v.x}, ${v.y})`),
},
];
}),
);

const formatter: SeriesFormatter<'scatter'> = ({ series, seriesOrder }) => {
return {
series: defaultizeValueFormatter(series, (v) => `(${v.x}, ${v.y})`),
series: completeSeries,
seriesOrder,
};
};
Expand Down
Loading