From 6d2192a4a666629e306a6bae70dcc4f9d57d7a13 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Mon, 25 Nov 2024 14:52:53 +0100 Subject: [PATCH] Add different signature for each series type hook --- package.json | 2 +- packages/x-charts/src/BarChart/BarPlot.tsx | 2 +- .../ChartsVoronoiHandler.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 | 4 ++ .../x-charts/src/hooks/useBarSeries.test.ts | 59 +++++++++++++++ packages/x-charts/src/hooks/useBarSeries.ts | 50 +++++++++++++ .../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 +++++++++++++ .../src/hooks/useScatterSeries.test.ts | 71 +++++++++++++++++++ .../x-charts/src/hooks/useScatterSeries.ts | 50 +++++++++++++ packages/x-charts/src/hooks/useSeries.ts | 52 -------------- 19 files changed, 471 insertions(+), 61 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/package.json b/package.json index 0d18c62a5494d..1e502c6c3e1e9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "test:coverage:inspect": "cross-env NODE_ENV=test TZ=UTC BABEL_ENV=coverage mocha --inspect-brk", "test:karma": "cross-env NODE_ENV=test TZ=UTC karma start test/karma.conf.js", "test:karma:parallel": "cross-env NODE_ENV=test TZ=UTC PARALLEL=true karma start test/karma.conf.js", - "test:unit": "cross-env NODE_ENV=test TZ=UTC mocha -n expose_gc 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}'", + "test:unit": "cross-env NODE_ENV=test TZ=UTC mocha -n expose_gc 'packages/x-charts/src/**/useBarSeries.test.{js,ts,tsx}'", "test:e2e": "cross-env NODE_ENV=production pnpm test:e2e:build && concurrently --success first --kill-others \"pnpm test:e2e:run\" \"pnpm test:e2e:server\"", "test:e2e:build": "cross-env E2E_BUILD=true webpack --config test/e2e/webpack.config.js", "test:e2e:dev": "concurrently \"pnpm test:e2e:build --watch\" \"pnpm test:e2e:server\"", 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/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx index 29d8303317573..449622d568885 100644 --- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx +++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx @@ -11,7 +11,7 @@ import { ScatterItemIdentifier } from '../models'; import { SeriesId } from '../models/seriesType/common'; import { useDrawingArea, useSvgRef } from '../hooks'; import { useHighlighted } from '../context'; -import { useScatterSeries } from '../hooks/useSeries'; +import { useScatterSeries } from '../hooks/useScatterSeries'; export type ChartsVoronoiHandlerProps = { /** 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 a804880fa11ff..a413a690e8934 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -5,3 +5,7 @@ export * from './useAxis'; export * from './useColorScale'; export * from './useSvgRef'; export * from './useSeries'; +export * from './useScatterSeries'; +export * from './usePieSeries'; +export * from './useBarSeries'; +export * from './useLineSeries'; 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/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/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]); -}