Skip to content

Commit

Permalink
[charts] Allow dataset to be used with ScatterChart (mui#14915)
Browse files Browse the repository at this point in the history
Signed-off-by: Jose C Quintas Jr <juniorquintas@gmail.com>
Co-authored-by: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com>
Co-authored-by: alex <alex.fauquette@gmail.com>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent 3f12d3d commit 0400006
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 11 deletions.
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

0 comments on commit 0400006

Please sign in to comment.