From 35f6025ba220c207487f50fede17ef129e326371 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:34:42 +0200 Subject: [PATCH] [charts] Support axis with single value (#14191) --- .../x-charts/src/BarChart/extremums.test.ts | 4 +- packages/x-charts/src/BarChart/extremums.ts | 18 +++----- .../x-charts/src/ChartsXAxis/ChartsXAxis.tsx | 11 +++-- .../x-charts/src/ChartsYAxis/ChartsYAxis.tsx | 11 +++-- packages/x-charts/src/LineChart/extremums.ts | 43 +++++++------------ .../x-charts/src/ScatterChart/extremums.ts | 34 ++++++--------- .../CartesianProvider/getAxisExtremum.ts | 16 ++----- .../PluginProvider/ExtremumGetter.types.ts | 2 +- packages/x-charts/src/hooks/useTicks.ts | 7 ++- packages/x-charts/src/internals/isInfinity.ts | 3 ++ 10 files changed, 63 insertions(+), 86 deletions(-) create mode 100644 packages/x-charts/src/internals/isInfinity.ts diff --git a/packages/x-charts/src/BarChart/extremums.test.ts b/packages/x-charts/src/BarChart/extremums.test.ts index 6222d82357e8..798954aac8dc 100644 --- a/packages/x-charts/src/BarChart/extremums.test.ts +++ b/packages/x-charts/src/BarChart/extremums.test.ts @@ -55,8 +55,8 @@ describe('BarChart - extremums', () => { it('should correctly get Infinity when empty data', () => { const [x, y] = getExtremumX(buildData([], 'horizontal')); - expect(x).to.equal(null); - expect(y).to.equal(null); + expect(x).to.equal(Infinity); + expect(y).to.equal(-Infinity); }); }); }); diff --git a/packages/x-charts/src/BarChart/extremums.ts b/packages/x-charts/src/BarChart/extremums.ts index d6fb3f2e79d9..e0c6d0a2ba1f 100644 --- a/packages/x-charts/src/BarChart/extremums.ts +++ b/packages/x-charts/src/BarChart/extremums.ts @@ -22,19 +22,15 @@ const getValueExtremum: ExtremumGetter<'bar'> = (params) => { .reduce( (acc: ExtremumGetterResult, seriesId) => { const [seriesMin, seriesMax] = series[seriesId].stackedData?.reduce( - (seriesAcc, values) => [ - Math.min(...values, ...(seriesAcc[0] === null ? [] : [seriesAcc[0]])), - Math.max(...values, ...(seriesAcc[1] === null ? [] : [seriesAcc[1]])), - ], - series[seriesId].stackedData[0], - ) ?? [null, null]; + (seriesAcc, values) => { + return [Math.min(...values, seriesAcc[0]), Math.max(...values, seriesAcc[1])]; + }, + [Infinity, -Infinity], + ) ?? [Infinity, -Infinity]; - return [ - acc[0] === null ? seriesMin : Math.min(seriesMin, acc[0]), - acc[1] === null ? seriesMax : Math.max(seriesMax, acc[1]), - ]; + return [Math.min(seriesMin, acc[0]), Math.max(seriesMax, acc[1])]; }, - [null, null], + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx index 52409925482d..b3e47a0dcff9 100644 --- a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx +++ b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx @@ -13,6 +13,8 @@ import { getMinXTranslation } from '../internals/geometry'; import { useMounted } from '../hooks/useMounted'; import { useDrawingArea } from '../hooks/useDrawingArea'; import { getWordsByLines } from '../internals/getWordsByLines'; +import { isInfinity } from '../internals/isInfinity'; +import { isBandScale } from '../internals/isBandScale'; const useUtilityClasses = (ownerState: ChartsXAxisProps & { theme: Theme }) => { const { classes, position } = ownerState; @@ -194,10 +196,11 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { }); const domain = xScale.domain(); - if (domain.length === 0 || domain[0] === domain[1]) { - // Skip axis rendering if - // - the data is empty (for band and point axis) - // - No data is associated to the axis (other scale types) + const ordinalAxis = isBandScale(xScale); + // Skip axis rendering if no data is available + // - The domain is an empty array for band/point scales. + // - The domains contains Infinity for continuous scales. + if ((ordinalAxis && domain.length === 0) || (!ordinalAxis && domain.some(isInfinity))) { return null; } return ( diff --git a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx index eee7a68a7d29..55c2ac3e08af 100644 --- a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx +++ b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx @@ -10,6 +10,8 @@ import { ChartsYAxisProps } from '../models/axis'; import { AxisRoot } from '../internals/components/AxisSharedComponents'; import { ChartsText, ChartsTextProps } from '../ChartsText'; import { getAxisUtilityClass } from '../ChartsAxis/axisClasses'; +import { isInfinity } from '../internals/isInfinity'; +import { isBandScale } from '../internals/isBandScale'; const useUtilityClasses = (ownerState: ChartsYAxisProps & { theme: Theme }) => { const { classes, position } = ownerState; @@ -145,10 +147,11 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { }); const domain = yScale.domain(); - if (domain.length === 0 || domain[0] === domain[1]) { - // Skip axis rendering if - // - the data is empty (for band and point axis) - // - No data is associated to the axis (other scale types) + const ordinalAxis = isBandScale(yScale); + // Skip axis rendering if no data is available + // - The domain is an empty array for band/point scales. + // - The domains contains Infinity for continuous scales. + if ((ordinalAxis && domain.length === 0) || (!ordinalAxis && domain.some(isInfinity))) { return null; } diff --git a/packages/x-charts/src/LineChart/extremums.ts b/packages/x-charts/src/LineChart/extremums.ts index e0fdcfac51c0..100c2677e6e3 100644 --- a/packages/x-charts/src/LineChart/extremums.ts +++ b/packages/x-charts/src/LineChart/extremums.ts @@ -1,7 +1,4 @@ -import { - ExtremumGetter, - ExtremumGetterResult, -} from '../context/PluginProvider/ExtremumGetter.types'; +import { ExtremumGetter } from '../context/PluginProvider/ExtremumGetter.types'; export const getExtremumX: ExtremumGetter<'line'> = (params) => { const { axis } = params; @@ -11,23 +8,20 @@ export const getExtremumX: ExtremumGetter<'line'> = (params) => { return [minX, maxX]; }; -type GetValuesTypes = (d: [number, number]) => [number, number]; +type GetValues = (d: [number, number]) => [number, number]; function getSeriesExtremums( - getValues: GetValuesTypes, + getValues: GetValues, stackedData: [number, number][], -): ExtremumGetterResult { - if (stackedData.length === 0) { - return [null, null]; - } - return stackedData.reduce((seriesAcc, stackedValue) => { - const [base, value] = getValues(stackedValue); - - if (seriesAcc[0] === null) { - return [Math.min(base, value), Math.max(base, value)] as [number, number]; - } - return [Math.min(base, value, seriesAcc[0]), Math.max(base, value, seriesAcc[1])]; - }, getValues(stackedData[0])); +): [number, number] { + return stackedData.reduce<[number, number]>( + (seriesAcc, stackedValue) => { + const [base, value] = getValues(stackedValue); + + return [Math.min(base, value, seriesAcc[0]), Math.max(base, value, seriesAcc[1])]; + }, + [Infinity, -Infinity], + ); } export const getExtremumY: ExtremumGetter<'line'> = (params) => { @@ -39,28 +33,21 @@ export const getExtremumY: ExtremumGetter<'line'> = (params) => { return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); }) .reduce( - (acc: ExtremumGetterResult, seriesId) => { + (acc, seriesId) => { const { area, stackedData } = series[seriesId]; const isArea = area !== undefined; // Since this series is not used to display an area, we do not consider the base (the d[0]). - const getValues: GetValuesTypes = + const getValues: GetValues = isArea && axis.scaleType !== 'log' && typeof series[seriesId].baseline !== 'string' ? (d) => d : (d) => [d[1], d[1]]; const seriesExtremums = getSeriesExtremums(getValues, stackedData); - if (acc[0] === null) { - return seriesExtremums; - } - if (seriesExtremums[0] === null) { - return acc; - } - const [seriesMin, seriesMax] = seriesExtremums; return [Math.min(seriesMin, acc[0]), Math.max(seriesMax, acc[1])]; }, - [null, null], + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/ScatterChart/extremums.ts b/packages/x-charts/src/ScatterChart/extremums.ts index e63cad1b03b1..adb44d7e1729 100644 --- a/packages/x-charts/src/ScatterChart/extremums.ts +++ b/packages/x-charts/src/ScatterChart/extremums.ts @@ -7,12 +7,6 @@ const mergeMinMax = ( acc: ExtremumGetterResult, val: ExtremumGetterResult, ): ExtremumGetterResult => { - if (acc[0] === null || acc[1] === null) { - return val; - } - if (val[0] === null || val[1] === null) { - return acc; - } return [Math.min(acc[0], val[0]), Math.max(acc[1], val[1])]; }; @@ -24,18 +18,17 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { const axisId = series[seriesId].xAxisId ?? series[seriesId].xAxisKey; return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) - .reduce( - (acc: ExtremumGetterResult, seriesId) => { - const seriesMinMax = series[seriesId].data.reduce( + .reduce( + (acc, seriesId) => { + const seriesMinMax = series[seriesId].data.reduce( (accSeries: ExtremumGetterResult, { x }) => { - const val = [x, x] as ExtremumGetterResult; - return mergeMinMax(accSeries, val); + return mergeMinMax(accSeries, [x, x]); }, - [null, null], + [Infinity, -Infinity], ); return mergeMinMax(acc, seriesMinMax); }, - [null, null] as ExtremumGetterResult, + [Infinity, -Infinity], ); }; @@ -47,17 +40,16 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { const axisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) - .reduce( - (acc: ExtremumGetterResult, seriesId) => { - const seriesMinMax = series[seriesId].data.reduce( - (accSeries: ExtremumGetterResult, { y }) => { - const val = [y, y] as ExtremumGetterResult; - return mergeMinMax(accSeries, val); + .reduce( + (acc, seriesId) => { + const seriesMinMax = series[seriesId].data.reduce( + (accSeries, { y }) => { + return mergeMinMax(accSeries, [y, y]); }, - [null, null], + [Infinity, -Infinity], ); return mergeMinMax(acc, seriesMinMax); }, - [null, null] as ExtremumGetterResult, + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts b/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts index be7839633c47..2785a46c4abf 100644 --- a/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts +++ b/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts @@ -18,19 +18,9 @@ const axisExtremumCallback = ( series, axis, isDefaultAxis, - }) ?? [null, null]; + }) ?? [Infinity, -Infinity]; - const [minData, maxData] = acc; - - if (minData === null || maxData === null) { - return [minChartTypeData!, maxChartTypeData!]; - } - - if (minChartTypeData === null || maxChartTypeData === null) { - return [minData, maxData]; - } - - return [Math.min(minChartTypeData, minData), Math.max(maxChartTypeData, maxData)]; + return [Math.min(minChartTypeData, acc[0]), Math.max(maxChartTypeData, acc[1])]; }; export const getAxisExtremum = ( @@ -44,6 +34,6 @@ export const getAxisExtremum = ( return charTypes.reduce( (acc, charType) => axisExtremumCallback(acc, charType, axis, getters, isDefaultAxis, formattedSeries), - [null, null], + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts b/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts index a1cda8d39331..bd6b0bb709a9 100644 --- a/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts +++ b/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts @@ -16,7 +16,7 @@ type ExtremumGetterParams = { isDefaultAxis: boolean; }; -export type ExtremumGetterResult = [number, number] | [null, null]; +export type ExtremumGetterResult = [number, number]; export type ExtremumGetter = ( params: ExtremumGetterParams, diff --git a/packages/x-charts/src/hooks/useTicks.ts b/packages/x-charts/src/hooks/useTicks.ts index 4b41fe74eeab..3cc08f9a80c6 100644 --- a/packages/x-charts/src/hooks/useTicks.ts +++ b/packages/x-charts/src/hooks/useTicks.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { AxisConfig, D3Scale } from '../models/axis'; import { isBandScale } from '../internals/isBandScale'; +import { isInfinity } from '../internals/isInfinity'; export interface TickParams { /** @@ -145,8 +146,10 @@ export function useTicks( })); } - if (scale.domain().length === 0 || scale.domain()[0] === scale.domain()[1]) { - // The axis should not be visible, so ticks should also be hidden. + const domain = scale.domain(); + // Skip axis rendering if no data is available + // - The domains contains Infinity for continuous scales. + if (domain.some(isInfinity)) { return []; } diff --git a/packages/x-charts/src/internals/isInfinity.ts b/packages/x-charts/src/internals/isInfinity.ts new file mode 100644 index 000000000000..d09834c55e40 --- /dev/null +++ b/packages/x-charts/src/internals/isInfinity.ts @@ -0,0 +1,3 @@ +export function isInfinity(v: any): v is number { + return typeof v === 'number' && !Number.isFinite(v); +}