Skip to content

Commit

Permalink
[charts] Make useChartGradientId public (#16106)
Browse files Browse the repository at this point in the history
  • Loading branch information
JCQuintas authored Jan 13, 2025
1 parent 6e76ec9 commit 2c9ddc9
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 60 deletions.
6 changes: 3 additions & 3 deletions packages/x-charts/src/ChartsLegend/ContinuousColorLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ContinuousColorLegendClasses,
useUtilityClasses,
} from './continuousColorLegendClasses';
import { useChartGradientObjectBound } from '../internals/components/ChartsAxesGradients';
import { useChartGradientIdObjectBoundBuilder } from '../hooks/useChartGradientId';

type LabelFormatter = (params: { value: number | Date; formattedValue: string }) => string;

Expand Down Expand Up @@ -210,7 +210,7 @@ const ContinuousColorLegend = consumeThemeProps(
...other
} = props;

const generateGradientId = useChartGradientObjectBound();
const generateGradientId = useChartGradientIdObjectBoundBuilder();
const axisItem = useAxis({ axisDirection, axisId });

const colorMap = axisItem?.colorMap;
Expand Down Expand Up @@ -261,7 +261,7 @@ const ContinuousColorLegend = consumeThemeProps(
rotate={rotateGradient}
reverse={reverse}
thickness={thickness}
gradientId={gradientId ?? generateGradientId(axisItem.id, axisDirection!)}
gradientId={gradientId ?? generateGradientId(axisItem.id)}
/>
</li>
{reverse ? minComponent : maxComponent}
Expand Down
19 changes: 9 additions & 10 deletions packages/x-charts/src/LineChart/AreaPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import { getValueToPositionMapper } from '../hooks/useScale';
import getCurveFactory from '../internals/getCurve';
import { DEFAULT_X_AXIS_KEY } from '../constants';
import { LineItemIdentifier } from '../models/seriesType/line';
import { useChartGradient } from '../internals/components/ChartsAxesGradients';
import { useLineSeries } from '../hooks/useSeries';
import { AxisId } from '../models/axis';
import { useSkipAnimation } from '../context/AnimationProvider';
import { useChartGradientIdBuilder } from '../hooks/useChartGradientId';
import { useXAxes, useYAxes } from '../hooks/useAxis';

export interface AreaPlotSlots extends AreaElementSlots {}
Expand Down Expand Up @@ -52,6 +51,7 @@ const useAggregatedData = () => {
const seriesData = useLineSeries();
const { xAxis, xAxisIds } = useXAxes();
const { yAxis, yAxisIds } = useYAxes();
const getGradientId = useChartGradientIdBuilder();

// This memo prevents odd line chart behavior when hydrating.
const allData = React.useMemo(() => {
Expand Down Expand Up @@ -81,9 +81,9 @@ const useAggregatedData = () => {
const yScale = yAxis[yAxisId].scale;
const xData = xAxis[xAxisId].data;

const gradientUsed: [AxisId, 'x' | 'y'] | undefined =
(yAxis[yAxisId].colorScale && [yAxisId, 'y']) ||
(xAxis[xAxisId].colorScale && [xAxisId, 'x']) ||
const gradientId: string | undefined =
(yAxis[yAxisId].colorScale && getGradientId(yAxisId)) ||
(xAxis[xAxisId].colorScale && getGradientId(xAxisId)) ||
undefined;

if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -137,13 +137,13 @@ const useAggregatedData = () => {
const d = areaPath.curve(curve)(d3Data) || '';
return {
...series[seriesId],
gradientUsed,
gradientId,
d,
seriesId,
};
});
});
}, [seriesData, xAxisIds, yAxisIds, xAxis, yAxis]);
}, [seriesData, xAxisIds, yAxisIds, xAxis, yAxis, getGradientId]);

