diff --git a/docs/data/charts/scatter/ScatterDataset.js b/docs/data/charts/scatter/ScatterDataset.js new file mode 100644 index 000000000000..5a50fdc79676 --- /dev/null +++ b/docs/data/charts/scatter/ScatterDataset.js @@ -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 ( + + ); +} diff --git a/docs/data/charts/scatter/ScatterDataset.tsx b/docs/data/charts/scatter/ScatterDataset.tsx new file mode 100644 index 000000000000..5a50fdc79676 --- /dev/null +++ b/docs/data/charts/scatter/ScatterDataset.tsx @@ -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 ( + + ); +} diff --git a/docs/data/charts/scatter/ScatterDataset.tsx.preview b/docs/data/charts/scatter/ScatterDataset.tsx.preview new file mode 100644 index 000000000000..41a49c4bd157 --- /dev/null +++ b/docs/data/charts/scatter/ScatterDataset.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/charts/scatter/scatter.md b/docs/data/charts/scatter/scatter.md index 967e8eabbf38..0422d0c52858 100644 --- a/docs/data/charts/scatter/scatter.md +++ b/docs/data/charts/scatter/scatter.md @@ -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. diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts index 4f11a03dc6cc..a4a7b3f5cfa2 100644 --- a/docs/next-env.d.ts +++ b/docs/next-env.d.ts @@ -2,4 +2,4 @@ /// // 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. diff --git a/docs/pages/x/api/charts/scatter-series-type.json b/docs/pages/x/api/charts/scatter-series-type.json index 7488c7cc1a37..9d560abcee2d 100644 --- a/docs/pages/x/api/charts/scatter-series-type.json +++ b/docs/pages/x/api/charts/scatter-series-type.json @@ -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": "{
/**
* The key used to retrieve data from the dataset for the X axis.
*/
x: string
/**
* The key used to retrieve data from the dataset for the Y axis.
*/
y: string
/**
* The key used to retrieve data from the dataset for the Z axis.
*/
z?: string
/**
* The key used to retrieve data from the dataset for the id.
*/
id: string
}" + } + }, "disableHover": { "type": { "description": "boolean" }, "default": "false" }, "highlightScope": { "type": { "description": "Partial<HighlightScope>" } }, "id": { "type": { "description": "SeriesId" } }, diff --git a/docs/translations/api-docs/charts/scatter-series-type.json b/docs/translations/api-docs/charts/scatter-series-type.json index f2c801b5c89d..4f3fed0aa9e9 100644 --- a/docs/translations/api-docs/charts/scatter-series-type.json +++ b/docs/translations/api-docs/charts/scatter-series-type.json @@ -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.

When this prop is provided, all of x, y, and id must be provided.
While z is optional." + }, "disableHover": { "description": "If true, the interaction will not use element hover for this series." }, diff --git a/packages/x-charts/src/ScatterChart/extremums.ts b/packages/x-charts/src/ScatterChart/extremums.ts index 77fcd28946be..806295a1997f 100644 --- a/packages/x-charts/src/ScatterChart/extremums.ts +++ b/packages/x-charts/src/ScatterChart/extremums.ts @@ -27,7 +27,7 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, }); - const seriesMinMax = series[seriesId].data.reduce( + const seriesMinMax = series[seriesId].data?.reduce( (accSeries, d, dataIndex) => { if (filter && !filter(d, dataIndex)) { return accSeries; @@ -36,7 +36,7 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { }, [Infinity, -Infinity], ); - return mergeMinMax(acc, seriesMinMax); + return mergeMinMax(acc, seriesMinMax ?? [Infinity, -Infinity]); }, [Infinity, -Infinity], ); @@ -59,7 +59,7 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, }); - const seriesMinMax = series[seriesId].data.reduce( + const seriesMinMax = series[seriesId].data?.reduce( (accSeries, d, dataIndex) => { if (filter && !filter(d, dataIndex)) { return accSeries; @@ -68,7 +68,7 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { }, [Infinity, -Infinity], ); - return mergeMinMax(acc, seriesMinMax); + return mergeMinMax(acc, seriesMinMax ?? [Infinity, -Infinity]); }, [Infinity, -Infinity], ); diff --git a/packages/x-charts/src/ScatterChart/formatter.ts b/packages/x-charts/src/ScatterChart/formatter.ts index 671b8fc4353d..60c024f88c2e 100644 --- a/packages/x-charts/src/ScatterChart/formatter.ts +++ b/packages/x-charts/src/ScatterChart/formatter.ts @@ -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, }; }; diff --git a/packages/x-charts/src/models/seriesType/scatter.ts b/packages/x-charts/src/models/seriesType/scatter.ts index 4048b662c388..211763f1ef36 100644 --- a/packages/x-charts/src/models/seriesType/scatter.ts +++ b/packages/x-charts/src/models/seriesType/scatter.ts @@ -13,7 +13,7 @@ export type ScatterValueType = { export interface ScatterSeriesType extends CommonSeriesType, CartesianSeriesType { type: 'scatter'; - data: ScatterValueType[]; + data?: ScatterValueType[]; markerSize?: number; /** * The label to display on the tooltip or the legend. It can be a string or a function. @@ -33,6 +33,31 @@ export interface ScatterSeriesType extends CommonSeriesType, C * The id of the z-axis used to render the series. */ zAxisId?: string; + + /** + * The keys used to retrieve data from the dataset. + * + * When this prop is provided, all of `x`, `y`, and `id` must be provided. + * While `z` is optional. + */ + datasetKeys?: { + /** + * The key used to retrieve data from the dataset for the X axis. + */ + x: string; + /** + * The key used to retrieve data from the dataset for the Y axis. + */ + y: string; + /** + * The key used to retrieve data from the dataset for the Z axis. + */ + z?: string; + /** + * The key used to retrieve data from the dataset for the id. + */ + id: string; + }; } /**