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..27c74f8bd998c --- /dev/null +++ b/packages/x-charts-pro/src/hooks/useHeatmapSeries.ts @@ -0,0 +1,33 @@ +'use client'; +import { + useSeriesOfType, + 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 { + return useSeriesOfType('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/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx index 71baa7d972c81..e6f74b3a04839 100644 --- a/packages/x-charts/src/BarChart/BarPlot.tsx +++ b/packages/x-charts/src/BarChart/BarPlot.tsx @@ -13,7 +13,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 { useSkipAnimation } from '../context/AnimationProvider'; import { SeriesProcessorResult } from '../internals/plugins/models/seriesConfig/seriesProcessor.types'; 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/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index ad7eea30bf346..161aaedd7e58f 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -15,7 +15,7 @@ import { getCurveFactory } from '../internals/getCurve'; import { isBandScale } from '../internals/isBandScale'; import { DEFAULT_X_AXIS_KEY } from '../constants'; import { LineItemIdentifier } from '../models/seriesType/line'; -import { useLineSeries } from '../hooks/useSeries'; +import { useLineSeries } from '../hooks/useLineSeries'; import { useSkipAnimation } from '../context/AnimationProvider'; import { useChartGradientIdBuilder } from '../hooks/useChartGradientId'; import { useXAxes, useYAxes } from '../hooks/useAxis'; diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index c6fe1768dfb6f..0b7b736bc5b76 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -8,7 +8,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 { useChartContext } from '../context/ChartProvider'; import { selectorChartsInteractionXAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; import { useXAxes, useYAxes } from '../hooks/useAxis'; diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx index e9c588a6e03ac..d25b35a63cbe1 100644 --- a/packages/x-charts/src/LineChart/LinePlot.tsx +++ b/packages/x-charts/src/LineChart/LinePlot.tsx @@ -15,7 +15,7 @@ import { getCurveFactory } from '../internals/getCurve'; import { isBandScale } from '../internals/isBandScale'; import { DEFAULT_X_AXIS_KEY } from '../constants'; import { LineItemIdentifier } from '../models/seriesType/line'; -import { useLineSeries } from '../hooks/useSeries'; +import { useLineSeries } from '../hooks/useLineSeries'; import { useSkipAnimation } from '../context/AnimationProvider'; import { useChartGradientIdBuilder } from '../hooks/useChartGradientId'; import { useXAxes, useYAxes } from '../hooks'; diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx index 813b4c8b4c444..a7462719e4bbd 100644 --- a/packages/x-charts/src/LineChart/MarkPlot.tsx +++ b/packages/x-charts/src/LineChart/MarkPlot.tsx @@ -5,7 +5,7 @@ import { DEFAULT_X_AXIS_KEY } from '../constants'; import { useSkipAnimation } from '../context/AnimationProvider'; import { useChartId } from '../hooks/useChartId'; 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 9cce72cacefe3..6ae797aa7f981 100644 --- a/packages/x-charts/src/PieChart/PiePlot.tsx +++ b/packages/x-charts/src/PieChart/PiePlot.tsx @@ -5,7 +5,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'; import { useDrawingArea } from '../hooks'; diff --git a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx index d50a68fe829c4..5ae381c3c9e1a 100644 --- a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { Scatter, ScatterProps } from './Scatter'; import getColor from './getColor'; -import { useScatterSeries } from '../hooks/useSeries'; +import { useScatterSeries } from '../hooks/useScatterSeries'; import { useXAxes, useYAxes } from '../hooks'; import { useZAxes } from '../hooks/useZAxis'; diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index 8aec879341109..2b60e7ce7b78e 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -5,14 +5,12 @@ export * from './useAxis'; export * from './useZAxis'; export * from './useColorScale'; export * from './useSvgRef'; +export * from './useSeries'; +export * from './useScatterSeries'; +export * from './usePieSeries'; +export * from './useBarSeries'; +export * from './useLineSeries'; export * from './useItemHighlighted'; export * from './useItemHighlightedGetter'; -export { - useSeries, - usePieSeries, - useLineSeries, - useBarSeries, - useScatterSeries, -} from './useSeries'; export * from './useLegend'; export { useChartGradientId, useChartGradientIdObjectBound } from './useChartGradientId'; diff --git a/packages/x-charts/src/hooks/useAxis.ts b/packages/x-charts/src/hooks/useAxis.ts index a9536e0f69b59..bd2fabd7471af 100644 --- a/packages/x-charts/src/hooks/useAxis.ts +++ b/packages/x-charts/src/hooks/useAxis.ts @@ -6,7 +6,18 @@ import { } from '../internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.selectors'; import { useSelector } from '../internals/store/useSelector'; import { useStore } from '../internals/store/useStore'; +import { AxisId } from '../models/axis'; +/** + * Get all the X axes. + * + * - `xAxis` is an object with the shape `{ [axisId]: axis }`. + * - `xAxisIds` is an array of axis IDs. + * + * If access to a specific X axis is needed, use the `useXAxis` hook instead. + * + * @returns `{ xAxis, xAxisIds }` - The X axes and their IDs. + */ export function useXAxes() { const store = useStore<[UseChartCartesianAxisSignature]>(); const { axis: xAxis, axisIds: xAxisIds } = useSelector(store, selectorChartXAxis); @@ -14,6 +25,16 @@ export function useXAxes() { return { xAxis, xAxisIds }; } +/** + * Get all the Y axes. + * + * - `yAxis` is an object with the shape `{ [axisId]: axis }`. + * - `yAxisIds` is an array of axis IDs. + * + * If access to a specific Y axis is needed, use the `useYAxis` hook instead. + * + * @returns `{ yAxis, yAxisIds }` - The Y axes and their IDs. + */ export function useYAxes() { const store = useStore<[UseChartCartesianAxisSignature]>(); const { axis: yAxis, axisIds: yAxisIds } = useSelector(store, selectorChartYAxis); @@ -21,20 +42,30 @@ export function useYAxes() { return { yAxis, yAxisIds }; } -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 store = useStore<[UseChartCartesianAxisSignature]>(); const { axis: xAxis, axisIds: xAxisIds } = useSelector(store, selectorChartXAxis); - 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 store = useStore<[UseChartCartesianAxisSignature]>(); const { axis: yAxis, axisIds: yAxisIds } = useSelector(store, selectorChartYAxis); - 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..5cd1f01c96e9c --- /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 { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; + +describe('useBarSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'bar', + stackedData: [] as [number, number][], + } as const; + + const mockSeries: ProcessedSeries = { + 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..f6b72ad0587ff --- /dev/null +++ b/packages/x-charts/src/hooks/useBarSeries.ts @@ -0,0 +1,31 @@ +'use client'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; + +/** + * 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(): ProcessedSeries['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 { + return useSeriesOfType('bar', ...seriesIds); +} diff --git a/packages/x-charts/src/hooks/useChartId.ts b/packages/x-charts/src/hooks/useChartId.ts index f51899a80ef61..383f5d1ab17df 100644 --- a/packages/x-charts/src/hooks/useChartId.ts +++ b/packages/x-charts/src/hooks/useChartId.ts @@ -3,7 +3,11 @@ import { useStore } from '../internals/store/useStore'; import { useSelector } from '../internals/store/useSelector'; import { selectorChartId } from '../internals/plugins/corePlugins/useChartId/useChartId.selectors'; -export function useChartId() { +/** + * Get the unique identifier of the chart. + * @returns chartId if it exists. + */ +export function useChartId(): string | undefined { const store = useStore(); return useSelector(store, selectorChartId); } diff --git a/packages/x-charts/src/hooks/useColorScale.ts b/packages/x-charts/src/hooks/useColorScale.ts index 76406894f13ce..7546f63eb0d8e 100644 --- a/packages/x-charts/src/hooks/useColorScale.ts +++ b/packages/x-charts/src/hooks/useColorScale.ts @@ -1,34 +1,46 @@ 'use client'; -import { AxisScaleComputedConfig, ScaleName } from '../models/axis'; -import { useXAxes, useYAxes } from './useAxis'; -import { useZAxes } from './useZAxis'; - +import { AxisId, AxisScaleComputedConfig, ScaleName } from '../models/axis'; +import { useXAxis, useYAxis } from './useAxis'; +import { useZAxis } from './useZAxis'; + +/** + * 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 } = useXAxes(); - - 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 } = useYAxes(); + const axis = useYAxis(axisId); - const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0]; - - 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 } = useZAxes(); - - const id = typeof identifier === 'string' ? identifier : zAxisIds[identifier ?? 0]; + const axis = useZAxis(axisId); - return zAxis[id]?.colorScale; + return axis.colorScale; } diff --git a/packages/x-charts/src/hooks/useDrawingArea.ts b/packages/x-charts/src/hooks/useDrawingArea.ts index 0519e645a4faf..63710546bdc78 100644 --- a/packages/x-charts/src/hooks/useDrawingArea.ts +++ b/packages/x-charts/src/hooks/useDrawingArea.ts @@ -11,7 +11,15 @@ export type ChartDrawingArea = { width: number; height: number; }; -export function useDrawingArea() { + +/** + * 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, and right dimensions. + * + * @returns The drawing area dimensions. + */ +export function useDrawingArea(): ChartDrawingArea { const store = useStore(); return useSelector(store, selectorChartDrawingArea); } 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..73c3fc20e2c03 --- /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 { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; + +describe('useLineSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'line', + stackedData: [] as [number, number][], + } as const; + + const mockSeries: ProcessedSeries = { + 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..1f9f60d573285 --- /dev/null +++ b/packages/x-charts/src/hooks/useLineSeries.ts @@ -0,0 +1,31 @@ +'use client'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; + +/** + * 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(): ProcessedSeries['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 { + return useSeriesOfType('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..523d54337d5ef --- /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 { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; + +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: ProcessedSeries = { + 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..5547354d354b8 --- /dev/null +++ b/packages/x-charts/src/hooks/usePieSeries.ts @@ -0,0 +1,31 @@ +'use client'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; + +/** + * 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(): ProcessedSeries['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 { + return useSeriesOfType('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..031cb84ba3880 --- /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 { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; + +describe('useScatterSeries', () => { + const defaultProps = { + valueFormatter: (v: any) => v, + color: 'red', + layout: 'vertical', + type: 'scatter', + } as const; + + const mockSeries: ProcessedSeries = { + 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..2ab00186fd203 --- /dev/null +++ b/packages/x-charts/src/hooks/useScatterSeries.ts @@ -0,0 +1,31 @@ +'use client'; +import { ProcessedSeries } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.types'; +import { SeriesId } from '../models/seriesType/common'; +import { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { useSeriesOfType } from '../internals/useSeriesOfType'; + +/** + * 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(): ProcessedSeries['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 { + return useSeriesOfType('scatter', ...seriesIds); +} diff --git a/packages/x-charts/src/hooks/useSeries.ts b/packages/x-charts/src/hooks/useSeries.ts index ed1e54bf26fe7..d45c7ef497cdd 100644 --- a/packages/x-charts/src/hooks/useSeries.ts +++ b/packages/x-charts/src/hooks/useSeries.ts @@ -1,5 +1,4 @@ 'use client'; -import * as React from 'react'; import { useStore } from '../internals/store/useStore'; import { useSelector } from '../internals/store/useSelector'; import { selectorChartSeriesProcessed } from '../internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors'; @@ -15,55 +14,3 @@ export function useSeries() { const store = useStore<[UseChartSeriesSignature]>(); return useSelector(store, selectorChartSeriesProcessed); } - -/** - * 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() { - 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() { - 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() { - 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() { - 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 2da48b2992733..3e8dea2665919 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 { useChartContext } from '../context/ChartProvider'; +/** + * Get the ref for the SVG element. + * @returns The SVG ref. + */ export function useSvgRef(): React.RefObject { const context = useChartContext(); 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..13d76a8f474ec --- /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/useSeries'; + +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], + ); +}