Skip to content

Commit

Permalink
Add different signature for each series type hook
Browse files Browse the repository at this point in the history
  • Loading branch information
JCQuintas committed Nov 25, 2024
1 parent f59ddbb commit 6d2192a
Show file tree
Hide file tree
Showing 19 changed files with 471 additions and 61 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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\"",
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/BarChart/BarPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/AreaPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/LineHighlightPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/LinePlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/LineChart/MarkPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/PieChart/PiePlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
2 changes: 1 addition & 1 deletion packages/x-charts/src/ScatterChart/ScatterPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ScatterProps>;
Expand Down
4 changes: 4 additions & 0 deletions packages/x-charts/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
59 changes: 59 additions & 0 deletions packages/x-charts/src/hooks/useBarSeries.test.ts
Original file line number Diff line number Diff line change
@@ -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']]);
});
});
50 changes: 50 additions & 0 deletions packages/x-charts/src/hooks/useBarSeries.ts
Original file line number Diff line number Diff line change
@@ -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<SeriesId, DefaultizedBarSeriesType>; 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],
);
}
59 changes: 59 additions & 0 deletions packages/x-charts/src/hooks/useLineSeries.test.ts
Original file line number Diff line number Diff line change
@@ -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']]);
});
});
50 changes: 50 additions & 0 deletions packages/x-charts/src/hooks/useLineSeries.ts
Original file line number Diff line number Diff line change
@@ -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<SeriesId, DefaultizedLineSeriesType>; 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],
);
}
69 changes: 69 additions & 0 deletions packages/x-charts/src/hooks/usePieSeries.test.ts
Original file line number Diff line number Diff line change
@@ -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']]);
});
});
Loading

0 comments on commit 6d2192a

Please sign in to comment.