Skip to content

Commit

Permalink
[charts] Use plugins to define series extremum and colors (#13397)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette authored Jun 7, 2024
1 parent 1a0ccab commit 4feee8d
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 189 deletions.
66 changes: 38 additions & 28 deletions packages/x-charts/src/ChartContainer/ChartContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SeriesContextProviderProps,
} from '../context/SeriesContextProvider';
import { InteractionProvider } from '../context/InteractionProvider';
import { ColorProvider } from '../context/ColorProvider';
import { useReducedMotion } from '../hooks/useReducedMotion';
import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface';
import {
Expand Down Expand Up @@ -57,39 +58,48 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont
const svgRef = React.useRef<SVGSVGElement>(null);
const handleRef = useForkRef(ref, svgRef);

const { seriesFormatters } = usePluginsMerge(plugins);
const { xExtremumGetters, yExtremumGetters, seriesFormatters, colorProcessors } =
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}
seriesFormatters={seriesFormatters}
>
<CartesianContextProvider xAxis={xAxis} yAxis={yAxis} dataset={dataset}>
<InteractionProvider>
<HighlightedProvider
highlightedItem={highlightedItem}
onHighlightChange={onHighlightChange}
>
<ChartsSurface
width={width}
height={height}
ref={handleRef}
sx={sx}
title={title}
desc={desc}
disableAxisListener={disableAxisListener}
<ColorProvider colorProcessors={colorProcessors}>
<SeriesContextProvider
series={series}
colors={colors}
dataset={dataset}
seriesFormatters={seriesFormatters}
>
<CartesianContextProvider
xAxis={xAxis}
yAxis={yAxis}
dataset={dataset}
xExtremumGetters={xExtremumGetters}
yExtremumGetters={yExtremumGetters}
>
<InteractionProvider>
<HighlightedProvider
highlightedItem={highlightedItem}
onHighlightChange={onHighlightChange}
>
<ChartsAxesGradients />
{children}
</ChartsSurface>
</HighlightedProvider>
</InteractionProvider>
</CartesianContextProvider>
</SeriesContextProvider>
<ChartsSurface
width={width}
height={height}
ref={handleRef}
sx={sx}
title={title}
desc={desc}
disableAxisListener={disableAxisListener}
>
<ChartsAxesGradients />
{children}
</ChartsSurface>
</HighlightedProvider>
</InteractionProvider>
</CartesianContextProvider>
</SeriesContextProvider>
</ColorProvider>
</DrawingProvider>
);
});
Expand Down
44 changes: 24 additions & 20 deletions packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AxisDefaultized } from '../models/axis';
import { ChartsTooltipClasses } from './chartsTooltipClasses';
import { DefaultChartsAxisTooltipContent } from './DefaultChartsAxisTooltipContent';
import { isCartesianSeriesType } from '../internals/isCartesian';
import colorGetter from '../internals/colorGetter';
import { useColorProcessor } from '../hooks/useColor';
import { ZAxisContext } from '../context/ZAxisContextProvider';
import { useSeries } from '../hooks/useSeries';

Expand Down Expand Up @@ -63,6 +63,8 @@ function ChartsAxisTooltipContent(props: {
const { zAxisIds, zAxis } = React.useContext(ZAxisContext);
const series = useSeries();

const colorProcessors = useColorProcessor();

const USED_AXIS_ID = isXaxis ? xAxisIds[0] : yAxisIds[0];

const relevantSeries = React.useMemo(() => {
Expand All @@ -76,31 +78,33 @@ function ChartsAxisTooltipContent(props: {
if (axisKey === undefined || axisKey === USED_AXIS_ID) {
const seriesToAdd = series[seriesType]!.series[seriesId];

let getColor: (index: number) => string;
switch (seriesToAdd.type) {
case 'scatter':
getColor = colorGetter(
seriesToAdd,
xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]],
yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]],
zAxis[seriesToAdd.zAxisKey ?? zAxisIds[0]],
);
break;
default:
getColor = colorGetter(
seriesToAdd,
xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]],
yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]],
);
break;
}
const zAxisKey = (seriesToAdd as any).zAxisKey ?? zAxisIds[0];

const getColor =
colorProcessors[seriesType]?.(
seriesToAdd as any,
xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]],
yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]],
zAxisKey && zAxis[zAxisKey],
) ?? (() => '');

rep.push({ ...seriesToAdd, getColor });
}
});
});
return rep;
}, [USED_AXIS_ID, isXaxis, series, xAxis, xAxisIds, yAxis, yAxisIds, zAxis, zAxisIds]);
}, [
USED_AXIS_ID,
colorProcessors,
isXaxis,
series,
xAxis,
xAxisIds,
yAxis,
yAxisIds,
zAxis,
zAxisIds,
]);

