From a0b508125a819a96adf464ecb989000d00fd102a Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:33:35 +0200 Subject: [PATCH] [charts] Introduce plugins system (#13367) --- docs/pages/x/api/charts/chart-container.json | 1 + .../charts/responsive-chart-container.json | 1 + docs/scripts/generateProptypes.ts | 1 + .../chart-container/chart-container.json | 3 ++ .../responsive-chart-container.json | 3 ++ packages/x-charts/src/BarChart/BarChart.tsx | 2 +- packages/x-charts/src/BarChart/formatter.ts | 2 +- packages/x-charts/src/BarChart/getColor.ts | 12 ++--- packages/x-charts/src/BarChart/plugin.ts | 12 +++++ .../src/ChartContainer/ChartContainer.tsx | 28 ++++++++-- .../src/ChartContainer/defaultPlugins.ts | 6 +++ .../src/ChartContainer/usePluginsMerge.ts | 41 +++++++++++++++ packages/x-charts/src/LineChart/LineChart.tsx | 2 +- packages/x-charts/src/LineChart/formatter.ts | 2 +- packages/x-charts/src/LineChart/getColor.ts | 8 +-- packages/x-charts/src/LineChart/plugin.ts | 12 +++++ packages/x-charts/src/PieChart/PieChart.tsx | 2 +- packages/x-charts/src/PieChart/plugin.ts | 9 ++++ .../ResponsiveChartContainer.tsx | 5 ++ .../src/ScatterChart/ScatterChart.tsx | 2 +- .../x-charts/src/ScatterChart/formatter.ts | 2 +- .../x-charts/src/ScatterChart/getColor.ts | 8 +-- packages/x-charts/src/ScatterChart/plugin.ts | 12 +++++ .../src/SparkLineChart/SparkLineChart.tsx | 2 +- .../src/context/CartesianContextProvider.tsx | 5 ++ .../useHighlighted.test.tsx | 2 +- .../src/context/SeriesContextProvider.tsx | 52 +++++++++++-------- .../x-charts/src/hooks/useSeries.test.tsx | 6 ++- .../src/internals/defaultizeValueFormatter.ts | 4 +- packages/x-charts/src/models/index.ts | 1 + packages/x-charts/src/models/plugin.ts | 27 ++++++++++ .../x-charts/src/models/seriesType/config.ts | 2 +- scripts/x-charts.exports.json | 3 ++ 33 files changed, 226 insertions(+), 54 deletions(-) create mode 100644 packages/x-charts/src/BarChart/plugin.ts create mode 100644 packages/x-charts/src/ChartContainer/defaultPlugins.ts create mode 100644 packages/x-charts/src/ChartContainer/usePluginsMerge.ts create mode 100644 packages/x-charts/src/LineChart/plugin.ts create mode 100644 packages/x-charts/src/PieChart/plugin.ts create mode 100644 packages/x-charts/src/ScatterChart/plugin.ts create mode 100644 packages/x-charts/src/models/plugin.ts diff --git a/docs/pages/x/api/charts/chart-container.json b/docs/pages/x/api/charts/chart-container.json index e640d966559c6..bd978a54494a9 100644 --- a/docs/pages/x/api/charts/chart-container.json +++ b/docs/pages/x/api/charts/chart-container.json @@ -32,6 +32,7 @@ "describedArgs": ["highlightedItem"] } }, + "plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } }, "xAxis": { "type": { "name": "arrayOf", diff --git a/docs/pages/x/api/charts/responsive-chart-container.json b/docs/pages/x/api/charts/responsive-chart-container.json index ae2c162995f63..cc76f819d0271 100644 --- a/docs/pages/x/api/charts/responsive-chart-container.json +++ b/docs/pages/x/api/charts/responsive-chart-container.json @@ -31,6 +31,7 @@ "describedArgs": ["highlightedItem"] } }, + "plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } }, "width": { "type": { "name": "number" } }, "xAxis": { "type": { diff --git a/docs/scripts/generateProptypes.ts b/docs/scripts/generateProptypes.ts index 9cab0f8cedfeb..b43a78c8c0b3a 100644 --- a/docs/scripts/generateProptypes.ts +++ b/docs/scripts/generateProptypes.ts @@ -71,6 +71,7 @@ async function generateProptypes(project: XTypeScriptProject, sourceFile: string 'topAxis', 'leftAxis', 'rightAxis', + 'plugins', ]; if (propsToNotResolve.includes(name)) { return false; diff --git a/docs/translations/api-docs/charts/chart-container/chart-container.json b/docs/translations/api-docs/charts/chart-container/chart-container.json index 9e58aa2904d70..625af15ee0311 100644 --- a/docs/translations/api-docs/charts/chart-container/chart-container.json +++ b/docs/translations/api-docs/charts/chart-container/chart-container.json @@ -19,6 +19,9 @@ "description": "The callback fired when the highlighted item changes.", "typeDescriptions": { "highlightedItem": "The newly highlighted item." } }, + "plugins": { + "description": "An array of plugins defining how to preprocess data. If not provided, the container supports line, bar, scatter and pie charts." + }, "series": { "description": "The array of series to display. Each type of series has its own specificity. Please refer to the appropriate docs page to learn more about it." }, diff --git a/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json b/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json index 66c192844541a..812a9391959a6 100644 --- a/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json +++ b/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json @@ -21,6 +21,9 @@ "description": "The callback fired when the highlighted item changes.", "typeDescriptions": { "highlightedItem": "The newly highlighted item." } }, + "plugins": { + "description": "An array of plugins defining how to preprocess data. If not provided, the container supports line, bar, scatter and pie charts." + }, "series": { "description": "The array of series to display. Each type of series has its own specificity. Please refer to the appropriate docs page to learn more about it." }, diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index e9e868f85cba0..d35db5a296e1a 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -51,7 +51,7 @@ export interface BarChartSlotProps ChartsOverlaySlotProps {} export interface BarChartProps - extends Omit, + extends Omit, Omit, Omit, Omit, diff --git a/packages/x-charts/src/BarChart/formatter.ts b/packages/x-charts/src/BarChart/formatter.ts index 548f363c30106..808268e3e130d 100644 --- a/packages/x-charts/src/BarChart/formatter.ts +++ b/packages/x-charts/src/BarChart/formatter.ts @@ -6,7 +6,7 @@ import { DatasetType, Formatter, } from '../models/seriesType/config'; -import defaultizeValueFormatter from '../internals/defaultizeValueFormatter'; +import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter'; import { DefaultizedProps } from '../models/helpers'; import { SeriesId } from '../models/seriesType/common'; diff --git a/packages/x-charts/src/BarChart/getColor.ts b/packages/x-charts/src/BarChart/getColor.ts index 31a06feac93b3..ccb8beb2ea232 100644 --- a/packages/x-charts/src/BarChart/getColor.ts +++ b/packages/x-charts/src/BarChart/getColor.ts @@ -3,14 +3,14 @@ import { DefaultizedBarSeriesType } from '../models/seriesType/bar'; export default function getColor( series: DefaultizedBarSeriesType, - xAxis: AxisDefaultized, - yAxis: AxisDefaultized, + xAxis?: AxisDefaultized, + yAxis?: AxisDefaultized, ) { const verticalLayout = series.layout === 'vertical'; - const bandColorScale = verticalLayout ? xAxis.colorScale : yAxis.colorScale; - const valueColorScale = verticalLayout ? yAxis.colorScale : xAxis.colorScale; - const bandValues = verticalLayout ? xAxis.data! : yAxis.data!; + const bandColorScale = verticalLayout ? xAxis?.colorScale : yAxis?.colorScale; + const valueColorScale = verticalLayout ? yAxis?.colorScale : xAxis?.colorScale; + const bandValues = verticalLayout ? xAxis?.data : yAxis?.data; if (valueColorScale) { return (dataIndex: number) => { @@ -22,7 +22,7 @@ export default function getColor( return color; }; } - if (bandColorScale) { + if (bandColorScale && bandValues) { return (dataIndex: number) => { const value = bandValues[dataIndex]; const color = value === null ? series.color : bandColorScale(value); diff --git a/packages/x-charts/src/BarChart/plugin.ts b/packages/x-charts/src/BarChart/plugin.ts new file mode 100644 index 0000000000000..1527621c8026e --- /dev/null +++ b/packages/x-charts/src/BarChart/plugin.ts @@ -0,0 +1,12 @@ +import { ChartsPluginType } from '../models/plugin'; +import { getExtremumX, getExtremumY } from './extremums'; +import formatter from './formatter'; +import getColor from './getColor'; + +export const plugin: ChartsPluginType<'bar'> = { + seriesType: 'bar', + seriesFormatter: formatter, + colorProcessor: getColor, + xExtremumGetter: getExtremumX, + yExtremumGetter: getExtremumY, +}; diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 944ab7447489d..0fcf2de946d64 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -15,16 +15,24 @@ import { } from '../context/CartesianContextProvider'; import { ChartsAxesGradients } from '../internals/components/ChartsAxesGradients'; import { HighlightedProvider, HighlightedProviderProps } from '../context'; +import { ChartsPluginTypes } from '../models/plugin'; +import { ChartSeriesType } from '../models/seriesType/config'; +import { usePluginsMerge } from './usePluginsMerge'; -export type ChartContainerProps = Omit< +export type ChartContainerProps = Omit< ChartsSurfaceProps & - SeriesContextProviderProps & + Omit & Omit & - CartesianContextProviderProps & + Omit & HighlightedProviderProps, 'children' > & { children?: React.ReactNode; + /** + * An array of plugins defining how to preprocess data. + * If not provided, the container supports line, bar, scatter and pie charts. + */ + plugins?: ChartsPluginTypes[]; }; const ChartContainer = React.forwardRef(function ChartContainer(props: ChartContainerProps, ref) { @@ -43,16 +51,23 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont disableAxisListener, highlightedItem, onHighlightChange, + plugins, children, } = props; const svgRef = React.useRef(null); const handleRef = useForkRef(ref, svgRef); + const { seriesFormatters } = usePluginsMerge(plugins); useReducedMotion(); // a11y reduce motion (see: https://react-spring.dev/docs/utilities/use-reduced-motion) return ( - + (plugins?: ChartsPluginTypes[]) { + const defaultizedPlugins = plugins ?? defaultPlugins; + + return React.useMemo(() => { + const seriesFormatters: SeriesFormatterConfig = {}; + const colorProcessors: ColorProcessorsConfig = {}; + const xExtremumGetters: ExtremumGettersConfig = {}; + const yExtremumGetters: ExtremumGettersConfig = {}; + + for (let i = 0; i < defaultizedPlugins.length; i += 1) { + const plugin = defaultizedPlugins[i]; + + // To remove those any we will need to solve this union discrimination issue: + // https://www.typescriptlang.org/play/?#code/FDAuE8AcFMAIDkCuBbARtATgYQPYDsAzASwHNYBeWAb2FlgGsi8ATALlgHI8V0MOBuWrBwwMAQ1A4M7ABQAPdtzSYAlBQB8sJb0EBfEBBiwAyqAxMSuQqQrUhjFuw4BnMxYFCRmCVNkLYruZ4JGrkmoEWeiAAxviuWqhWxCTsSMrY+Mm2VAxMbLAARNqYBQA0wqI+0rByGrAATLAAVDWw+rF48YFJpOymQZaZNpQ5DvkFEcFlFd6S1bVhsAAG9S0AJFRyukttMXGgsB3JzrYA2niJQyTl3VcAugZQcADylXPOALJikJAW2ULFDAAflSPEwPRIpw4XnEcw4d1KQkmJBBJjcwQhUJhVXhiN0gmAHXi2LmXx+FnYr1mUk+31+wWy+JABCksBkABtoAcjjYcARDldnGoaCA6AB6MWwADqUnoJxw9FgRH5AHc4L9ooroGJogALQ5iZxwPJEABuRGYiDE7PASJVRFAerZPJIADoxsKhHRooa4FwwXxWF66DNYVIyfTIS73Xk7rZoySpIIQyHUBhtfRkyGfUbOMiOEGU3RExgIxZTtGxnHKAm3kng8xoAQxIh2aBC0W0xms-pvftqLkWOUS2141chBLYABJDimuB4HBKxtiWBiVA4RAHXU4FWwSSwTkHAAqxlgiBYmFcYhYAusbrGq5vtepGFX6YPTHo0GYnjrpbp5ZVrYJZ6EAA + seriesFormatters[plugin.seriesType] = plugin.seriesFormatter as any; + + colorProcessors[plugin.seriesType] = plugin.colorProcessor as any; + + if (plugin.xExtremumGetter) { + xExtremumGetters[plugin.seriesType] = plugin.xExtremumGetter as any; + } + + if (plugin.yExtremumGetter) { + yExtremumGetters[plugin.seriesType] = plugin.yExtremumGetter as any; + } + } + return { + seriesFormatters, + colorProcessors, + xExtremumGetters, + yExtremumGetters, + }; + }, [defaultizedPlugins]); +} diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx index 1b3686e865fe7..98a15f16debd5 100644 --- a/packages/x-charts/src/LineChart/LineChart.tsx +++ b/packages/x-charts/src/LineChart/LineChart.tsx @@ -64,7 +64,7 @@ export interface LineChartSlotProps ChartsOverlaySlotProps {} export interface LineChartProps - extends Omit, + extends Omit, Omit, Omit, ChartsOnAxisClickHandlerProps { diff --git a/packages/x-charts/src/LineChart/formatter.ts b/packages/x-charts/src/LineChart/formatter.ts index 8a7cb0f645894..95a2af39db278 100644 --- a/packages/x-charts/src/LineChart/formatter.ts +++ b/packages/x-charts/src/LineChart/formatter.ts @@ -6,7 +6,7 @@ import { DatasetType, Formatter, } from '../models/seriesType/config'; -import defaultizeValueFormatter from '../internals/defaultizeValueFormatter'; +import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter'; import { DefaultizedProps } from '../models/helpers'; import { SeriesId } from '../models/seriesType/common'; diff --git a/packages/x-charts/src/LineChart/getColor.ts b/packages/x-charts/src/LineChart/getColor.ts index af924c4fc574c..07207a177cf9d 100644 --- a/packages/x-charts/src/LineChart/getColor.ts +++ b/packages/x-charts/src/LineChart/getColor.ts @@ -3,11 +3,11 @@ import { DefaultizedLineSeriesType } from '../models/seriesType/line'; export default function getColor( series: DefaultizedLineSeriesType, - xAxis: AxisDefaultized, - yAxis: AxisDefaultized, + xAxis?: AxisDefaultized, + yAxis?: AxisDefaultized, ) { - const yColorScale = yAxis.colorScale; - const xColorScale = xAxis.colorScale; + const yColorScale = yAxis?.colorScale; + const xColorScale = xAxis?.colorScale; if (yColorScale) { return (dataIndex: number) => { diff --git a/packages/x-charts/src/LineChart/plugin.ts b/packages/x-charts/src/LineChart/plugin.ts new file mode 100644 index 0000000000000..bc799c54b1ec7 --- /dev/null +++ b/packages/x-charts/src/LineChart/plugin.ts @@ -0,0 +1,12 @@ +import { ChartsPluginType } from '../models/plugin'; +import { getExtremumX, getExtremumY } from './extremums'; +import formatter from './formatter'; +import getColor from './getColor'; + +export const plugin: ChartsPluginType<'line'> = { + seriesType: 'line', + colorProcessor: getColor, + seriesFormatter: formatter, + xExtremumGetter: getExtremumX, + yExtremumGetter: getExtremumY, +}; diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index f1de888106466..9c3e1b7769723 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -52,7 +52,7 @@ export interface PieChartSlotProps ChartsOverlaySlotProps {} export interface PieChartProps - extends Omit, + extends Omit, Omit, Omit, Pick { diff --git a/packages/x-charts/src/PieChart/plugin.ts b/packages/x-charts/src/PieChart/plugin.ts new file mode 100644 index 0000000000000..53962ded67649 --- /dev/null +++ b/packages/x-charts/src/PieChart/plugin.ts @@ -0,0 +1,9 @@ +import { ChartsPluginType } from '../models/plugin'; +import formatter from './formatter'; +import getColor from './getColor'; + +export const plugin: ChartsPluginType<'pie'> = { + seriesType: 'pie', + colorProcessor: getColor, + seriesFormatter: formatter, +}; diff --git a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx index 7593d5130de0a..d58ef1b33fadc 100644 --- a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx +++ b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx @@ -103,6 +103,11 @@ ResponsiveChartContainer.propTypes = { * @param {HighlightItemData | null} highlightedItem The newly highlighted item. */ onHighlightChange: PropTypes.func, + /** + * An array of plugins defining how to preprocess data. + * If not provided, the container supports line, bar, scatter and pie charts. + */ + plugins: PropTypes.arrayOf(PropTypes.object), /** * The array of series to display. * Each type of series has its own specificity. diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index 37e6c3faed136..3548f83c99774 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -54,7 +54,7 @@ export interface ScatterChartSlotProps ChartsOverlaySlotProps {} export interface ScatterChartProps - extends Omit, + extends Omit, Omit, Omit, Omit, diff --git a/packages/x-charts/src/ScatterChart/formatter.ts b/packages/x-charts/src/ScatterChart/formatter.ts index d0db1a6ad0a5a..4f212f9afa8ef 100644 --- a/packages/x-charts/src/ScatterChart/formatter.ts +++ b/packages/x-charts/src/ScatterChart/formatter.ts @@ -1,4 +1,4 @@ -import defaultizeValueFormatter from '../internals/defaultizeValueFormatter'; +import { defaultizeValueFormatter } from '../internals/defaultizeValueFormatter'; import { Formatter } from '../models/seriesType/config'; const formatter: Formatter<'scatter'> = ({ series, seriesOrder }) => { diff --git a/packages/x-charts/src/ScatterChart/getColor.ts b/packages/x-charts/src/ScatterChart/getColor.ts index 79166556b1437..273a8b37c7042 100644 --- a/packages/x-charts/src/ScatterChart/getColor.ts +++ b/packages/x-charts/src/ScatterChart/getColor.ts @@ -4,13 +4,13 @@ import { DefaultizedScatterSeriesType } from '../models/seriesType/scatter'; export default function getColor( series: DefaultizedScatterSeriesType, - xAxis: AxisDefaultized, - yAxis: AxisDefaultized, + xAxis?: AxisDefaultized, + yAxis?: AxisDefaultized, zAxis?: ZAxisDefaultized, ) { const zColorScale = zAxis?.colorScale; - const yColorScale = yAxis.colorScale; - const xColorScale = xAxis.colorScale; + const yColorScale = yAxis?.colorScale; + const xColorScale = xAxis?.colorScale; if (zColorScale) { return (dataIndex: number) => { diff --git a/packages/x-charts/src/ScatterChart/plugin.ts b/packages/x-charts/src/ScatterChart/plugin.ts new file mode 100644 index 0000000000000..ca77573981a1b --- /dev/null +++ b/packages/x-charts/src/ScatterChart/plugin.ts @@ -0,0 +1,12 @@ +import { ChartsPluginType } from '../models/plugin'; +import { getExtremumX, getExtremumY } from './extremums'; +import formatter from './formatter'; +import getColor from './getColor'; + +export const plugin: ChartsPluginType<'scatter'> = { + seriesType: 'scatter', + seriesFormatter: formatter, + colorProcessor: getColor, + xExtremumGetter: getExtremumX, + yExtremumGetter: getExtremumY, +}; diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx index 1b0f05fd52032..d95141a27acd2 100644 --- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx +++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx @@ -40,7 +40,7 @@ export interface SparkLineChartSlotProps ChartsTooltipSlotProps {} export interface SparkLineChartProps - extends Omit { + extends Omit { /** * The xAxis configuration. * Notice it is a single configuration object, not an array of configuration. diff --git a/packages/x-charts/src/context/CartesianContextProvider.tsx b/packages/x-charts/src/context/CartesianContextProvider.tsx index 63662459ae400..7a2a1a43a79f3 100644 --- a/packages/x-charts/src/context/CartesianContextProvider.tsx +++ b/packages/x-charts/src/context/CartesianContextProvider.tsx @@ -25,6 +25,7 @@ import { getScale } from '../internals/getScale'; import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants'; import { CartesianChartSeriesType, + ChartSeriesType, ChartSeries, DatasetType, ExtremumGetter, @@ -37,6 +38,10 @@ import { SeriesId } from '../models/seriesType/common'; import { getColorScale, getOrdinalColorScale } from '../internals/colorScale'; import { useSeries } from '../hooks/useSeries'; +export type ExtremumGettersConfig = { + [K in T]?: ExtremumGetter; +}; + export type CartesianContextProviderProps = { /** * The configuration of the x-axes. diff --git a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx index 03812aae6959a..1ed2006624560 100644 --- a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx +++ b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx @@ -42,7 +42,7 @@ describe('useHighlighted', () => { it('should not throw an error when parent context is present', () => { const { getByText } = render( - + diff --git a/packages/x-charts/src/context/SeriesContextProvider.tsx b/packages/x-charts/src/context/SeriesContextProvider.tsx index 63a3055fecb36..05ee76e3bf289 100644 --- a/packages/x-charts/src/context/SeriesContextProvider.tsx +++ b/packages/x-charts/src/context/SeriesContextProvider.tsx @@ -1,9 +1,5 @@ import * as React from 'react'; import { useTheme } from '@mui/material/styles'; -import barSeriesFormatter from '../BarChart/formatter'; -import scatterSeriesFormatter from '../ScatterChart/formatter'; -import lineSeriesFormatter from '../LineChart/formatter'; -import pieSeriesFormatter from '../PieChart/formatter'; import { AllSeriesType } from '../models/seriesType'; import { defaultizeColor } from '../internals/defaultizeColor'; import { @@ -15,19 +11,29 @@ import { import { ChartsColorPalette, blueberryTwilightPalette } from '../colorPalettes'; import { Initializable } from './context.types'; -export type SeriesContextProviderProps = { +export type SeriesFormatterType = ( + series: AllSeriesType[], + colors: string[], + dataset?: DatasetType, +) => { [type in T]?: FormatterResult }; + +export type SeriesContextProviderProps = { dataset?: DatasetType; /** * The array of series to display. * Each type of series has its own specificity. * Please refer to the appropriate docs page to learn more about it. */ - series: AllSeriesType[]; + series: AllSeriesType[]; /** * Color palette used to colorize multiple series. * @default blueberryTwilightPalette */ colors?: ChartsColorPalette; + /** + * Preprocessors for each series types. + */ + seriesFormatters: SeriesFormatterConfig; children: React.ReactNode; }; @@ -42,13 +48,9 @@ if (process.env.NODE_ENV !== 'production') { SeriesContext.displayName = 'SeriesContext'; } -const seriesTypeFormatter: { - [type in ChartSeriesType]?: (series: any, dataset?: DatasetType) => any; -} = { - bar: barSeriesFormatter, - scatter: scatterSeriesFormatter, - line: lineSeriesFormatter, - pie: pieSeriesFormatter, +export type SeriesFormatterConfig = { + // TODO replace the function type by Formatter + [K in T]?: (series: FormatterParams, dataset?: DatasetType) => any; }; /** @@ -59,7 +61,12 @@ const seriesTypeFormatter: { * @param colors The color palette used to defaultize series colors * @returns An object structuring all the series by type. */ -const formatSeries = (series: AllSeriesType[], colors: string[], dataset?: DatasetType) => { +const preprocessSeries = ( + series: AllSeriesType[], + colors: string[], + seriesFormatters: SeriesFormatterConfig, + dataset?: DatasetType, +) => { // Group series by type const seriesGroups: { [type in ChartSeriesType]?: FormatterParams } = {}; series.forEach((seriesData, seriesIndex: number) => { @@ -81,31 +88,32 @@ const formatSeries = (series: AllSeriesType[], colors: string[], dataset?: Datas const formattedSeries: FormattedSeries = {}; // Apply formatter on a type group - (Object.keys(seriesTypeFormatter) as ChartSeriesType[]).forEach((type) => { - if (seriesGroups[type] !== undefined) { - formattedSeries[type] = - seriesTypeFormatter[type]?.(seriesGroups[type], dataset) ?? seriesGroups[type]; + (Object.keys(seriesFormatters) as T[]).forEach((type) => { + const group = seriesGroups[type]; + if (group !== undefined) { + formattedSeries[type] = seriesFormatters[type]?.(group, dataset) ?? seriesGroups[type]; } }); return formattedSeries; }; -function SeriesContextProvider(props: SeriesContextProviderProps) { - const { series, dataset, colors = blueberryTwilightPalette, children } = props; +function SeriesContextProvider(props: SeriesContextProviderProps) { + const { series, dataset, colors = blueberryTwilightPalette, seriesFormatters, children } = props; const theme = useTheme(); const formattedSeries = React.useMemo( () => ({ isInitialized: true, - data: formatSeries( + data: preprocessSeries( series, typeof colors === 'function' ? colors(theme.palette.mode) : colors, + seriesFormatters, dataset as DatasetType, ), }), - [series, colors, theme.palette.mode, dataset], + [series, colors, theme.palette.mode, seriesFormatters, dataset], ); return {children}; diff --git a/packages/x-charts/src/hooks/useSeries.test.tsx b/packages/x-charts/src/hooks/useSeries.test.tsx index 392bece9582b2..0e2d247a0b406 100644 --- a/packages/x-charts/src/hooks/useSeries.test.tsx +++ b/packages/x-charts/src/hooks/useSeries.test.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { ErrorBoundary, createRenderer } from '@mui/internal-test-utils'; import { useSeries } from './useSeries'; +import barFormatter from '../BarChart/formatter'; import { SeriesContextProvider } from '../context/SeriesContextProvider'; function UseSeries() { @@ -41,7 +42,10 @@ describe('useSeries', () => { it('should not throw an error when parent context is present', () => { const { getByText } = render( - + , ); diff --git a/packages/x-charts/src/internals/defaultizeValueFormatter.ts b/packages/x-charts/src/internals/defaultizeValueFormatter.ts index b9fa91ba88103..22b7040e0f1cb 100644 --- a/packages/x-charts/src/internals/defaultizeValueFormatter.ts +++ b/packages/x-charts/src/internals/defaultizeValueFormatter.ts @@ -1,6 +1,6 @@ import { SeriesId, SeriesValueFormatter } from '../models/seriesType/common'; -function defaultizeValueFormatter< +export function defaultizeValueFormatter< TValue, ISeries extends { valueFormatter?: SeriesValueFormatter }, >( @@ -19,5 +19,3 @@ function defaultizeValueFormatter< }); return defaultizedSeries; } - -export default defaultizeValueFormatter; diff --git a/packages/x-charts/src/models/index.ts b/packages/x-charts/src/models/index.ts index abb5d36002f65..9f82bffa3f6ad 100644 --- a/packages/x-charts/src/models/index.ts +++ b/packages/x-charts/src/models/index.ts @@ -1,6 +1,7 @@ export * from './seriesType'; export * from './layout'; export * from './stacking'; +export * from './plugin'; export type { AxisConfig, ChartsYAxisProps, diff --git a/packages/x-charts/src/models/plugin.ts b/packages/x-charts/src/models/plugin.ts new file mode 100644 index 0000000000000..176f5abcc3571 --- /dev/null +++ b/packages/x-charts/src/models/plugin.ts @@ -0,0 +1,27 @@ +import { ChartSeriesType, ExtremumGetter, Formatter } from './seriesType/config'; +import { AxisDefaultized } from './axis'; +import { DefaultizedSeriesType } from './seriesType'; +import { ZAxisDefaultized } from './z-axis'; + +type ColorProcessor = ( + series: DefaultizedSeriesType, + xAxis?: AxisDefaultized, + yAxis?: AxisDefaultized, + zAxis?: ZAxisDefaultized, +) => (dataIndex: number) => string; + +export type ColorProcessorsConfig = { + [Key in T]?: ColorProcessor; +}; + +export type ChartsPluginType = { + seriesType: T; + seriesFormatter: Formatter; + colorProcessor: ColorProcessor; + xExtremumGetter?: ExtremumGetter; + yExtremumGetter?: ExtremumGetter; +}; + +export type ChartsPluginTypes = { + [Key in T]: ChartsPluginType; +}[T]; diff --git a/packages/x-charts/src/models/seriesType/config.ts b/packages/x-charts/src/models/seriesType/config.ts index 952afd3ee2087..6ae2346499c8e 100644 --- a/packages/x-charts/src/models/seriesType/config.ts +++ b/packages/x-charts/src/models/seriesType/config.ts @@ -10,7 +10,7 @@ import { SeriesId } from './common'; export interface ChartsSeriesConfig { bar: { /** - * Series type when passed to the formatter (some ids are defaultised to simplify the DX) + * Series type when passed to the formatter (some ids are given default values to simplify the DX) */ seriesInput: DefaultizedProps & { color: string }; /** diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index b474cb1075ef4..22e3d6404aefb 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -92,6 +92,8 @@ { "name": "ChartsOnAxisClickHandler", "kind": "Function" }, { "name": "ChartsOnAxisClickHandlerProps", "kind": "Interface" }, { "name": "ChartsPieSorting", "kind": "TypeAlias" }, + { "name": "ChartsPluginType", "kind": "TypeAlias" }, + { "name": "ChartsPluginTypes", "kind": "TypeAlias" }, { "name": "ChartsReferenceLine", "kind": "Function" }, { "name": "ChartsReferenceLineClasses", "kind": "Interface" }, { "name": "ChartsReferenceLineClassKey", "kind": "TypeAlias" }, @@ -116,6 +118,7 @@ { "name": "cheerfulFiestaPalette", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteDark", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteLight", "kind": "Variable" }, + { "name": "ColorProcessorsConfig", "kind": "TypeAlias" }, { "name": "ComputedPieRadius", "kind": "Interface" }, { "name": "ContinuousScaleName", "kind": "TypeAlias" }, { "name": "CurveType", "kind": "TypeAlias" },