Skip to content

Commit

Permalink
[charts] Introduce plugins system (mui#13367)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette authored and thomasmoon committed Sep 6, 2024
1 parent 9ca107f commit 0e951bd
Show file tree
Hide file tree
Showing 33 changed files with 226 additions and 54 deletions.
1 change: 1 addition & 0 deletions docs/pages/x/api/charts/chart-container.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"describedArgs": ["highlightedItem"]
}
},
"plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } },
"xAxis": {
"type": {
"name": "arrayOf",
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/charts/responsive-chart-container.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"describedArgs": ["highlightedItem"]
}
},
"plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } },
"width": { "type": { "name": "number" } },
"xAxis": {
"type": {
Expand Down
1 change: 1 addition & 0 deletions docs/scripts/generateProptypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ async function generateProptypes(project: XTypeScriptProject, sourceFile: string
'topAxis',
'leftAxis',
'rightAxis',
'plugins',
];
if (propsToNotResolve.includes(name)) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
},
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface BarChartSlotProps
ChartsOverlaySlotProps {}

export interface BarChartProps
extends Omit<ResponsiveChartContainerProps, 'series'>,
extends Omit<ResponsiveChartContainerProps, 'series' | 'plugins'>,
Omit<ChartsAxisProps, 'slots' | 'slotProps'>,
Omit<BarPlotProps, 'slots' | 'slotProps'>,
Omit<ChartsOverlayProps, 'slots' | 'slotProps'>,
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/BarChart/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
12 changes: 6 additions & 6 deletions packages/x-charts/src/BarChart/getColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions packages/x-charts/src/BarChart/plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
28 changes: 24 additions & 4 deletions packages/x-charts/src/ChartContainer/ChartContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends ChartSeriesType = ChartSeriesType> = Omit<
ChartsSurfaceProps &
SeriesContextProviderProps &
Omit<SeriesContextProviderProps, 'seriesFormatters'> &
Omit<DrawingProviderProps, 'svgRef'> &
CartesianContextProviderProps &
Omit<CartesianContextProviderProps, 'xExtremumGetters' | 'yExtremumGetters'> &
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<T>[];
};

