Skip to content

Commit

Permalink
[charts] Support axis with single value (#14191)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette committed Aug 14, 2024
1 parent 10d946e commit 35f6025
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 86 deletions.
4 changes: 2 additions & 2 deletions packages/x-charts/src/BarChart/extremums.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Expand Down
18 changes: 7 additions & 11 deletions packages/x-charts/src/BarChart/extremums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
);
};

Expand Down
11 changes: 7 additions & 4 deletions packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
Expand Down
11 changes: 7 additions & 4 deletions packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
43 changes: 15 additions & 28 deletions packages/x-charts/src/LineChart/extremums.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) => {
Expand All @@ -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],
);
};
34 changes: 13 additions & 21 deletions packages/x-charts/src/ScatterChart/extremums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])];
};

Expand All @@ -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<ExtremumGetterResult>(
(acc, seriesId) => {
const seriesMinMax = series[seriesId].data.reduce<ExtremumGetterResult>(
(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],
);
};

Expand All @@ -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<ExtremumGetterResult>(
(acc, seriesId) => {
const seriesMinMax = series[seriesId].data.reduce<ExtremumGetterResult>(
(accSeries, { y }) => {
return mergeMinMax(accSeries, [y, y]);
},
[null, null],
[Infinity, -Infinity],
);
return mergeMinMax(acc, seriesMinMax);
},
[null, null] as ExtremumGetterResult,
[Infinity, -Infinity],
);
};
16 changes: 3 additions & 13 deletions packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,9 @@ const axisExtremumCallback = <T extends CartesianChartSeriesType>(
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 = (
Expand All @@ -44,6 +34,6 @@ export const getAxisExtremum = (
return charTypes.reduce<ExtremumGetterResult>(
(acc, charType) =>
axisExtremumCallback(acc, charType, axis, getters, isDefaultAxis, formattedSeries),
[null, null],
[Infinity, -Infinity],
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type ExtremumGetterParams<T extends ChartSeriesType> = {
isDefaultAxis: boolean;
};

export type ExtremumGetterResult = [number, number] | [null, null];
export type ExtremumGetterResult = [number, number];

export type ExtremumGetter<T extends ChartSeriesType> = (
params: ExtremumGetterParams<T>,
Expand Down
7 changes: 5 additions & 2 deletions packages/x-charts/src/hooks/useTicks.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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 [];
}

Expand Down
3 changes: 3 additions & 0 deletions packages/x-charts/src/internals/isInfinity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isInfinity(v: any): v is number {
return typeof v === 'number' && !Number.isFinite(v);
}

0 comments on commit 35f6025

Please sign in to comment.