From bedd53266417e4b0a94497b5321c89a16a4e4f1c Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 7 Feb 2025 18:39:34 +0100 Subject: [PATCH 1/5] useSeries docs --- packages/x-charts/src/BarChart/BarPlot.tsx | 2 +- packages/x-charts/src/LineChart/AreaPlot.tsx | 2 +- .../src/LineChart/LineHighlightPlot.tsx | 2 +- packages/x-charts/src/LineChart/LinePlot.tsx | 2 +- packages/x-charts/src/LineChart/MarkPlot.tsx | 2 +- packages/x-charts/src/PieChart/PiePlot.tsx | 2 +- .../x-charts/src/ScatterChart/ScatterPlot.tsx | 2 +- packages/x-charts/src/hooks/index.ts | 12 ++-- packages/x-charts/src/hooks/useAxis.ts | 19 +++-- .../x-charts/src/hooks/useBarSeries.test.ts | 59 +++++++++++++++ packages/x-charts/src/hooks/useBarSeries.ts | 50 +++++++++++++ packages/x-charts/src/hooks/useChartId.ts | 6 +- packages/x-charts/src/hooks/useColorScale.ts | 44 ++++++++---- packages/x-charts/src/hooks/useDrawingArea.ts | 7 ++ .../x-charts/src/hooks/useLineSeries.test.ts | 59 +++++++++++++++ packages/x-charts/src/hooks/useLineSeries.ts | 50 +++++++++++++ .../x-charts/src/hooks/usePieSeries.test.ts | 69 ++++++++++++++++++ packages/x-charts/src/hooks/usePieSeries.ts | 50 +++++++++++++ packages/x-charts/src/hooks/useScale.ts | 32 +++++---- .../src/hooks/useScatterSeries.test.ts | 71 +++++++++++++++++++ .../x-charts/src/hooks/useScatterSeries.ts | 50 +++++++++++++ packages/x-charts/src/hooks/useSeries.ts | 52 -------------- packages/x-charts/src/hooks/useSvgRef.ts | 4 ++ 23 files changed, 550 insertions(+), 98 deletions(-) create mode 100644 packages/x-charts/src/hooks/useBarSeries.test.ts create mode 100644 packages/x-charts/src/hooks/useBarSeries.ts create mode 100644 packages/x-charts/src/hooks/useLineSeries.test.ts create mode 100644 packages/x-charts/src/hooks/useLineSeries.ts create mode 100644 packages/x-charts/src/hooks/usePieSeries.test.ts create mode 100644 packages/x-charts/src/hooks/usePieSeries.ts create mode 100644 packages/x-charts/src/hooks/useScatterSeries.test.ts create mode 100644 packages/x-charts/src/hooks/useScatterSeries.ts diff --git a/packages/x-charts/src/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx index 33924df041dba..9938a617fdf1d 100644 --- a/packages/x-charts/src/BarChart/BarPlot.tsx +++ b/packages/x-charts/src/BarChart/BarPlot.tsx @@ -14,7 +14,7 @@ import { BarClipPath } from './BarClipPath'; import { BarLabelItemProps, BarLabelSlotProps, BarLabelSlots } from './BarLabel/BarLabelItem'; import { BarLabelPlot } from './BarLabel/BarLabelPlot'; import { checkScaleErrors } from './checkScaleErrors'; -import { useBarSeries } from '../hooks/useSeries'; +import { useBarSeries } from '../hooks/useBarSeries'; import { SeriesFormatterResult } from '../context/PluginProvider'; import { useSkipAnimation } from '../context/AnimationProvider'; diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index 2a4f2d6f32806..4ceaae52a777a 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -16,7 +16,7 @@ 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 { useLineSeries } from '../hooks/useLineSeries'; import { AxisId } from '../models/axis'; import { useSkipAnimation } from '../context/AnimationProvider'; diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index bd37e6e2ec4bf..a120408f9676f 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -9,7 +9,7 @@ import { LineHighlightElement, LineHighlightElementProps } from './LineHighlight import { getValueToPositionMapper } from '../hooks/useScale'; import { DEFAULT_X_AXIS_KEY } from '../constants'; import getColor from './getColor'; -import { useLineSeries } from '../hooks/useSeries'; +import { useLineSeries } from '../hooks/useLineSeries'; import { useDrawingArea } from '../hooks/useDrawingArea'; import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors'; diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx index b0986bee8d677..eded7613805c7 100644 --- a/packages/x-charts/src/LineChart/LinePlot.tsx +++ b/packages/x-charts/src/LineChart/LinePlot.tsx @@ -16,7 +16,7 @@ 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 { useLineSeries } from '../hooks/useLineSeries'; import { AxisId } from '../models/axis'; import { useSkipAnimation } from '../context/AnimationProvider'; diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx index 7731eba6392fb..bd601f9c613d8 100644 --- a/packages/x-charts/src/LineChart/MarkPlot.tsx +++ b/packages/x-charts/src/LineChart/MarkPlot.tsx @@ -7,7 +7,7 @@ import { useCartesianContext } from '../context/CartesianProvider'; import { useChartId } from '../hooks/useChartId'; import { useDrawingArea } from '../hooks/useDrawingArea'; import { getValueToPositionMapper } from '../hooks/useScale'; -import { useLineSeries } from '../hooks/useSeries'; +import { useLineSeries } from '../hooks/useLineSeries'; import { cleanId } from '../internals/cleanId'; import { LineItemIdentifier } from '../models/seriesType/line'; import { CircleMarkElement } from './CircleMarkElement'; diff --git a/packages/x-charts/src/PieChart/PiePlot.tsx b/packages/x-charts/src/PieChart/PiePlot.tsx index 50190f23472a0..d7e684ce50386 100644 --- a/packages/x-charts/src/PieChart/PiePlot.tsx +++ b/packages/x-charts/src/PieChart/PiePlot.tsx @@ -6,7 +6,7 @@ import { PieArcPlot, PieArcPlotProps, PieArcPlotSlotProps, PieArcPlotSlots } fro import { PieArcLabelPlotSlots, PieArcLabelPlotSlotProps, PieArcLabelPlot } from './PieArcLabelPlot'; import { getPercentageValue } from '../internals/getPercentageValue'; import { getPieCoordinates } from './getPieCoordinates'; -import { usePieSeries } from '../hooks/useSeries'; +import { usePieSeries } from '../hooks/usePieSeries'; import { useSkipAnimation } from '../context/AnimationProvider'; export interface PiePlotSlots extends PieArcPlotSlots, PieArcLabelPlotSlots {} diff --git a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx index 639121a8c79e8..193070613b74d 100644 --- a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx @@ -5,7 +5,7 @@ import { Scatter, ScatterProps } from './Scatter'; import { useCartesianContext } from '../context/CartesianProvider'; import getColor from './getColor'; import { ZAxisContext } from '../context/ZAxisContextProvider'; -import { useScatterSeries } from '../hooks/useSeries'; +import { useScatterSeries } from '../hooks/useScatterSeries'; export interface ScatterPlotSlots { scatter?: React.JSXElementConstructor; diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index 7f6e5dcdbdafe..a413a690e8934 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -4,10 +4,8 @@ export * from './useScale'; export * from './useAxis'; export * from './useColorScale'; export * from './useSvgRef'; -export { - useSeries as unstable_useSeries, - usePieSeries as unstable_usePieSeries, - useLineSeries as unstable_useLineSeries, - useBarSeries as unstable_useBarSeries, - useScatterSeries as unstable_useScatterSeries, -} from './useSeries'; +export * from './useSeries'; +export * from './useScatterSeries'; +export * from './usePieSeries'; +export * from './useBarSeries'; +export * from './useLineSeries'; diff --git a/packages/x-charts/src/hooks/useAxis.ts b/packages/x-charts/src/hooks/useAxis.ts index 7f4ac25f6cef4..61104d4ceb982 100644 --- a/packages/x-charts/src/hooks/useAxis.ts +++ b/packages/x-charts/src/hooks/useAxis.ts @@ -1,18 +1,29 @@ 'use client'; import { useCartesianContext } from '../context/CartesianProvider'; +import { AxisId } from '../models/axis'; -export function useXAxis(identifier?: number | string) { +/** + * Get the X axis. + * @param {AxisId | undefined} axisId - If provided returns the x axis with axisId, else returns the values for the default x axis. + * @returns The X axis. + */ +export function useXAxis(axisId?: AxisId) { const { xAxis, xAxisIds } = useCartesianContext(); - const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0]; + const id = axisId ?? xAxisIds[0]; return xAxis[id]; } -export function useYAxis(identifier?: number | string) { +/** + * Get the Y axis. + * @param {AxisId | undefined} axisId - If provided returns the y axis with axisId, else returns the values for the default y axis. + * @returns The Y axis. + */ +export function useYAxis(axisId?: AxisId) { const { yAxis, yAxisIds } = useCartesianContext(); - const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0]; + const id = axisId ?? yAxisIds[0]; return yAxis[id]; } diff --git a/packages/x-charts/src/hooks/useBarSeries.test.ts b/packages/x-charts/src/hooks/useBarSeries.test.ts new file mode 100644 index 0000000000000..68845b8e31c6c --- /dev/null +++ b/packages/x-charts/src/hooks/useBarSeries.test.ts @@ -0,0 +1,59 @@ +import { renderHook } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { stub, restore } from 'sinon'; +import { useBarSeries } from './useBarSeries'; +import * as series from './useSeries'; +import { SeriesId } from '../models/seriesType/common'; +import { FormattedSeries } from '../context/SeriesProvider'; + +describe('useBarSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'bar', + stackedData: [] as [number, number][], + } as const; + + const mockSeries: FormattedSeries = { + bar: { + series: { + '1': { ...defaultProps, id: '1', data: [1, 2, 3] }, + '2': { ...defaultProps, id: '2', data: [4, 5, 6] }, + }, + seriesOrder: ['1', '2'], + stackingGroups: [], + }, + }; + + beforeEach(() => { + stub(series, 'useSeries').returns(mockSeries); + }); + + afterEach(() => { + restore(); + }); + + it('should return all bar series when no seriesIds are provided', () => { + const { result } = renderHook(() => useBarSeries()); + expect(result.current).to.deep.equal(mockSeries.bar); + }); + + it('should return the specific bar series when a single seriesId is provided', () => { + const { result } = renderHook(() => useBarSeries('1' as SeriesId)); + expect(result.current).to.deep.equal(mockSeries!.bar!.series['1']); + }); + + it('should return the specific bar series when multiple seriesIds are provided', () => { + const { result } = renderHook(() => useBarSeries('1' as SeriesId, '2' as SeriesId)); + expect(result.current).to.deep.equal([ + mockSeries!.bar!.series['1'], + mockSeries!.bar!.series['2'], + ]); + }); + + it('should filter out undefined series when invalid seriesIds are provided', () => { + const { result } = renderHook(() => useBarSeries('1' as SeriesId, '3' as SeriesId)); + expect(result.current).to.deep.equal([mockSeries!.bar!.series['1']]); + }); +}); diff --git a/packages/x-charts/src/hooks/useBarSeries.ts b/packages/x-charts/src/hooks/useBarSeries.ts new file mode 100644 index 0000000000000..020856eb9edd9 --- /dev/null +++ b/packages/x-charts/src/hooks/useBarSeries.ts @@ -0,0 +1,50 @@ +'use client'; +import * as React from 'react'; +import { FormattedSeries } from '../context/SeriesProvider'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeries } from './useSeries'; + +/** + * Get access to the internal state of bar series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} barSeries + */ +export function useBarSeries(): FormattedSeries['bar']; +/** + * Get access to the internal state of bar series. + * + * @param {SeriesId} seriesId The id of the series to get. + * @returns {ChartSeriesDefaultized<'bar'> | undefined} barSeries + */ +export function useBarSeries(seriesId: SeriesId): ChartSeriesDefaultized<'bar'>; +/** + * Get access to the internal state of bar series. + * + * @param {SeriesId[]} seriesIds The ids of the series to get. Order is preserved. + * @returns {ChartSeriesDefaultized<'bar'>[] | undefined} barSeries + */ +export function useBarSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'bar'>[]; +export function useBarSeries(...seriesIds: SeriesId[]): any { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series.bar; + } + + if (seriesIds.length === 1) { + return series?.bar?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.bar?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series.bar, ...seriesIds], + ); +} diff --git a/packages/x-charts/src/hooks/useChartId.ts b/packages/x-charts/src/hooks/useChartId.ts index 644d0acfaab6a..2c6c16798222a 100644 --- a/packages/x-charts/src/hooks/useChartId.ts +++ b/packages/x-charts/src/hooks/useChartId.ts @@ -2,7 +2,11 @@ import * as React from 'react'; import { DrawingAreaContext } from '../context/DrawingAreaProvider'; -export function useChartId() { +/** + * Get the unique identifier of the chart. + * @returns {string} chartId + */ +export function useChartId(): string { const { chartId } = React.useContext(DrawingAreaContext); return React.useMemo(() => chartId, [chartId]); diff --git a/packages/x-charts/src/hooks/useColorScale.ts b/packages/x-charts/src/hooks/useColorScale.ts index f4284efa7454b..f9e947b17a018 100644 --- a/packages/x-charts/src/hooks/useColorScale.ts +++ b/packages/x-charts/src/hooks/useColorScale.ts @@ -1,35 +1,49 @@ 'use client'; import * as React from 'react'; -import { useCartesianContext } from '../context/CartesianProvider'; -import { AxisScaleComputedConfig, ScaleName } from '../models/axis'; +import { AxisId, AxisScaleComputedConfig, ScaleName } from '../models/axis'; import { ZAxisContext } from '../context/ZAxisContextProvider'; - +import { useXAxis, useYAxis } from './useAxis'; + +/** + * Get the X axis color scale. + * + * @param {AxisId | undefined} axisId - If provided returns color scale for axisId, else returns the values for the default axis. + * @returns {AxisScaleComputedConfig[S]['colorScale'] | undefined} The color scale for the specified X axis, or undefined if not found. + */ export function useXColorScale( - identifier?: number | string, + axisId?: AxisId, ): AxisScaleComputedConfig[S]['colorScale'] | undefined { - const { xAxis, xAxisIds } = useCartesianContext(); - - const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0]; + const axis = useXAxis(axisId); - return xAxis[id].colorScale; + return axis.colorScale; } +/** + * Get the Y axis color scale. + * + * @param {AxisId | undefined} axisId - If provided returns color scale for axisId, else returns the values for the default axis. + * @returns {AxisScaleComputedConfig[S]['colorScale'] | undefined} The color scale for the specified Y axis, or undefined if not found. + */ export function useYColorScale( - identifier?: number | string, + axisId?: AxisId, ): AxisScaleComputedConfig[S]['colorScale'] | undefined { - const { yAxis, yAxisIds } = useCartesianContext(); - - const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0]; + const axis = useYAxis(axisId); - return yAxis[id].colorScale; + return axis.colorScale; } +/** + * Get the Z axis color scale. + * + * @param {AxisId | undefined} axisId - If provided returns color scale for axisId, else returns the values for the default axis. + * @returns {AxisScaleComputedConfig[S]['colorScale'] | undefined} The color scale for the specified Z axis, or undefined if not found. + */ export function useZColorScale( - identifier?: number | string, + axisId?: AxisId, ): AxisScaleComputedConfig[S]['colorScale'] | undefined { const { zAxis, zAxisIds } = React.useContext(ZAxisContext); - const id = typeof identifier === 'string' ? identifier : zAxisIds[identifier ?? 0]; + const id = axisId ?? zAxisIds[0]; return zAxis[id]?.colorScale; } diff --git a/packages/x-charts/src/hooks/useDrawingArea.ts b/packages/x-charts/src/hooks/useDrawingArea.ts index f45457e907434..21e58cd1550ea 100644 --- a/packages/x-charts/src/hooks/useDrawingArea.ts +++ b/packages/x-charts/src/hooks/useDrawingArea.ts @@ -2,6 +2,13 @@ import * as React from 'react'; import { DrawingAreaContext, DrawingAreaState } from '../context/DrawingAreaProvider'; +/** + * Get the drawing area dimensions and coordinates. The drawing area is the area where the chart is rendered. + * + * It includes the left, top, width, height, bottom, right, and a function to check if a given point is inside the drawing area. + * + * @returns {DrawingAreaState} The drawing area dimensions. + */ export function useDrawingArea(): DrawingAreaState { const { left, top, width, height, bottom, right, isPointInside } = React.useContext(DrawingAreaContext); diff --git a/packages/x-charts/src/hooks/useLineSeries.test.ts b/packages/x-charts/src/hooks/useLineSeries.test.ts new file mode 100644 index 0000000000000..2b7f6085ef645 --- /dev/null +++ b/packages/x-charts/src/hooks/useLineSeries.test.ts @@ -0,0 +1,59 @@ +import { renderHook } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { stub, restore } from 'sinon'; +import { useLineSeries } from './useLineSeries'; +import * as series from './useSeries'; +import { SeriesId } from '../models/seriesType/common'; +import { FormattedSeries } from '../context/SeriesProvider'; + +describe('useLineSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'line', + stackedData: [] as [number, number][], + } as const; + + const mockSeries: FormattedSeries = { + line: { + series: { + '1': { ...defaultProps, id: '1', data: [1, 2, 3] }, + '2': { ...defaultProps, id: '2', data: [4, 5, 6] }, + }, + seriesOrder: ['1', '2'], + stackingGroups: [], + }, + }; + + beforeEach(() => { + stub(series, 'useSeries').returns(mockSeries); + }); + + afterEach(() => { + restore(); + }); + + it('should return all line series when no seriesIds are provided', () => { + const { result } = renderHook(() => useLineSeries()); + expect(result.current).to.deep.equal(mockSeries.line); + }); + + it('should return the specific line series when a single seriesId is provided', () => { + const { result } = renderHook(() => useLineSeries('1' as SeriesId)); + expect(result.current).to.deep.equal(mockSeries!.line!.series['1']); + }); + + it('should return the specific line series when multiple seriesIds are provided', () => { + const { result } = renderHook(() => useLineSeries('1' as SeriesId, '2' as SeriesId)); + expect(result.current).to.deep.equal([ + mockSeries!.line!.series['1'], + mockSeries!.line!.series['2'], + ]); + }); + + it('should filter out undefined series when invalid seriesIds are provided', () => { + const { result } = renderHook(() => useLineSeries('1' as SeriesId, '3' as SeriesId)); + expect(result.current).to.deep.equal([mockSeries!.line!.series['1']]); + }); +}); diff --git a/packages/x-charts/src/hooks/useLineSeries.ts b/packages/x-charts/src/hooks/useLineSeries.ts new file mode 100644 index 0000000000000..a3198ba92f8d5 --- /dev/null +++ b/packages/x-charts/src/hooks/useLineSeries.ts @@ -0,0 +1,50 @@ +'use client'; +import * as React from 'react'; +import { FormattedSeries } from '../context/SeriesProvider'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeries } from './useSeries'; + +/** + * Get access to the internal state of line series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} lineSeries + */ +export function useLineSeries(): FormattedSeries['line']; +/** + * Get access to the internal state of line series. + * + * @param {SeriesId} seriesId The id of the series to get. + * @returns {ChartSeriesDefaultized<'line'> | undefined} lineSeries + */ +export function useLineSeries(seriesId: SeriesId): ChartSeriesDefaultized<'line'>; +/** + * Get access to the internal state of line series. + * + * @param {SeriesId[]} seriesIds The ids of the series to get. Order is preserved. + * @returns {ChartSeriesDefaultized<'line'>[] | undefined} lineSeries + */ +export function useLineSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'line'>[]; +export function useLineSeries(...seriesIds: SeriesId[]): any { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series.line; + } + + if (seriesIds.length === 1) { + return series?.line?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.line?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series.line, ...seriesIds], + ); +} diff --git a/packages/x-charts/src/hooks/usePieSeries.test.ts b/packages/x-charts/src/hooks/usePieSeries.test.ts new file mode 100644 index 0000000000000..80dd3bc743bdd --- /dev/null +++ b/packages/x-charts/src/hooks/usePieSeries.test.ts @@ -0,0 +1,69 @@ +import { renderHook } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { stub, restore } from 'sinon'; +import { usePieSeries } from './usePieSeries'; +import * as series from './useSeries'; +import { SeriesId } from '../models/seriesType/common'; +import { FormattedSeries } from '../context/SeriesProvider'; + +describe('usePieSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'pie', + stackedData: [] as [number, number][], + } as const; + + const dataExample = { + id: 1, + value: 10, + startAngle: 0, + endAngle: 1, + color: 'red', + formattedValue: '10', + index: 0, + padAngle: 0, + } as const; + + const mockSeries: FormattedSeries = { + pie: { + series: { + '1': { ...defaultProps, id: '1', data: [dataExample] }, + '2': { ...defaultProps, id: '2', data: [dataExample] }, + }, + seriesOrder: ['1', '2'], + }, + }; + + beforeEach(() => { + stub(series, 'useSeries').returns(mockSeries); + }); + + afterEach(() => { + restore(); + }); + + it('should return all pie series when no seriesIds are provided', () => { + const { result } = renderHook(() => usePieSeries()); + expect(result.current).to.deep.equal(mockSeries.pie); + }); + + it('should return the specific pie series when a single seriesId is provided', () => { + const { result } = renderHook(() => usePieSeries('1' as SeriesId)); + expect(result.current).to.deep.equal(mockSeries!.pie!.series['1']); + }); + + it('should return the specific pie series when multiple seriesIds are provided', () => { + const { result } = renderHook(() => usePieSeries('1' as SeriesId, '2' as SeriesId)); + expect(result.current).to.deep.equal([ + mockSeries!.pie!.series['1'], + mockSeries!.pie!.series['2'], + ]); + }); + + it('should filter out undefined series when invalid seriesIds are provided', () => { + const { result } = renderHook(() => usePieSeries('1' as SeriesId, '3' as SeriesId)); + expect(result.current).to.deep.equal([mockSeries!.pie!.series['1']]); + }); +}); diff --git a/packages/x-charts/src/hooks/usePieSeries.ts b/packages/x-charts/src/hooks/usePieSeries.ts new file mode 100644 index 0000000000000..56592a621754b --- /dev/null +++ b/packages/x-charts/src/hooks/usePieSeries.ts @@ -0,0 +1,50 @@ +'use client'; +import * as React from 'react'; +import { FormattedSeries } from '../context/SeriesProvider'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeries } from './useSeries'; + +/** + * Get access to the internal state of pie series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} pieSeries + */ +export function usePieSeries(): FormattedSeries['pie']; +/** + * Get access to the internal state of pie series. + * + * @param {SeriesId} seriesId The id of the series to get. + * @returns {ChartSeriesDefaultized<'pie'> | undefined} pieSeries + */ +export function usePieSeries(seriesId: SeriesId): ChartSeriesDefaultized<'pie'>; +/** + * Get access to the internal state of pie series. + * + * @param {SeriesId[]} seriesIds The ids of the series to get. Order is preserved. + * @returns {ChartSeriesDefaultized<'pie'>[] | undefined} pieSeries + */ +export function usePieSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'pie'>[]; +export function usePieSeries(...seriesIds: SeriesId[]): any { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series.pie; + } + + if (seriesIds.length === 1) { + return series?.pie?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.pie?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series.pie, ...seriesIds], + ); +} diff --git a/packages/x-charts/src/hooks/useScale.ts b/packages/x-charts/src/hooks/useScale.ts index 6081559d0a685..58caa3c8d53b1 100644 --- a/packages/x-charts/src/hooks/useScale.ts +++ b/packages/x-charts/src/hooks/useScale.ts @@ -1,33 +1,41 @@ 'use client'; import { isBandScale } from '../internals/isBandScale'; -import { AxisScaleConfig, D3Scale, ScaleName } from '../models/axis'; +import { AxisId, AxisScaleConfig, D3Scale, ScaleName } from '../models/axis'; import { useXAxis, useYAxis } from './useAxis'; /** * For a given scale return a function that map value to their position. * Useful when dealing with specific scale such as band. - * @param scale The scale to use - * @returns (value: any) => number + * @param {D3Scale} scale The scale to use + * @returns {(value: any) => number} A function that map value to their position */ -export function getValueToPositionMapper(scale: D3Scale) { +export function getValueToPositionMapper(scale: D3Scale): (value: any) => number { if (isBandScale(scale)) { return (value: any) => (scale(value) ?? 0) + scale.bandwidth() / 2; } return (value: any) => scale(value) as number; } -export function useXScale( - identifier?: number | string, -): AxisScaleConfig[S]['scale'] { - const axis = useXAxis(identifier); +/** + * Get the X scale. + * + * @param {AxisId | undefined} axisId - If provided returns the scale for the x axis with axisId, else returns the values for the default x axis. + * @returns {AxisScaleConfig[S]['scale']} The scale for the specified X axis. + */ +export function useXScale(axisId?: AxisId): AxisScaleConfig[S]['scale'] { + const axis = useXAxis(axisId); return axis.scale; } -export function useYScale( - identifier?: number | string, -): AxisScaleConfig[S]['scale'] { - const axis = useYAxis(identifier); +/** + * Get the Y scale. + * + * @param {AxisId | undefined} axisId - If provided returns the scale for the y axis with axisId, else returns the values for the default y axis. + * @returns {AxisScaleConfig[S]['scale']} The scale for the specified Y axis. + */ +export function useYScale(axisId?: AxisId): AxisScaleConfig[S]['scale'] { + const axis = useYAxis(axisId); return axis.scale; } diff --git a/packages/x-charts/src/hooks/useScatterSeries.test.ts b/packages/x-charts/src/hooks/useScatterSeries.test.ts new file mode 100644 index 0000000000000..4113b4192b955 --- /dev/null +++ b/packages/x-charts/src/hooks/useScatterSeries.test.ts @@ -0,0 +1,71 @@ +import { renderHook } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { stub, restore } from 'sinon'; +import { useScatterSeries } from './useScatterSeries'; +import * as series from './useSeries'; +import { SeriesId } from '../models/seriesType/common'; +import { FormattedSeries } from '../context/SeriesProvider'; + +describe('useScatterSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'scatter', + } as const; + + const mockSeries: FormattedSeries = { + scatter: { + series: { + '1': { + ...defaultProps, + id: '1', + data: [ + { id: 1, x: 1, y: 1 }, + { id: 2, x: 2, y: 2 }, + ], + }, + '2': { + ...defaultProps, + id: '2', + data: [ + { id: 3, x: 3, y: 3 }, + { id: 4, x: 4, y: 4 }, + ], + }, + }, + seriesOrder: ['1', '2'], + }, + }; + + beforeEach(() => { + stub(series, 'useSeries').returns(mockSeries); + }); + + afterEach(() => { + restore(); + }); + + it('should return all scatter series when no seriesIds are provided', () => { + const { result } = renderHook(() => useScatterSeries()); + expect(result.current).to.deep.equal(mockSeries.scatter); + }); + + it('should return the specific scatter series when a single seriesId is provided', () => { + const { result } = renderHook(() => useScatterSeries('1' as SeriesId)); + expect(result.current).to.deep.equal(mockSeries!.scatter!.series['1']); + }); + + it('should return the specific scatter series when multiple seriesIds are provided', () => { + const { result } = renderHook(() => useScatterSeries('1' as SeriesId, '2' as SeriesId)); + expect(result.current).to.deep.equal([ + mockSeries!.scatter!.series['1'], + mockSeries!.scatter!.series['2'], + ]); + }); + + it('should filter out undefined series when invalid seriesIds are provided', () => { + const { result } = renderHook(() => useScatterSeries('1' as SeriesId, '3' as SeriesId)); + expect(result.current).to.deep.equal([mockSeries!.scatter!.series['1']]); + }); +}); diff --git a/packages/x-charts/src/hooks/useScatterSeries.ts b/packages/x-charts/src/hooks/useScatterSeries.ts new file mode 100644 index 0000000000000..538c5f390e356 --- /dev/null +++ b/packages/x-charts/src/hooks/useScatterSeries.ts @@ -0,0 +1,50 @@ +'use client'; +import * as React from 'react'; +import { FormattedSeries } from '../context/SeriesProvider'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeries } from './useSeries'; + +/** + * Get access to the internal state of scatter series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} scatterSeries + */ +export function useScatterSeries(): FormattedSeries['scatter']; +/** + * Get access to the internal state of scatter series. + * + * @param {SeriesId} seriesId The id of the series to get. + * @returns {ChartSeriesDefaultized<'scatter'> | undefined} scatterSeries + */ +export function useScatterSeries(seriesId: SeriesId): ChartSeriesDefaultized<'scatter'>; +/** + * Get access to the internal state of scatter series. + * + * @param {SeriesId[]} seriesIds The ids of the series to get. Order is preserved. + * @returns {ChartSeriesDefaultized<'scatter'>[] | undefined} scatterSeries + */ +export function useScatterSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'scatter'>[]; +export function useScatterSeries(...seriesIds: SeriesId[]): any { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series.scatter; + } + + if (seriesIds.length === 1) { + return series?.scatter?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.scatter?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series.pie, ...seriesIds], + ); +} diff --git a/packages/x-charts/src/hooks/useSeries.ts b/packages/x-charts/src/hooks/useSeries.ts index 4f68bfa6ef1e2..6a7b6f88cbbe6 100644 --- a/packages/x-charts/src/hooks/useSeries.ts +++ b/packages/x-charts/src/hooks/useSeries.ts @@ -22,55 +22,3 @@ export function useSeries(): FormattedSeries { return data; } - -/** - * Get access to the internal state of pie series. - * The returned object contains: - * - series: a mapping from ids to series attributes. - * - seriesOrder: the array of series ids. - * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} pieSeries - */ -export function usePieSeries(): FormattedSeries['pie'] { - const series = useSeries(); - - return React.useMemo(() => series.pie, [series.pie]); -} - -/** - * Get access to the internal state of line series. - * The returned object contains: - * - series: a mapping from ids to series attributes. - * - seriesOrder: the array of series ids. - * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} lineSeries - */ -export function useLineSeries(): FormattedSeries['line'] { - const series = useSeries(); - - return React.useMemo(() => series.line, [series.line]); -} - -/** - * Get access to the internal state of bar series. - * The returned object contains: - * - series: a mapping from ids to series attributes. - * - seriesOrder: the array of series ids. - * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} barSeries - */ -export function useBarSeries(): FormattedSeries['bar'] { - const series = useSeries(); - - return React.useMemo(() => series.bar, [series.bar]); -} - -/** - * Get access to the internal state of scatter series. - * The returned object contains: - * - series: a mapping from ids to series attributes. - * - seriesOrder: the array of series ids. - * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} scatterSeries - */ -export function useScatterSeries(): FormattedSeries['scatter'] { - const series = useSeries(); - - return React.useMemo(() => series.scatter, [series.scatter]); -} diff --git a/packages/x-charts/src/hooks/useSvgRef.ts b/packages/x-charts/src/hooks/useSvgRef.ts index ef1e3fe842ac0..4500d8b140053 100644 --- a/packages/x-charts/src/hooks/useSvgRef.ts +++ b/packages/x-charts/src/hooks/useSvgRef.ts @@ -2,6 +2,10 @@ import * as React from 'react'; import { SvgRefContext } from '../context/SvgRefProvider'; +/** + * Get the ref for the SVG element. + * @returns The SVG ref. + */ export function useSvgRef(): React.MutableRefObject { const { isInitialized, data } = React.useContext(SvgRefContext); From 7a70ef4c55be8ccb6be4ef3a49b5d5f8c524bf33 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 7 Feb 2025 19:16:59 +0100 Subject: [PATCH 2/5] add heatmap and tests --- .../x-charts-pro/src/Heatmap/HeatmapPlot.tsx | 2 +- .../src/Heatmap/HeatmapTooltip.tsx | 2 +- packages/x-charts-pro/src/hooks/index.ts | 2 +- .../src/hooks/useHeatmapSeries.test.ts | 72 +++++++++++++++++++ .../src/hooks/useHeatmapSeries.ts | 52 ++++++++++++++ packages/x-charts-pro/src/hooks/useSeries.ts | 16 ----- .../ChartsVoronoiHandler.tsx | 2 +- .../x-charts/src/hooks/useBarSeries.test.ts | 4 +- packages/x-charts/src/hooks/useBarSeries.ts | 4 +- .../x-charts/src/hooks/useLineSeries.test.ts | 4 +- packages/x-charts/src/hooks/useLineSeries.ts | 4 +- .../x-charts/src/hooks/usePieSeries.test.ts | 4 +- packages/x-charts/src/hooks/usePieSeries.ts | 4 +- .../src/hooks/useScatterSeries.test.ts | 4 +- .../x-charts/src/hooks/useScatterSeries.ts | 4 +- 15 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 packages/x-charts-pro/src/hooks/useHeatmapSeries.test.ts create mode 100644 packages/x-charts-pro/src/hooks/useHeatmapSeries.ts delete mode 100644 packages/x-charts-pro/src/hooks/useSeries.ts diff --git a/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx b/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx index 171ce56b3cffa..daea4f3d8aafc 100644 --- a/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx +++ b/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useXScale, useYScale, useZColorScale } from '@mui/x-charts/hooks'; -import { useHeatmapSeries } from '../hooks/useSeries'; +import { useHeatmapSeries } from '../hooks/useHeatmapSeries'; import { HeatmapItem, HeatmapItemProps } from './HeatmapItem'; export interface HeatmapPlotProps extends Pick {} diff --git a/packages/x-charts-pro/src/Heatmap/HeatmapTooltip.tsx b/packages/x-charts-pro/src/Heatmap/HeatmapTooltip.tsx index bce80b58c67ae..fb3fe1f0ae7d2 100644 --- a/packages/x-charts-pro/src/Heatmap/HeatmapTooltip.tsx +++ b/packages/x-charts-pro/src/Heatmap/HeatmapTooltip.tsx @@ -16,7 +16,7 @@ import { } from '@mui/x-charts/ChartsTooltip'; import { useXAxis, useYAxis } from '@mui/x-charts/hooks'; import { getLabel, ChartsLabelMark } from '@mui/x-charts/internals'; -import { useHeatmapSeries } from '../hooks/useSeries'; +import { useHeatmapSeries } from '../hooks/useHeatmapSeries'; export interface HeatmapTooltipProps extends Omit {} diff --git a/packages/x-charts-pro/src/hooks/index.ts b/packages/x-charts-pro/src/hooks/index.ts index f9be207805602..60ff5de53db1b 100644 --- a/packages/x-charts-pro/src/hooks/index.ts +++ b/packages/x-charts-pro/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useHeatmapSeries } from './useSeries'; +export { useHeatmapSeries } from './useHeatmapSeries'; export * from './zoom'; diff --git a/packages/x-charts-pro/src/hooks/useHeatmapSeries.test.ts b/packages/x-charts-pro/src/hooks/useHeatmapSeries.test.ts new file mode 100644 index 0000000000000..39b9f92bbf3c1 --- /dev/null +++ b/packages/x-charts-pro/src/hooks/useHeatmapSeries.test.ts @@ -0,0 +1,72 @@ +import { renderHook } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { stub, restore } from 'sinon'; +import type { SeriesId, ProcessedSeries } from '@mui/x-charts/internals'; +import * as series from '@mui/x-charts/hooks/useSeries'; +import { useHeatmapSeries } from './useHeatmapSeries'; + +describe('useHeatmapSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'heatmap', + } as const; + + const mockSeries: ProcessedSeries = { + heatmap: { + series: { + '1': { + ...defaultProps, + id: '1', + data: [ + [0, 0, 10], + [0, 1, 20], + [0, 2, 40], + ], + }, + '2': { + ...defaultProps, + id: '2', + data: [ + [3, 2, 20], + [3, 3, 70], + [3, 4, 90], + ], + }, + }, + seriesOrder: ['1', '2'], + }, + }; + + beforeEach(() => { + stub(series, 'useSeries').returns(mockSeries); + }); + + afterEach(() => { + restore(); + }); + + it('should return all heatmap series when no seriesIds are provided', () => { + const { result } = renderHook(() => useHeatmapSeries()); + expect(result.current).to.deep.equal(mockSeries.heatmap); + }); + + it('should return the specific heatmap series when a single seriesId is provided', () => { + const { result } = renderHook(() => useHeatmapSeries('1' as SeriesId)); + expect(result.current).to.deep.equal(mockSeries!.heatmap!.series['1']); + }); + + it('should return the specific heatmap series when multiple seriesIds are provided', () => { + const { result } = renderHook(() => useHeatmapSeries('1' as SeriesId, '2' as SeriesId)); + expect(result.current).to.deep.equal([ + mockSeries!.heatmap!.series['1'], + mockSeries!.heatmap!.series['2'], + ]); + }); + + it('should filter out undefined series when invalid seriesIds are provided', () => { + const { result } = renderHook(() => useHeatmapSeries('1' as SeriesId, '3' as SeriesId)); + expect(result.current).to.deep.equal([mockSeries!.heatmap!.series['1']]); + }); +}); diff --git a/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts b/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts new file mode 100644 index 0000000000000..90b534ea39958 --- /dev/null +++ b/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts @@ -0,0 +1,52 @@ +'use client'; +import * as React from 'react'; +import { + useSeries, + ProcessedSeries, + SeriesId, + ChartSeriesDefaultized, +} from '@mui/x-charts/internals'; + +/** + * Get access to the internal state of heatmap series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined heatmapSeries + */ +export function useHeatmapSeries(): ProcessedSeries['heatmap']; +/** + * Get access to the internal state of heatmap series. + * + * @param {SeriesId} seriesId The id of the series to get. + * @returns {ChartSeriesDefaultized<'heatmap'> | undefined} heatmapSeries + */ +export function useHeatmapSeries(seriesId: SeriesId): ChartSeriesDefaultized<'heatmap'>; +/** + * Get access to the internal state of heatmap series. + * + * @param {SeriesId[]} seriesIds The ids of the series to get. Order is preserved. + * @returns {ChartSeriesDefaultized<'heatmap'>[] | undefined} heatmapSeries + */ +export function useHeatmapSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'heatmap'>[]; +export function useHeatmapSeries(...seriesIds: SeriesId[]): any { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series.heatmap; + } + + if (seriesIds.length === 1) { + return series?.heatmap?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.heatmap?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series.heatmap, ...seriesIds], + ); +} diff --git a/packages/x-charts-pro/src/hooks/useSeries.ts b/packages/x-charts-pro/src/hooks/useSeries.ts deleted file mode 100644 index 6a150b390d983..0000000000000 --- a/packages/x-charts-pro/src/hooks/useSeries.ts +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; -import * as React from 'react'; -import { useSeries, ProcessedSeries } from '@mui/x-charts/internals'; - -/** - * Get access to the internal state of heatmap series. - * The returned object contains: - * - series: a mapping from ids to series attributes. - * - seriesOrder: the array of series ids. - * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined heatmapSeries - */ -export function useHeatmapSeries(): ProcessedSeries['heatmap'] { - const series = useSeries(); - - return React.useMemo(() => series.heatmap, [series.heatmap]); -} diff --git a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx index 1620c8b41b177..2a3740846d018 100644 --- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx +++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx @@ -8,7 +8,7 @@ import { useStore } from '../internals/store/useStore'; import { getSVGPoint } from '../internals/getSVGPoint'; import { ScatterItemIdentifier } from '../models'; import { SeriesId } from '../models/seriesType/common'; -import { useScatterSeries } from '../hooks/useSeries'; +import { useScatterSeries } from '../hooks/useScatterSeries'; import { useChartContext } from '../context/ChartProvider/useChartContext'; import { useDrawingArea } from '../hooks/useDrawingArea'; import { useSvgRef } from '../hooks/useSvgRef'; diff --git a/packages/x-charts/src/hooks/useBarSeries.test.ts b/packages/x-charts/src/hooks/useBarSeries.test.ts index 68845b8e31c6c..5cd1f01c96e9c 100644 --- a/packages/x-charts/src/hooks/useBarSeries.test.ts +++ b/packages/x-charts/src/hooks/useBarSeries.test.ts @@ -4,7 +4,7 @@ import { stub, restore } from 'sinon'; import { useBarSeries } from './useBarSeries'; import * as series from './useSeries'; import { SeriesId } from '../models/seriesType/common'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; describe('useBarSeries', () => { const defaultProps = { @@ -15,7 +15,7 @@ describe('useBarSeries', () => { stackedData: [] as [number, number][], } as const; - const mockSeries: FormattedSeries = { + const mockSeries: ProcessedSeries = { bar: { series: { '1': { ...defaultProps, id: '1', data: [1, 2, 3] }, diff --git a/packages/x-charts/src/hooks/useBarSeries.ts b/packages/x-charts/src/hooks/useBarSeries.ts index 020856eb9edd9..39c8334870865 100644 --- a/packages/x-charts/src/hooks/useBarSeries.ts +++ b/packages/x-charts/src/hooks/useBarSeries.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; import { useSeries } from './useSeries'; @@ -12,7 +12,7 @@ import { useSeries } from './useSeries'; * - seriesOrder: the array of series ids. * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} barSeries */ -export function useBarSeries(): FormattedSeries['bar']; +export function useBarSeries(): ProcessedSeries['bar']; /** * Get access to the internal state of bar series. * diff --git a/packages/x-charts/src/hooks/useLineSeries.test.ts b/packages/x-charts/src/hooks/useLineSeries.test.ts index 2b7f6085ef645..73c3fc20e2c03 100644 --- a/packages/x-charts/src/hooks/useLineSeries.test.ts +++ b/packages/x-charts/src/hooks/useLineSeries.test.ts @@ -4,7 +4,7 @@ import { stub, restore } from 'sinon'; import { useLineSeries } from './useLineSeries'; import * as series from './useSeries'; import { SeriesId } from '../models/seriesType/common'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; describe('useLineSeries', () => { const defaultProps = { @@ -15,7 +15,7 @@ describe('useLineSeries', () => { stackedData: [] as [number, number][], } as const; - const mockSeries: FormattedSeries = { + const mockSeries: ProcessedSeries = { line: { series: { '1': { ...defaultProps, id: '1', data: [1, 2, 3] }, diff --git a/packages/x-charts/src/hooks/useLineSeries.ts b/packages/x-charts/src/hooks/useLineSeries.ts index a3198ba92f8d5..e156bace87bf4 100644 --- a/packages/x-charts/src/hooks/useLineSeries.ts +++ b/packages/x-charts/src/hooks/useLineSeries.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; import { useSeries } from './useSeries'; @@ -12,7 +12,7 @@ import { useSeries } from './useSeries'; * - seriesOrder: the array of series ids. * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} lineSeries */ -export function useLineSeries(): FormattedSeries['line']; +export function useLineSeries(): ProcessedSeries['line']; /** * Get access to the internal state of line series. * diff --git a/packages/x-charts/src/hooks/usePieSeries.test.ts b/packages/x-charts/src/hooks/usePieSeries.test.ts index 80dd3bc743bdd..523d54337d5ef 100644 --- a/packages/x-charts/src/hooks/usePieSeries.test.ts +++ b/packages/x-charts/src/hooks/usePieSeries.test.ts @@ -4,7 +4,7 @@ import { stub, restore } from 'sinon'; import { usePieSeries } from './usePieSeries'; import * as series from './useSeries'; import { SeriesId } from '../models/seriesType/common'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; describe('usePieSeries', () => { const defaultProps = { @@ -26,7 +26,7 @@ describe('usePieSeries', () => { padAngle: 0, } as const; - const mockSeries: FormattedSeries = { + const mockSeries: ProcessedSeries = { pie: { series: { '1': { ...defaultProps, id: '1', data: [dataExample] }, diff --git a/packages/x-charts/src/hooks/usePieSeries.ts b/packages/x-charts/src/hooks/usePieSeries.ts index 56592a621754b..265ff7c949950 100644 --- a/packages/x-charts/src/hooks/usePieSeries.ts +++ b/packages/x-charts/src/hooks/usePieSeries.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; import { useSeries } from './useSeries'; @@ -12,7 +12,7 @@ import { useSeries } from './useSeries'; * - seriesOrder: the array of series ids. * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} pieSeries */ -export function usePieSeries(): FormattedSeries['pie']; +export function usePieSeries(): ProcessedSeries['pie']; /** * Get access to the internal state of pie series. * diff --git a/packages/x-charts/src/hooks/useScatterSeries.test.ts b/packages/x-charts/src/hooks/useScatterSeries.test.ts index 4113b4192b955..031cb84ba3880 100644 --- a/packages/x-charts/src/hooks/useScatterSeries.test.ts +++ b/packages/x-charts/src/hooks/useScatterSeries.test.ts @@ -4,7 +4,7 @@ import { stub, restore } from 'sinon'; import { useScatterSeries } from './useScatterSeries'; import * as series from './useSeries'; import { SeriesId } from '../models/seriesType/common'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; describe('useScatterSeries', () => { const defaultProps = { @@ -14,7 +14,7 @@ describe('useScatterSeries', () => { type: 'scatter', } as const; - const mockSeries: FormattedSeries = { + const mockSeries: ProcessedSeries = { scatter: { series: { '1': { diff --git a/packages/x-charts/src/hooks/useScatterSeries.ts b/packages/x-charts/src/hooks/useScatterSeries.ts index 538c5f390e356..5915dd40d175a 100644 --- a/packages/x-charts/src/hooks/useScatterSeries.ts +++ b/packages/x-charts/src/hooks/useScatterSeries.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { FormattedSeries } from '../context/SeriesProvider'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; import { useSeries } from './useSeries'; @@ -12,7 +12,7 @@ import { useSeries } from './useSeries'; * - seriesOrder: the array of series ids. * @returns {{ series: Record; seriesOrder: SeriesId[]; } | undefined} scatterSeries */ -export function useScatterSeries(): FormattedSeries['scatter']; +export function useScatterSeries(): ProcessedSeries['scatter']; /** * Get access to the internal state of scatter series. * From 247c6f35aee02eca6cf215028db9d45b265be90f Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 7 Feb 2025 19:27:21 +0100 Subject: [PATCH 3/5] fix multiple exports --- packages/x-charts/src/hooks/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index 12d2ccb3fadae..2b60e7ce7b78e 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -12,6 +12,5 @@ export * from './useBarSeries'; export * from './useLineSeries'; export * from './useItemHighlighted'; export * from './useItemHighlightedGetter'; -export { useSeries } from './useSeries'; export * from './useLegend'; export { useChartGradientId, useChartGradientIdObjectBound } from './useChartGradientId'; From 10219b3ebd46cab945cd9c40cf1abb4d1313d0df Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 7 Feb 2025 19:41:42 +0100 Subject: [PATCH 4/5] deduplicate behaviour --- .../src/hooks/useHeatmapSeries.ts | 23 ++------------- packages/x-charts/src/hooks/useBarSeries.ts | 23 ++------------- packages/x-charts/src/hooks/useLineSeries.ts | 23 ++------------- packages/x-charts/src/hooks/usePieSeries.ts | 23 ++------------- .../x-charts/src/hooks/useScatterSeries.ts | 23 ++------------- packages/x-charts/src/internals/index.ts | 1 + .../x-charts/src/internals/useSeriesOfType.ts | 29 +++++++++++++++++++ 7 files changed, 40 insertions(+), 105 deletions(-) create mode 100644 packages/x-charts/src/internals/useSeriesOfType.ts diff --git a/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts b/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts index 90b534ea39958..27c74f8bd998c 100644 --- a/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts +++ b/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts @@ -1,7 +1,6 @@ 'use client'; -import * as React from 'react'; import { - useSeries, + useSeriesOfType, ProcessedSeries, SeriesId, ChartSeriesDefaultized, @@ -30,23 +29,5 @@ export function useHeatmapSeries(seriesId: SeriesId): ChartSeriesDefaultized<'he */ export function useHeatmapSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'heatmap'>[]; export function useHeatmapSeries(...seriesIds: SeriesId[]): any { - const series = useSeries(); - - return React.useMemo( - () => { - if (seriesIds.length === 0) { - return series.heatmap; - } - - if (seriesIds.length === 1) { - return series?.heatmap?.series[seriesIds[0]]; - } - - return seriesIds.map((id) => series?.heatmap?.series[id]).filter(Boolean); - }, - // DANGER: Ensure that the dependencies array is correct. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - [series.heatmap, ...seriesIds], - ); + return useSeriesOfType('heatmap', ...seriesIds); } diff --git a/packages/x-charts/src/hooks/useBarSeries.ts b/packages/x-charts/src/hooks/useBarSeries.ts index 39c8334870865..f6b72ad0587ff 100644 --- a/packages/x-charts/src/hooks/useBarSeries.ts +++ b/packages/x-charts/src/hooks/useBarSeries.ts @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; -import { useSeries } from './useSeries'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; /** * Get access to the internal state of bar series. @@ -28,23 +27,5 @@ export function useBarSeries(seriesId: SeriesId): ChartSeriesDefaultized<'bar'>; */ export function useBarSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'bar'>[]; export function useBarSeries(...seriesIds: SeriesId[]): any { - const series = useSeries(); - - return React.useMemo( - () => { - if (seriesIds.length === 0) { - return series.bar; - } - - if (seriesIds.length === 1) { - return series?.bar?.series[seriesIds[0]]; - } - - return seriesIds.map((id) => series?.bar?.series[id]).filter(Boolean); - }, - // DANGER: Ensure that the dependencies array is correct. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - [series.bar, ...seriesIds], - ); + return useSeriesOfType('bar', ...seriesIds); } diff --git a/packages/x-charts/src/hooks/useLineSeries.ts b/packages/x-charts/src/hooks/useLineSeries.ts index e156bace87bf4..1f9f60d573285 100644 --- a/packages/x-charts/src/hooks/useLineSeries.ts +++ b/packages/x-charts/src/hooks/useLineSeries.ts @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; -import { useSeries } from './useSeries'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; /** * Get access to the internal state of line series. @@ -28,23 +27,5 @@ export function useLineSeries(seriesId: SeriesId): ChartSeriesDefaultized<'line' */ export function useLineSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'line'>[]; export function useLineSeries(...seriesIds: SeriesId[]): any { - const series = useSeries(); - - return React.useMemo( - () => { - if (seriesIds.length === 0) { - return series.line; - } - - if (seriesIds.length === 1) { - return series?.line?.series[seriesIds[0]]; - } - - return seriesIds.map((id) => series?.line?.series[id]).filter(Boolean); - }, - // DANGER: Ensure that the dependencies array is correct. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - [series.line, ...seriesIds], - ); + return useSeriesOfType('line', ...seriesIds); } diff --git a/packages/x-charts/src/hooks/usePieSeries.ts b/packages/x-charts/src/hooks/usePieSeries.ts index 265ff7c949950..5547354d354b8 100644 --- a/packages/x-charts/src/hooks/usePieSeries.ts +++ b/packages/x-charts/src/hooks/usePieSeries.ts @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; -import { useSeries } from './useSeries'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; /** * Get access to the internal state of pie series. @@ -28,23 +27,5 @@ export function usePieSeries(seriesId: SeriesId): ChartSeriesDefaultized<'pie'>; */ export function usePieSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'pie'>[]; export function usePieSeries(...seriesIds: SeriesId[]): any { - const series = useSeries(); - - return React.useMemo( - () => { - if (seriesIds.length === 0) { - return series.pie; - } - - if (seriesIds.length === 1) { - return series?.pie?.series[seriesIds[0]]; - } - - return seriesIds.map((id) => series?.pie?.series[id]).filter(Boolean); - }, - // DANGER: Ensure that the dependencies array is correct. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - [series.pie, ...seriesIds], - ); + return useSeriesOfType('pie', ...seriesIds); } diff --git a/packages/x-charts/src/hooks/useScatterSeries.ts b/packages/x-charts/src/hooks/useScatterSeries.ts index 5915dd40d175a..2ab00186fd203 100644 --- a/packages/x-charts/src/hooks/useScatterSeries.ts +++ b/packages/x-charts/src/hooks/useScatterSeries.ts @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; import { SeriesId } from '../models/seriesType/common'; import { ChartSeriesDefaultized } from '../models/seriesType/config'; -import { useSeries } from './useSeries'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; /** * Get access to the internal state of scatter series. @@ -28,23 +27,5 @@ export function useScatterSeries(seriesId: SeriesId): ChartSeriesDefaultized<'sc */ export function useScatterSeries(...seriesIds: SeriesId[]): ChartSeriesDefaultized<'scatter'>[]; export function useScatterSeries(...seriesIds: SeriesId[]): any { - const series = useSeries(); - - return React.useMemo( - () => { - if (seriesIds.length === 0) { - return series.scatter; - } - - if (seriesIds.length === 1) { - return series?.scatter?.series[seriesIds[0]]; - } - - return seriesIds.map((id) => series?.scatter?.series[id]).filter(Boolean); - }, - // DANGER: Ensure that the dependencies array is correct. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - [series.pie, ...seriesIds], - ); + return useSeriesOfType('scatter', ...seriesIds); } diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index 1b03a08bce6ea..28894728cf4d8 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -12,6 +12,7 @@ export { useLineChartProps } from '../LineChart/useLineChartProps'; export { useBarChartProps } from '../BarChart/useBarChartProps'; export * from '../ChartContainer/useChartContainerProps'; export * from '../ChartDataProvider/useChartDataProviderProps'; +export * from './useSeriesOfType'; // plugins export * from './plugins/corePlugins/useChartId'; diff --git a/packages/x-charts/src/internals/useSeriesOfType.ts b/packages/x-charts/src/internals/useSeriesOfType.ts new file mode 100644 index 0000000000000..382bd0daae952 --- /dev/null +++ b/packages/x-charts/src/internals/useSeriesOfType.ts @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { ChartsSeriesConfig } from '../models/seriesType/config'; +import { SeriesId } from '../models/seriesType/common'; +import { useSeries } from '../hooks'; + +export function useSeriesOfType( + seriesType: T, + ...seriesIds: SeriesId[] +) { + const series = useSeries(); + + return React.useMemo( + () => { + if (seriesIds.length === 0) { + return series[seriesType]; + } + + if (seriesIds.length === 1) { + return series?.[seriesType]?.series[seriesIds[0]]; + } + + return seriesIds.map((id) => series?.[seriesType]?.series[id]).filter(Boolean); + }, + // DANGER: Ensure that the dependencies array is correct. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + [series[seriesType], ...seriesIds], + ); +} From 7a9241fa83972b6084e36e4f16d5bb5086f8eabe Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 7 Feb 2025 19:43:14 +0100 Subject: [PATCH 5/5] fix cycle --- packages/x-charts/src/internals/useSeriesOfType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-charts/src/internals/useSeriesOfType.ts b/packages/x-charts/src/internals/useSeriesOfType.ts index 382bd0daae952..13d76a8f474ec 100644 --- a/packages/x-charts/src/internals/useSeriesOfType.ts +++ b/packages/x-charts/src/internals/useSeriesOfType.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { ChartsSeriesConfig } from '../models/seriesType/config'; import { SeriesId } from '../models/seriesType/common'; -import { useSeries } from '../hooks'; +import { useSeries } from '../hooks/useSeries'; export function useSeriesOfType( seriesType: T,