const ChartContainer = React.forwardRef(function ChartContainer(props: ChartContainerProps, ref) {
Expand All @@ -43,16 +51,23 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont
disableAxisListener,
highlightedItem,
onHighlightChange,
plugins,
children,
} = props;
const svgRef = React.useRef<SVGSVGElement>(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 (
<DrawingProvider width={width} height={height} margin={margin} svgRef={svgRef}>
<SeriesContextProvider series={series} colors={colors} dataset={dataset}>
<SeriesContextProvider
series={series}
colors={colors}
dataset={dataset}
seriesFormatters={seriesFormatters}
>
<CartesianContextProvider xAxis={xAxis} yAxis={yAxis} dataset={dataset}>
<InteractionProvider>
<HighlightedProvider
Expand Down Expand Up @@ -131,6 +146,11 @@ ChartContainer.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.
Expand Down
6 changes: 6 additions & 0 deletions packages/x-charts/src/ChartContainer/defaultPlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { plugin as barPlugin } from '../BarChart/plugin';
import { plugin as scatterPlugin } from '../ScatterChart/plugin';
import { plugin as linePlugin } from '../LineChart/plugin';
import { plugin as piePlugin } from '../PieChart/plugin';

export const defaultPlugins = [barPlugin, scatterPlugin, linePlugin, piePlugin];
41 changes: 41 additions & 0 deletions packages/x-charts/src/ChartContainer/usePluginsMerge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import { ChartsPluginTypes, ColorProcessorsConfig } from '../models';
import { ChartSeriesType } from '../models/seriesType/config';
import { ExtremumGettersConfig } from '../context/CartesianContextProvider';
import { SeriesFormatterConfig } from '../context/SeriesContextProvider';
import { defaultPlugins } from './defaultPlugins';

export function usePluginsMerge<T extends ChartSeriesType>(plugins?: ChartsPluginTypes<T>[]) {
const defaultizedPlugins = plugins ?? defaultPlugins;

return React.useMemo(() => {
const seriesFormatters: SeriesFormatterConfig<ChartSeriesType> = {};
const colorProcessors: ColorProcessorsConfig<ChartSeriesType> = {};
const xExtremumGetters: ExtremumGettersConfig<ChartSeriesType> = {};
const yExtremumGetters: ExtremumGettersConfig<ChartSeriesType> = {};

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]);
}
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface LineChartSlotProps
ChartsOverlaySlotProps {}

export interface LineChartProps
extends Omit<ResponsiveChartContainerProps, 'series'>,
extends Omit<ResponsiveChartContainerProps, 'series' | 'plugins'>,
Omit<ChartsAxisProps, 'slots' | 'slotProps'>,
Omit<ChartsOverlayProps, 'slots' | 'slotProps'>,
ChartsOnAxisClickHandlerProps {
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
8 changes: 4 additions & 4 deletions packages/x-charts/src/LineChart/getColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/x-charts/src/LineChart/plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
2 changes: 1 addition & 1 deletion packages/x-charts/src/PieChart/PieChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface PieChartSlotProps
ChartsOverlaySlotProps {}

export interface PieChartProps
extends Omit<ResponsiveChartContainerProps, 'series' | 'leftAxis' | 'bottomAxis'>,
extends Omit<ResponsiveChartContainerProps, 'series' | 'leftAxis' | 'bottomAxis' | 'plugins'>,
Omit<ChartsAxisProps, 'slots' | 'slotProps'>,
Omit<ChartsOverlayProps, 'slots' | 'slotProps'>,
Pick<PiePlotProps, 'skipAnimation'> {
Expand Down
9 changes: 9 additions & 0 deletions packages/x-charts/src/PieChart/plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/ScatterChart/ScatterChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface ScatterChartSlotProps
ChartsOverlaySlotProps {}

export interface ScatterChartProps
extends Omit<ResponsiveChartContainerProps, 'series'>,
extends Omit<ResponsiveChartContainerProps, 'series' | 'plugins'>,
Omit<ZAxisContextProviderProps, 'children' | 'dataset'>,
Omit<ChartsAxisProps, 'slots' | 'slotProps'>,
Omit<ChartsOverlayProps, 'slots' | 'slotProps'>,
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/ScatterChart/formatter.ts
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/x-charts/src/ScatterChart/getColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/x-charts/src/ScatterChart/plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
2 changes: 1 addition & 1 deletion packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface SparkLineChartSlotProps
ChartsTooltipSlotProps {}

export interface SparkLineChartProps
extends Omit<ResponsiveChartContainerProps, 'series' | 'xAxis' | 'yAxis' | 'margin'> {
extends Omit<ResponsiveChartContainerProps, 'series' | 'xAxis' | 'yAxis' | 'margin' | 'plugins'> {
/**
* The xAxis configuration.
* Notice it is a single configuration object, not an array of configuration.
Expand Down
5 changes: 5 additions & 0 deletions packages/x-charts/src/context/CartesianContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,6 +38,10 @@ import { SeriesId } from '../models/seriesType/common';
import { getColorScale, getOrdinalColorScale } from '../internals/colorScale';
import { useSeries } from '../hooks/useSeries';

export type ExtremumGettersConfig<T extends ChartSeriesType = CartesianChartSeriesType> = {
[K in T]?: ExtremumGetter<K>;
};

export type CartesianContextProviderProps = {
/**
* The configuration of the x-axes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('useHighlighted', () => {

it('should not throw an error when parent context is present', () => {
const { getByText } = render(
<SeriesContextProvider series={[]}>
<SeriesContextProvider series={[]} seriesFormatters={{}}>
<HighlightedProvider highlightedItem={{ seriesId: 'test-id' }}>
<UseHighlighted />
</HighlightedProvider>
Expand Down
Loading

0 comments on commit 0e951bd

Please sign in to comment.