return allData;
};
Expand All @@ -163,20 +163,19 @@ function AreaPlot(props: AreaPlotProps) {
const { slots, slotProps, onItemClick, skipAnimation: inSkipAnimation, ...other } = props;
const skipAnimation = useSkipAnimation(inSkipAnimation);

const getGradientId = useChartGradient();
const completedData = useAggregatedData();

return (
<AreaPlotRoot {...other}>
{completedData.map(
({ d, seriesId, color, area, gradientUsed }) =>
({ d, seriesId, color, area, gradientId }) =>
!!area && (
<AreaElement
key={seriesId}
id={seriesId}
d={d}
color={color}
gradientId={gradientUsed && getGradientId(...gradientUsed)}
gradientId={gradientId}
slots={slots}
slotProps={slotProps}
onClick={onItemClick && ((event) => onItemClick(event, { type: 'line', seriesId }))}
Expand Down
19 changes: 9 additions & 10 deletions packages/x-charts/src/LineChart/LinePlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import { getValueToPositionMapper } from '../hooks/useScale';
import getCurveFactory from '../internals/getCurve';
import { DEFAULT_X_AXIS_KEY } from '../constants';
import { LineItemIdentifier } from '../models/seriesType/line';
import { useChartGradient } from '../internals/components/ChartsAxesGradients';
import { useLineSeries } from '../hooks/useSeries';
import { AxisId } from '../models/axis';
import { useSkipAnimation } from '../context/AnimationProvider';
import { useChartGradientIdBuilder } from '../hooks/useChartGradientId';
import { useXAxes, useYAxes } from '../hooks';

export interface LinePlotSlots extends LineElementSlots {}
Expand Down Expand Up @@ -53,6 +52,7 @@ const useAggregatedData = () => {

const { xAxis, xAxisIds } = useXAxes();
const { yAxis, yAxisIds } = useYAxes();
const getGradientId = useChartGradientIdBuilder();

// This memo prevents odd line chart behavior when hydrating.
const allData = React.useMemo(() => {
Expand All @@ -78,9 +78,9 @@ const useAggregatedData = () => {
const yScale = yAxis[yAxisId].scale;
const xData = xAxis[xAxisId].data;

const gradientUsed: [AxisId, 'x' | 'y'] | undefined =
(yAxis[yAxisId].colorScale && [yAxisId, 'y']) ||
(xAxis[xAxisId].colorScale && [xAxisId, 'x']) ||
const gradientId: string | undefined =
(yAxis[yAxisId].colorScale && getGradientId(yAxisId)) ||
(xAxis[xAxisId].colorScale && getGradientId(xAxisId)) ||
undefined;

if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -116,13 +116,13 @@ const useAggregatedData = () => {
const d = linePath.curve(getCurveFactory(series[seriesId].curve))(d3Data) || '';
return {
...series[seriesId],
gradientUsed,
gradientId,
d,
seriesId,
};
});
});
}, [seriesData, xAxisIds, yAxisIds, xAxis, yAxis]);
}, [seriesData, xAxisIds, yAxisIds, xAxis, yAxis, getGradientId]);

return allData;
};
Expand All @@ -141,18 +141,17 @@ function LinePlot(props: LinePlotProps) {
const { slots, slotProps, skipAnimation: inSkipAnimation, onItemClick, ...other } = props;
const skipAnimation = useSkipAnimation(inSkipAnimation);

const getGradientId = useChartGradient();
const completedData = useAggregatedData();
return (
<LinePlotRoot {...other}>
{completedData.map(({ d, seriesId, color, gradientUsed }) => {
{completedData.map(({ d, seriesId, color, gradientId }) => {
return (
<LineElement
key={seriesId}
id={seriesId}
d={d}
color={color}
gradientId={gradientUsed && getGradientId(...gradientUsed)}
gradientId={gradientId}
skipAnimation={skipAnimation}
slots={slots}
slotProps={slotProps}
Expand Down
4 changes: 2 additions & 2 deletions packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MakeOptional } from '@mui/x-internals/types';
import { BarPlot } from '../BarChart';
import { LinePlot, AreaPlot, LineHighlightPlot } from '../LineChart';
import { ChartContainer, ChartContainerProps } from '../ChartContainer';
import { DEFAULT_X_AXIS_KEY } from '../constants';
import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants';
import { ChartsTooltip } from '../ChartsTooltip';
import { ChartsTooltipSlots, ChartsTooltipSlotProps } from '../ChartsTooltip/ChartTooltip.types';
import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight';
Expand Down Expand Up @@ -187,7 +187,7 @@ const SparkLineChart = React.forwardRef(function SparkLineChart(
]}
yAxis={[
{
id: DEFAULT_X_AXIS_KEY,
id: DEFAULT_Y_AXIS_KEY,
...yAxis,
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('useSkipAnimation', () => {
const errorMessage2 =
'It looks like you rendered your component outside of a ChartsContainer parent component.';
const errorMessage3 = 'The above error occurred in the <UseSkipAnimation> component:';
const expextedError =
const expectedError =
reactMajor < 19
? [errorMessage1, errorMessage2, errorMessage3]
: `${errorMessage1}\n${errorMessage2}`;
Expand All @@ -49,7 +49,7 @@ describe('useSkipAnimation', () => {
<UseSkipAnimation />
</ErrorBoundary>,
),
).toErrorDev(expextedError);
).toErrorDev(expectedError);

expect((errorRef.current as any).errors).to.have.length(1);
expect((errorRef.current as any).errors[0].toString()).to.include(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('useHighlighted', () => {
const errorMessage2 =
'It looks like you rendered your component outside of a ChartsContainer parent component.';
const errorMessage3 = 'The above error occurred in the <UseHighlighted> component:';
const expextedError =
const expectedError =
reactMajor < 19
? [errorMessage1, errorMessage2, errorMessage3]
: `${errorMessage1}\n${errorMessage2}`;
Expand All @@ -34,7 +34,7 @@ describe('useHighlighted', () => {
<UseHighlighted />
</ErrorBoundary>,
),
).toErrorDev(expextedError);
).toErrorDev(expectedError);

expect((errorRef.current as any).errors).to.have.length(1);
expect((errorRef.current as any).errors[0].toString()).to.include(
Expand Down
1 change: 1 addition & 0 deletions packages/x-charts/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export {
useScatterSeries as unstable_useScatterSeries,
} from './useSeries';
export * from './useLegend';
export { useChartGradientId, useChartGradientIdObjectBound } from './useChartGradientId';
41 changes: 41 additions & 0 deletions packages/x-charts/src/hooks/useChartGradientId.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, screen } from '@mui/internal-test-utils';
import { useChartGradientId, useChartGradientIdObjectBound } from './useChartGradientId';
import { ChartDataProvider } from '../context';

function UseGradientId() {
const id = useChartGradientId('test-id');
return <div>{id}</div>;
}

function UseGradientIdObjectBound() {
const id = useChartGradientIdObjectBound('test-id');
return <div>{id}</div>;
}

describe('useChartGradientId', () => {
const { render } = createRenderer();

it('should properly generate a correct id', () => {
render(
<ChartDataProvider series={[]} height={100} width={100}>
<UseGradientId />
</ChartDataProvider>,
);

expect(screen.getByText(/:\w+:-gradient-test-id/)).toBeVisible();
});

describe('useChartGradientIdObjectBound', () => {
it('should properly generate a correct id', () => {
render(
<ChartDataProvider series={[]} height={100} width={100}>
<UseGradientIdObjectBound />
</ChartDataProvider>,
);

expect(screen.getByText(/:\w+:-gradient-test-id-object-bound/)).toBeVisible();
});
});
});
51 changes: 51 additions & 0 deletions packages/x-charts/src/hooks/useChartGradientId.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client';
import * as React from 'react';
import { useChartId } from './useChartId';
import { AxisId } from '../models/axis';

/**
* Returns a function that generates a gradient id for the given axis id.
*/
export function useChartGradientIdBuilder() {
const chartId = useChartId();
return React.useCallback((axisId: AxisId) => `${chartId}-gradient-${axisId}`, [chartId]);
}

/**
* Returns a function that generates a gradient id for the given axis id.
*/
export function useChartGradientIdObjectBoundBuilder() {
const chartId = useChartId();
return React.useCallback(
(axisId: AxisId) => `${chartId}-gradient-${axisId}-object-bound`,
[chartId],
);
}

/**
* Returns a gradient id for the given axis id.
*
* Can be useful when reusing the same gradient on custom components.
*
* For a gradient that respects the coordinates of the object on which it is applied, use `useChartGradientIdObjectBound` instead.
*
* @param axisId the axis id
* @returns the gradient id
*/
export function useChartGradientId(axisId: AxisId) {
return useChartGradientIdBuilder()(axisId);
}

/**
* Returns a gradient id for the given axis id.
*
* Can be useful when reusing the same gradient on custom components.
*
* This gradient differs from `useChartGradientId` in that it respects the coordinates of the object on which it is applied.
*
* @param axisId the axis id
* @returns the gradient id
*/
export function useChartGradientIdObjectBound(axisId: AxisId) {
return useChartGradientIdObjectBoundBuilder()(axisId);
}
4 changes: 2 additions & 2 deletions packages/x-charts/src/hooks/useSeries.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('useSeries', () => {
const errorMessage2 =
'It looks like you rendered your component outside of a ChartDataProvider.';
const errorMessage3 = 'The above error occurred in the <UseSeries> component:';
const expextedError =
const expectedError =
reactMajor < 19
? [errorMessage1, errorMessage2, errorMessage3]
: [errorMessage1, errorMessage2].join('\n');
Expand All @@ -33,7 +33,7 @@ describe('useSeries', () => {
<UseSeries />
</ErrorBoundary>,
),
).toErrorDev(expextedError);
).toErrorDev(expectedError);

expect((errorRef.current as any).errors).to.have.length(1);
expect((errorRef.current as any).errors[0].toString()).to.include(errorMessage1);
Expand Down
4 changes: 2 additions & 2 deletions packages/x-charts/src/hooks/useSvgRef.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ describe('useSvgRef', () => {
'It looks like you rendered your component outside of a ChartDataProvider.',
'The above error occurred in the <UseSvgRef> component',
];
const expextedError = reactMajor < 19 ? errorMessages : errorMessages.slice(0, 2).join('\n');
const expectedError = reactMajor < 19 ? errorMessages : errorMessages.slice(0, 2).join('\n');

expect(() =>
render(
<ErrorBoundary ref={errorRef}>
<UseSvgRef />
</ErrorBoundary>,
),
).toErrorDev(expextedError);
).toErrorDev(expectedError);

expect((errorRef.current as any).errors).to.have.length(1);
expect((errorRef.current as any).errors[0].toString()).to.include(
Expand Down
Loading

0 comments on commit 2c9ddc9

Please sign in to comment.