const relevantAxis = React.useMemo(() => {
return isXaxis ? xAxis[USED_AXIS_ID] : yAxis[USED_AXIS_ID];
Expand Down
37 changes: 12 additions & 25 deletions packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { ChartSeriesDefaultized, ChartSeriesType } from '../models/seriesType/co
import { ChartsTooltipClasses } from './chartsTooltipClasses';
import { DefaultChartsItemTooltipContent } from './DefaultChartsItemTooltipContent';
import { CartesianContext } from '../context/CartesianContextProvider';
import colorGetter from '../internals/colorGetter';
import { ZAxisContext } from '../context/ZAxisContextProvider';
import { useColorProcessor } from '../hooks/useColor';
import { useSeries } from '../hooks/useSeries';

export type ChartsItemContentProps<T extends ChartSeriesType = ChartSeriesType> = {
Expand Down Expand Up @@ -46,32 +46,19 @@ function ChartsItemTooltipContent<T extends ChartSeriesType>(props: {

const { xAxis, yAxis, xAxisIds, yAxisIds } = React.useContext(CartesianContext);
const { zAxis, zAxisIds } = React.useContext(ZAxisContext);
const colorProcessors = useColorProcessor();

const defaultXAxisId = xAxisIds[0];
const defaultYAxisId = yAxisIds[0];
const defaultZAxisId = zAxisIds[0];
const xAxisKey = (series as any).xAxisKey ?? xAxisIds[0];
const yAxisKey = (series as any).yAxisKey ?? yAxisIds[0];
const zAxisKey = (series as any).zAxisKey ?? zAxisIds[0];

let getColor: (index: number) => string;
switch (series.type) {
case 'pie':
getColor = colorGetter(series);
break;
case 'scatter':
getColor = colorGetter(
series,
xAxis[series.xAxisKey ?? defaultXAxisId],
yAxis[series.yAxisKey ?? defaultYAxisId],
zAxis[series.zAxisKey ?? defaultZAxisId],
);
break;
default:
getColor = colorGetter(
series,
xAxis[series.xAxisKey ?? defaultXAxisId],
yAxis[series.yAxisKey ?? defaultYAxisId],
);
break;
}
const getColor =
colorProcessors[series.type]?.(
series as any,
xAxisKey && xAxis[xAxisKey],
yAxisKey && yAxis[yAxisKey],
zAxisKey && zAxis[zAxisKey],
) ?? (() => '');

const Content = content ?? DefaultChartsItemTooltipContent;
const chartTooltipContentProps = useSlotProps({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,28 @@ function DefaultChartsAxisTooltipContent(props: ChartsAxisContentProps) {
)}

<tbody>
{series
.filter(isCartesianSeries)
.map(({ color, id, label, valueFormatter, data, getColor }) => {
// @ts-ignore
const formattedValue = valueFormatter(data[dataIndex] ?? null, { dataIndex });
if (formattedValue == null) {
return null;
}
const formattedLabel = getLabel(label, 'tooltip');
return (
<ChartsTooltipRow key={id} className={classes.row}>
<ChartsTooltipCell className={clsx(classes.markCell, classes.cell)}>
<ChartsTooltipMark
color={getColor(dataIndex) ?? color}
className={classes.mark}
/>
</ChartsTooltipCell>
<ChartsTooltipCell className={clsx(classes.labelCell, classes.cell)}>
{formattedLabel ? <Typography>{formattedLabel}</Typography> : null}
</ChartsTooltipCell>
<ChartsTooltipCell className={clsx(classes.valueCell, classes.cell)}>
<Typography>{formattedValue}</Typography>
</ChartsTooltipCell>
</ChartsTooltipRow>
);
})}
{series.filter(isCartesianSeries).map(({ id, label, valueFormatter, data, getColor }) => {
// @ts-ignore
const formattedValue = valueFormatter(data[dataIndex] ?? null, { dataIndex });
if (formattedValue == null) {
return null;
}
const formattedLabel = getLabel(label, 'tooltip');
const color = getColor(dataIndex);
return (
<ChartsTooltipRow key={id} className={classes.row}>
<ChartsTooltipCell className={clsx(classes.markCell, classes.cell)}>
{color && <ChartsTooltipMark color={color} className={classes.mark} />}
</ChartsTooltipCell>
<ChartsTooltipCell className={clsx(classes.labelCell, classes.cell)}>
{formattedLabel ? <Typography>{formattedLabel}</Typography> : null}
</ChartsTooltipCell>
<ChartsTooltipCell className={clsx(classes.valueCell, classes.cell)}>
<Typography>{formattedValue}</Typography>
</ChartsTooltipCell>
</ChartsTooltipRow>
);
})}
</tbody>
</ChartsTooltipTable>
</ChartsTooltipPaper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function DefaultChartsItemTooltipContent<T extends ChartSeriesType = ChartSeries
displayedLabel: getLabel(series.data[itemData.dataIndex].label, 'tooltip'),
}
: {
color: getColor(itemData.dataIndex) ?? series.color,
color: getColor(itemData.dataIndex),
displayedLabel: getLabel(series.label, 'tooltip'),
};

Expand Down
52 changes: 22 additions & 30 deletions packages/x-charts/src/context/CartesianContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import * as React from 'react';
import { scaleBand, scalePoint } from 'd3-scale';
import {
getExtremumX as getBarExtremumX,
getExtremumY as getBarExtremumY,
} from '../BarChart/extremums';
import {
getExtremumX as getScatterExtremumX,
getExtremumY as getScatterExtremumY,
} from '../ScatterChart/extremums';
import {
getExtremumX as getLineExtremumX,
getExtremumY as getLineExtremumY,
} from '../LineChart/extremums';
import {
AxisConfig,
AxisDefaultized,
Expand Down Expand Up @@ -59,25 +47,20 @@ export type CartesianContextProviderProps = {
* An array of objects that can be used to populate series and axes data using their `dataKey` property.
*/
dataset?: DatasetType;
/**
* An object with x-axis extremum getters per series type.
*/
xExtremumGetters: ExtremumGettersConfig;
/**
* An object with y-axis extremum getters per series type.
*/
yExtremumGetters: ExtremumGettersConfig;
children: React.ReactNode;
};

const DEFAULT_CATEGORY_GAP_RATIO = 0.2;
const DEFAULT_BAR_GAP_RATIO = 0.1;

// TODO: those might be better placed in a distinct file
const xExtremumGetters: { [T in CartesianChartSeriesType]: ExtremumGetter<T> } = {
bar: getBarExtremumX,
scatter: getScatterExtremumX,
line: getLineExtremumX,
};

const yExtremumGetters: { [T in CartesianChartSeriesType]: ExtremumGetter<T> } = {
bar: getBarExtremumY,
scatter: getScatterExtremumY,
line: getLineExtremumY,
};

type DefaultizedAxisConfig<AxisProps> = {
[axisKey: string]: AxisDefaultized<ScaleName, any, AxisProps>;
};
Expand Down Expand Up @@ -111,7 +94,14 @@ if (process.env.NODE_ENV !== 'production') {
}

function CartesianContextProvider(props: CartesianContextProviderProps) {
const { xAxis: inXAxis, yAxis: inYAxis, dataset, children } = props;
const {
xAxis: inXAxis,
yAxis: inYAxis,
dataset,
xExtremumGetters,
yExtremumGetters,
children,
} = props;
const formattedSeries = useSeries();
const drawingArea = useDrawingArea();

Expand Down Expand Up @@ -156,17 +146,17 @@ function CartesianContextProvider(props: CartesianContextProviderProps) {
acc: ExtremumGetterResult,
chartType: T,
axis: AxisConfig,
getters: { [T2 in CartesianChartSeriesType]: ExtremumGetter<T2> },
getters: { [T2 in CartesianChartSeriesType]?: ExtremumGetter<T2> },
isDefaultAxis: boolean,
): ExtremumGetterResult => {
const getter = getters[chartType];
const series = (formattedSeries[chartType]?.series as Record<SeriesId, ChartSeries<T>>) ?? {};

const [minChartTypeData, maxChartTypeData] = getter({
const [minChartTypeData, maxChartTypeData] = getter?.({
series,
axis,
isDefaultAxis,
});
}) ?? [null, null];

const [minData, maxData] = acc;

Expand All @@ -183,7 +173,7 @@ function CartesianContextProvider(props: CartesianContextProviderProps) {

const getAxisExtremum = (
axis: AxisConfig,
getters: { [T in CartesianChartSeriesType]: ExtremumGetter<T> },
getters: { [T in CartesianChartSeriesType]?: ExtremumGetter<T> },
isDefaultAxis: boolean,
) => {
const charTypes = Object.keys(getters) as CartesianChartSeriesType[];
Expand Down Expand Up @@ -356,7 +346,9 @@ function CartesianContextProvider(props: CartesianContextProviderProps) {
drawingArea.width,
formattedSeries,
xAxis,
xExtremumGetters,
yAxis,
yExtremumGetters,
]);

// @ts-ignore
Expand Down
Loading

0 comments on commit 4feee8d

Please sign in to comment.