From 90f4f153d68d24ef68f862a1079158696fb9a082 Mon Sep 17 00:00:00 2001 From: Sen Lin Date: Wed, 24 Apr 2024 20:58:54 +0100 Subject: [PATCH 1/4] Add stepRatioControl --- src/chart/generateCategoricalChart.tsx | 15 +++-- src/chart/types.ts | 2 + src/util/CartesianUtils.ts | 13 +++- src/util/ChartUtils.ts | 4 +- src/util/scale/getNiceTickValues.ts | 33 ++++++++-- .../stories/API/chart/BarChart.stories.tsx | 1 + storybook/stories/API/props/ChartProps.ts | 18 +++++ test/cartesian/YAxis.spec.tsx | 66 +++++++++++++++++++ test/util/ChartUtils.spec.tsx | 16 +++++ 9 files changed, 154 insertions(+), 14 deletions(-) diff --git a/src/chart/generateCategoricalChart.tsx b/src/chart/generateCategoricalChart.tsx index d97d8bdd67..5499f9f2bc 100644 --- a/src/chart/generateCategoricalChart.tsx +++ b/src/chart/generateCategoricalChart.tsx @@ -78,6 +78,7 @@ import { } from '../util/types'; import { AccessibilityManager } from './AccessibilityManager'; import { isDomainSpecifiedByUser } from '../util/isDomainSpecifiedByUser'; +import { StepRatioControl } from '../util/scale/getNiceTickValues'; import { ChartLayoutContextProvider } from '../context/chartLayoutContext'; import { AxisMap, CategoricalChartState } from './types'; import { AccessibilityContextProvider } from '../context/accessibilityContext'; @@ -802,6 +803,7 @@ export interface CategoricalChartProps { data?: any[]; layout?: LayoutType; stackOffset?: StackOffsetType; + stepRatioControl?: StepRatioControl; throttleDelay?: number; margin?: Margin; barCategoryGap?: number | string; @@ -1006,7 +1008,7 @@ export const generateCategoricalChart = ({ return null; } - const { children, layout, stackOffset, data, reverseStackOrder } = props; + const { children, layout, stackOffset, data, reverseStackOrder, stepRatioControl } = props; const { numericAxisName, cateAxisName } = getAxisNameByLayout(layout); const graphicalItems = findAllByType(children, GraphicalChild); const stackGroups: AxisStackGroups = getStackGroupsByAxisId( @@ -1035,7 +1037,7 @@ export const generateCategoricalChart = ({ const offset: ChartOffset = calculateOffset({ ...axisObj, props }, prevState?.legendBBox); Object.keys(axisObj).forEach(key => { - axisObj[key] = formatAxisMap(props, axisObj[key], offset, key.replace('Map', ''), chartName); + axisObj[key] = formatAxisMap(props, axisObj[key], offset, key.replace('Map', ''), chartName, stepRatioControl); }); const cateAxisMap = axisObj[`${cateAxisName}Map`]; const ticksObj = tooltipTicksGenerator(cateAxisMap); @@ -1080,6 +1082,7 @@ export const generateCategoricalChart = ({ margin: { top: 5, right: 5, bottom: 5, left: 5 } as Margin, reverseStackOrder: false, syncMethod: 'index', + stepRatioControl: 0.05, ...defaultProps, }; @@ -1219,7 +1222,7 @@ export const generateCategoricalChart = ({ nextProps: CategoricalChartProps, prevState: CategoricalChartState, ): CategoricalChartState => { - const { dataKey, data, children, width, height, layout, stackOffset, margin } = nextProps; + const { dataKey, data, children, width, height, layout, stackOffset, margin, stepRatioControl } = nextProps; const { dataStartIndex, dataEndIndex } = prevState; if (prevState.updateId === undefined) { @@ -1242,6 +1245,7 @@ export const generateCategoricalChart = ({ prevHeight: height, prevLayout: layout, prevStackOffset: stackOffset, + prevStepRatioControl: stepRatioControl, prevMargin: margin, prevChildren: children, }; @@ -1253,6 +1257,7 @@ export const generateCategoricalChart = ({ height !== prevState.prevHeight || layout !== prevState.prevLayout || stackOffset !== prevState.prevStackOffset || + stepRatioControl !== prevState.prevStepRatioControl || !shallowEqual(margin, prevState.prevMargin) ) { const defaultState = createDefaultState(nextProps); @@ -1296,6 +1301,7 @@ export const generateCategoricalChart = ({ prevHeight: height, prevLayout: layout, prevStackOffset: stackOffset, + prevStepRatioControl: stepRatioControl, prevMargin: margin, prevChildren: children, }; @@ -1856,7 +1862,8 @@ export const generateCategoricalChart = ({ return null; } - const { children, className, width, height, style, compact, title, desc, ...others } = this.props; + const { children, className, width, height, style, compact, title, desc, stepRatioControl, ...others } = + this.props; const attrs = filterProps(others, false); // The "compact" mode is mainly used as the panorama within Brush diff --git a/src/chart/types.ts b/src/chart/types.ts index 1c54e65d20..1494c623dd 100644 --- a/src/chart/types.ts +++ b/src/chart/types.ts @@ -11,6 +11,7 @@ import { } from '../util/types'; import { AxisStackGroups } from '../util/ChartUtils'; import { BoundingBox } from '../util/useGetBoundingClientRect'; +import { StepRatioControl } from '../util/scale/getNiceTickValues'; export type AxisMap = { [axisId: string]: BaseAxisProps; @@ -82,6 +83,7 @@ export interface CategoricalChartState { prevStackOffset?: StackOffsetType; prevMargin?: Margin; prevChildren?: any; + prevStepRatioControl?: StepRatioControl; stackGroups?: AxisStackGroups; tooltipPortal?: HTMLElement | null; diff --git a/src/util/CartesianUtils.ts b/src/util/CartesianUtils.ts index 1d62b48598..d87b732b7d 100644 --- a/src/util/CartesianUtils.ts +++ b/src/util/CartesianUtils.ts @@ -6,6 +6,7 @@ import { findChildByType } from './ReactUtils'; import { Coordinate, AxisType, Size } from './types'; import { getPercentValue } from './DataUtils'; import { Bar } from '../cartesian/Bar'; +import { StepRatioControl } from './scale/getNiceTickValues'; /** * Calculate the scale function, position, width, height of axes @@ -14,9 +15,17 @@ import { Bar } from '../cartesian/Bar'; * @param {Object} offset The offset of main part in the svg element * @param {String} axisType The type of axes, x-axis or y-axis * @param {String} chartName The name of chart + * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Object} Configuration */ -export const formatAxisMap = (props: any, axisMap: any, offset: any, axisType: AxisType, chartName: string) => { +export const formatAxisMap = ( + props: any, + axisMap: any, + offset: any, + axisType: AxisType, + chartName: string, + stepRatioControl: StepRatioControl = 0.05, +) => { const { width, height, layout, children } = props; const ids = Object.keys(axisMap); const steps: Record = { @@ -90,7 +99,7 @@ export const formatAxisMap = (props: any, axisMap: any, offset: any, axisType: A const { scale, realScaleType } = parseScale(axis, chartName, hasBar); scale.domain(domain).range(range); checkDomainOfScale(scale); - const ticks = getTicksOfScale(scale, { ...axis, realScaleType }); + const ticks = getTicksOfScale(scale, { ...axis, realScaleType, stepRatioControl }); if (axisType === 'xAxis') { needSpace = (orientation === 'top' && !mirror) || (orientation === 'bottom' && mirror); diff --git a/src/util/ChartUtils.ts b/src/util/ChartUtils.ts index f2884ab5ef..4cf24dfd86 100644 --- a/src/util/ChartUtils.ts +++ b/src/util/ChartUtils.ts @@ -1051,7 +1051,7 @@ export const getStackGroupsByAxisId = ( * @return {Object} null */ export const getTicksOfScale = (scale: any, opts: any) => { - const { realScaleType, type, tickCount, originalDomain, allowDecimals } = opts; + const { realScaleType, type, tickCount, originalDomain, allowDecimals, stepRatioControl } = opts; const scaleType = realScaleType || opts.scale; if (scaleType !== 'auto' && scaleType !== 'linear') { @@ -1070,7 +1070,7 @@ export const getTicksOfScale = (scale: any, opts: any) => { return null; } - const tickValues = getNiceTickValues(domain, tickCount, allowDecimals); + const tickValues = getNiceTickValues(domain, tickCount, allowDecimals, stepRatioControl); scale.domain([min(tickValues), max(tickValues)]); return { niceTicks: tickValues }; diff --git a/src/util/scale/getNiceTickValues.ts b/src/util/scale/getNiceTickValues.ts index fca4289615..075dc68324 100644 --- a/src/util/scale/getNiceTickValues.ts +++ b/src/util/scale/getNiceTickValues.ts @@ -7,6 +7,8 @@ import Decimal from 'decimal.js-light'; import { compose, range, memoize, map, reverse } from './util/utils'; import { getDigitCount, rangeStep } from './util/arithmetic'; +export type StepRatioControl = 0.05 | 0.03 | 0.01; + /** * Calculate a interval of a minimum value and a maximum value * @@ -32,9 +34,15 @@ export const getValidInterval = ([min, max]: [number, number]) => { * difference by the tickCount * @param {Boolean} allowDecimals Allow the ticks to be decimals or not * @param {Integer} correctionFactor A correction factor + * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Decimal} The step which is easy to understand between two ticks */ -export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correctionFactor: number) => { +export const getFormatStep = ( + roughStep: Decimal, + allowDecimals: boolean, + correctionFactor: number, + stepRatioControl: StepRatioControl = 0.05, +) => { if (roughStep.lte(0)) { return new Decimal(0); } @@ -45,7 +53,7 @@ export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correc const digitCountValue = new Decimal(10).pow(digitCount); const stepRatio = roughStep.div(digitCountValue); // When an integer and a float multiplied, the accuracy of result may be wrong - const stepRatioScale = digitCount !== 1 ? 0.05 : 0.1; + const stepRatioScale = digitCount !== 1 ? stepRatioControl : 0.1; const amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())) .add(correctionFactor) .mul(stepRatioScale); @@ -104,6 +112,7 @@ export const getTickOfSingleValue = (value: number, tickCount: number, allowDeci * @param {Integer} tickCount The count of ticks * @param {Boolean} allowDecimals Allow the ticks to be decimals or not * @param {Number} correctionFactor A correction factor + * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Object} The step, minimum value of ticks, maximum value of ticks */ export const calculateStep = ( @@ -112,6 +121,7 @@ export const calculateStep = ( tickCount: number, allowDecimals: boolean, correctionFactor = 0, + stepRatioControl: StepRatioControl = 0.05, ): any => { // dirty hack (for recharts' test) if (!Number.isFinite((max - min) / (tickCount - 1))) { @@ -123,7 +133,12 @@ export const calculateStep = ( } // The step which is easy to understand between two ticks - const step = getFormatStep(new Decimal(max).sub(min).div(tickCount - 1), allowDecimals, correctionFactor); + const step = getFormatStep( + new Decimal(max).sub(min).div(tickCount - 1), + allowDecimals, + correctionFactor, + stepRatioControl, + ); // A medial value of ticks let middle; @@ -144,7 +159,7 @@ export const calculateStep = ( if (scaleCount > tickCount) { // When more ticks need to cover the interval, step should be bigger. - return calculateStep(min, max, tickCount, allowDecimals, correctionFactor + 1); + return calculateStep(min, max, tickCount, allowDecimals, correctionFactor + 1, stepRatioControl); } if (scaleCount < tickCount) { // When less ticks can cover the interval, we should add some additional ticks @@ -165,9 +180,15 @@ export const calculateStep = ( * @param {Number} min, max min: The minimum value, max: The maximum value * @param {Integer} tickCount The count of ticks * @param {Boolean} allowDecimals Allow the ticks to be decimals or not + * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Array} ticks */ -function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowDecimals = true) { +function getNiceTickValuesFn( + [min, max]: [number, number], + tickCount = 6, + allowDecimals = true, + stepRatioControl: StepRatioControl = 0.05, +) { // More than two ticks should be return const count = Math.max(tickCount, 2); const [cormin, cormax] = getValidInterval([min, max]); @@ -186,7 +207,7 @@ function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowD } // Get the step between two ticks - const { step, tickMin, tickMax } = calculateStep(cormin, cormax, count, allowDecimals, 0); + const { step, tickMin, tickMax } = calculateStep(cormin, cormax, count, allowDecimals, 0, stepRatioControl); const values = rangeStep(tickMin, tickMax.add(new Decimal(0.1).mul(step)), step); diff --git a/storybook/stories/API/chart/BarChart.stories.tsx b/storybook/stories/API/chart/BarChart.stories.tsx index f088368f8b..6f390ee1d2 100644 --- a/storybook/stories/API/chart/BarChart.stories.tsx +++ b/storybook/stories/API/chart/BarChart.stories.tsx @@ -64,6 +64,7 @@ export const Stacked = { args: { data: pageDataWithNegativeNumbers, stackOffset: 'none', + stepRatioControl: 0.05, id: 'BarChart-Stacked', }, }; diff --git a/storybook/stories/API/props/ChartProps.ts b/storybook/stories/API/props/ChartProps.ts index 09d27cbf3a..88f241dcc1 100644 --- a/storybook/stories/API/props/ChartProps.ts +++ b/storybook/stories/API/props/ChartProps.ts @@ -181,6 +181,24 @@ toggling between multiple dataKey.`, category: 'General', }, }, + stepRatioControl: { + description: `This parameter controls the domain range for the y-axis and the step increments within that domain. + A lower value for this parameter moves the maximum y-axis value closer to the highest data point in the dataset. + For example, with the dataset [0, 400, 800, 1200, 1600], a stepRatioControl value of 0.05 would set the y-axis domain to [0, 2000]. + Conversely, setting stepRatioControl to 0.1 brings the y-axis domain closer to [0, 1600]. + Regardless of the parameter chosen, the step sizes within the domain remain even and will have "nice" values. + Adjusting this parameter is recommended only when a minor change to the dataset's maximum/minimum values causes the default domain to shift dramatically.`, + options: ['0.01', '0.03', '0.05'], + control: { + type: 'select', + }, + table: { + type: { + summary: 'number', + }, + category: 'General', + }, + }, cx: { description: 'The x-coordinate of the center of the circle.', table: { diff --git a/test/cartesian/YAxis.spec.tsx b/test/cartesian/YAxis.spec.tsx index 2380137edc..6755f9b91a 100644 --- a/test/cartesian/YAxis.spec.tsx +++ b/test/cartesian/YAxis.spec.tsx @@ -295,4 +295,70 @@ describe('', () => { expect(allText).toContain('1200'); expect(allText).toContain('1600'); }); + + it('should render all labels when stepRatioControl is 0.03', () => { + const { container } = render( + + + + + + + , + ); + const allLabels = container.querySelectorAll('.recharts-yAxis .recharts-text.recharts-cartesian-axis-tick-value'); + expect.soft(allLabels).toHaveLength(5); + const allText = Array.from(allLabels).map(el => el.textContent); + expect.soft(allText).toHaveLength(5); + expect(allText).toContain('0'); + expect(allText).toContain('390'); + expect(allText).toContain('780'); + expect(allText).toContain('1170'); + expect(allText).toContain('1560'); + }); + + it('should render all labels when stepRatioControl is 0.01', () => { + const { container } = render( + + + + + + + , + ); + const allLabels = container.querySelectorAll('.recharts-yAxis .recharts-text.recharts-cartesian-axis-tick-value'); + expect.soft(allLabels).toHaveLength(5); + const allText = Array.from(allLabels).map(el => el.textContent); + expect.soft(allText).toHaveLength(5); + expect(allText).toContain('0'); + expect(allText).toContain('380'); + expect(allText).toContain('760'); + expect(allText).toContain('1140'); + expect(allText).toContain('1520'); + }); }); diff --git a/test/util/ChartUtils.spec.tsx b/test/util/ChartUtils.spec.tsx index ec132ae497..25cf8547f4 100644 --- a/test/util/ChartUtils.spec.tsx +++ b/test/util/ChartUtils.spec.tsx @@ -349,6 +349,22 @@ describe('getTicksOfScale', () => { expect(result?.niceTicks).toEqual([0, 0.25, 0.5, 0.75, 1]); }); + + it('should generate correct tick values with stepRatioControl set to 0.03', () => { + const scale = scaleLinear(); + const opts = { + scale: 'linear', + type: 'number', + tickCount: 5, + originalDomain: ['auto', 'auto'], + allowDecimals: true, + stepRatioControl: 0.03, + }; + + const result = getTicksOfScale(scale, opts); + + expect(result?.niceTicks).toEqual([0, 0.27, 0.54, 0.81, 1.08]); + }); }); describe('calculateActiveTickIndex', () => { From ab43d5910b601d3cfe54eef376e72de1546ae71e Mon Sep 17 00:00:00 2001 From: Sen Lin Date: Sat, 27 Apr 2024 01:37:12 +0100 Subject: [PATCH 2/4] Revert "Add stepRatioControl" This reverts commit 90f4f153d68d24ef68f862a1079158696fb9a082. --- src/chart/generateCategoricalChart.tsx | 15 ++--- src/chart/types.ts | 2 - src/util/CartesianUtils.ts | 13 +--- src/util/ChartUtils.ts | 4 +- src/util/scale/getNiceTickValues.ts | 33 ++-------- .../stories/API/chart/BarChart.stories.tsx | 1 - storybook/stories/API/props/ChartProps.ts | 18 ----- test/cartesian/YAxis.spec.tsx | 66 ------------------- test/util/ChartUtils.spec.tsx | 16 ----- 9 files changed, 14 insertions(+), 154 deletions(-) diff --git a/src/chart/generateCategoricalChart.tsx b/src/chart/generateCategoricalChart.tsx index 5499f9f2bc..d97d8bdd67 100644 --- a/src/chart/generateCategoricalChart.tsx +++ b/src/chart/generateCategoricalChart.tsx @@ -78,7 +78,6 @@ import { } from '../util/types'; import { AccessibilityManager } from './AccessibilityManager'; import { isDomainSpecifiedByUser } from '../util/isDomainSpecifiedByUser'; -import { StepRatioControl } from '../util/scale/getNiceTickValues'; import { ChartLayoutContextProvider } from '../context/chartLayoutContext'; import { AxisMap, CategoricalChartState } from './types'; import { AccessibilityContextProvider } from '../context/accessibilityContext'; @@ -803,7 +802,6 @@ export interface CategoricalChartProps { data?: any[]; layout?: LayoutType; stackOffset?: StackOffsetType; - stepRatioControl?: StepRatioControl; throttleDelay?: number; margin?: Margin; barCategoryGap?: number | string; @@ -1008,7 +1006,7 @@ export const generateCategoricalChart = ({ return null; } - const { children, layout, stackOffset, data, reverseStackOrder, stepRatioControl } = props; + const { children, layout, stackOffset, data, reverseStackOrder } = props; const { numericAxisName, cateAxisName } = getAxisNameByLayout(layout); const graphicalItems = findAllByType(children, GraphicalChild); const stackGroups: AxisStackGroups = getStackGroupsByAxisId( @@ -1037,7 +1035,7 @@ export const generateCategoricalChart = ({ const offset: ChartOffset = calculateOffset({ ...axisObj, props }, prevState?.legendBBox); Object.keys(axisObj).forEach(key => { - axisObj[key] = formatAxisMap(props, axisObj[key], offset, key.replace('Map', ''), chartName, stepRatioControl); + axisObj[key] = formatAxisMap(props, axisObj[key], offset, key.replace('Map', ''), chartName); }); const cateAxisMap = axisObj[`${cateAxisName}Map`]; const ticksObj = tooltipTicksGenerator(cateAxisMap); @@ -1082,7 +1080,6 @@ export const generateCategoricalChart = ({ margin: { top: 5, right: 5, bottom: 5, left: 5 } as Margin, reverseStackOrder: false, syncMethod: 'index', - stepRatioControl: 0.05, ...defaultProps, }; @@ -1222,7 +1219,7 @@ export const generateCategoricalChart = ({ nextProps: CategoricalChartProps, prevState: CategoricalChartState, ): CategoricalChartState => { - const { dataKey, data, children, width, height, layout, stackOffset, margin, stepRatioControl } = nextProps; + const { dataKey, data, children, width, height, layout, stackOffset, margin } = nextProps; const { dataStartIndex, dataEndIndex } = prevState; if (prevState.updateId === undefined) { @@ -1245,7 +1242,6 @@ export const generateCategoricalChart = ({ prevHeight: height, prevLayout: layout, prevStackOffset: stackOffset, - prevStepRatioControl: stepRatioControl, prevMargin: margin, prevChildren: children, }; @@ -1257,7 +1253,6 @@ export const generateCategoricalChart = ({ height !== prevState.prevHeight || layout !== prevState.prevLayout || stackOffset !== prevState.prevStackOffset || - stepRatioControl !== prevState.prevStepRatioControl || !shallowEqual(margin, prevState.prevMargin) ) { const defaultState = createDefaultState(nextProps); @@ -1301,7 +1296,6 @@ export const generateCategoricalChart = ({ prevHeight: height, prevLayout: layout, prevStackOffset: stackOffset, - prevStepRatioControl: stepRatioControl, prevMargin: margin, prevChildren: children, }; @@ -1862,8 +1856,7 @@ export const generateCategoricalChart = ({ return null; } - const { children, className, width, height, style, compact, title, desc, stepRatioControl, ...others } = - this.props; + const { children, className, width, height, style, compact, title, desc, ...others } = this.props; const attrs = filterProps(others, false); // The "compact" mode is mainly used as the panorama within Brush diff --git a/src/chart/types.ts b/src/chart/types.ts index 1494c623dd..1c54e65d20 100644 --- a/src/chart/types.ts +++ b/src/chart/types.ts @@ -11,7 +11,6 @@ import { } from '../util/types'; import { AxisStackGroups } from '../util/ChartUtils'; import { BoundingBox } from '../util/useGetBoundingClientRect'; -import { StepRatioControl } from '../util/scale/getNiceTickValues'; export type AxisMap = { [axisId: string]: BaseAxisProps; @@ -83,7 +82,6 @@ export interface CategoricalChartState { prevStackOffset?: StackOffsetType; prevMargin?: Margin; prevChildren?: any; - prevStepRatioControl?: StepRatioControl; stackGroups?: AxisStackGroups; tooltipPortal?: HTMLElement | null; diff --git a/src/util/CartesianUtils.ts b/src/util/CartesianUtils.ts index d87b732b7d..1d62b48598 100644 --- a/src/util/CartesianUtils.ts +++ b/src/util/CartesianUtils.ts @@ -6,7 +6,6 @@ import { findChildByType } from './ReactUtils'; import { Coordinate, AxisType, Size } from './types'; import { getPercentValue } from './DataUtils'; import { Bar } from '../cartesian/Bar'; -import { StepRatioControl } from './scale/getNiceTickValues'; /** * Calculate the scale function, position, width, height of axes @@ -15,17 +14,9 @@ import { StepRatioControl } from './scale/getNiceTickValues'; * @param {Object} offset The offset of main part in the svg element * @param {String} axisType The type of axes, x-axis or y-axis * @param {String} chartName The name of chart - * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Object} Configuration */ -export const formatAxisMap = ( - props: any, - axisMap: any, - offset: any, - axisType: AxisType, - chartName: string, - stepRatioControl: StepRatioControl = 0.05, -) => { +export const formatAxisMap = (props: any, axisMap: any, offset: any, axisType: AxisType, chartName: string) => { const { width, height, layout, children } = props; const ids = Object.keys(axisMap); const steps: Record = { @@ -99,7 +90,7 @@ export const formatAxisMap = ( const { scale, realScaleType } = parseScale(axis, chartName, hasBar); scale.domain(domain).range(range); checkDomainOfScale(scale); - const ticks = getTicksOfScale(scale, { ...axis, realScaleType, stepRatioControl }); + const ticks = getTicksOfScale(scale, { ...axis, realScaleType }); if (axisType === 'xAxis') { needSpace = (orientation === 'top' && !mirror) || (orientation === 'bottom' && mirror); diff --git a/src/util/ChartUtils.ts b/src/util/ChartUtils.ts index 4cf24dfd86..f2884ab5ef 100644 --- a/src/util/ChartUtils.ts +++ b/src/util/ChartUtils.ts @@ -1051,7 +1051,7 @@ export const getStackGroupsByAxisId = ( * @return {Object} null */ export const getTicksOfScale = (scale: any, opts: any) => { - const { realScaleType, type, tickCount, originalDomain, allowDecimals, stepRatioControl } = opts; + const { realScaleType, type, tickCount, originalDomain, allowDecimals } = opts; const scaleType = realScaleType || opts.scale; if (scaleType !== 'auto' && scaleType !== 'linear') { @@ -1070,7 +1070,7 @@ export const getTicksOfScale = (scale: any, opts: any) => { return null; } - const tickValues = getNiceTickValues(domain, tickCount, allowDecimals, stepRatioControl); + const tickValues = getNiceTickValues(domain, tickCount, allowDecimals); scale.domain([min(tickValues), max(tickValues)]); return { niceTicks: tickValues }; diff --git a/src/util/scale/getNiceTickValues.ts b/src/util/scale/getNiceTickValues.ts index 075dc68324..fca4289615 100644 --- a/src/util/scale/getNiceTickValues.ts +++ b/src/util/scale/getNiceTickValues.ts @@ -7,8 +7,6 @@ import Decimal from 'decimal.js-light'; import { compose, range, memoize, map, reverse } from './util/utils'; import { getDigitCount, rangeStep } from './util/arithmetic'; -export type StepRatioControl = 0.05 | 0.03 | 0.01; - /** * Calculate a interval of a minimum value and a maximum value * @@ -34,15 +32,9 @@ export const getValidInterval = ([min, max]: [number, number]) => { * difference by the tickCount * @param {Boolean} allowDecimals Allow the ticks to be decimals or not * @param {Integer} correctionFactor A correction factor - * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Decimal} The step which is easy to understand between two ticks */ -export const getFormatStep = ( - roughStep: Decimal, - allowDecimals: boolean, - correctionFactor: number, - stepRatioControl: StepRatioControl = 0.05, -) => { +export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correctionFactor: number) => { if (roughStep.lte(0)) { return new Decimal(0); } @@ -53,7 +45,7 @@ export const getFormatStep = ( const digitCountValue = new Decimal(10).pow(digitCount); const stepRatio = roughStep.div(digitCountValue); // When an integer and a float multiplied, the accuracy of result may be wrong - const stepRatioScale = digitCount !== 1 ? stepRatioControl : 0.1; + const stepRatioScale = digitCount !== 1 ? 0.05 : 0.1; const amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())) .add(correctionFactor) .mul(stepRatioScale); @@ -112,7 +104,6 @@ export const getTickOfSingleValue = (value: number, tickCount: number, allowDeci * @param {Integer} tickCount The count of ticks * @param {Boolean} allowDecimals Allow the ticks to be decimals or not * @param {Number} correctionFactor A correction factor - * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Object} The step, minimum value of ticks, maximum value of ticks */ export const calculateStep = ( @@ -121,7 +112,6 @@ export const calculateStep = ( tickCount: number, allowDecimals: boolean, correctionFactor = 0, - stepRatioControl: StepRatioControl = 0.05, ): any => { // dirty hack (for recharts' test) if (!Number.isFinite((max - min) / (tickCount - 1))) { @@ -133,12 +123,7 @@ export const calculateStep = ( } // The step which is easy to understand between two ticks - const step = getFormatStep( - new Decimal(max).sub(min).div(tickCount - 1), - allowDecimals, - correctionFactor, - stepRatioControl, - ); + const step = getFormatStep(new Decimal(max).sub(min).div(tickCount - 1), allowDecimals, correctionFactor); // A medial value of ticks let middle; @@ -159,7 +144,7 @@ export const calculateStep = ( if (scaleCount > tickCount) { // When more ticks need to cover the interval, step should be bigger. - return calculateStep(min, max, tickCount, allowDecimals, correctionFactor + 1, stepRatioControl); + return calculateStep(min, max, tickCount, allowDecimals, correctionFactor + 1); } if (scaleCount < tickCount) { // When less ticks can cover the interval, we should add some additional ticks @@ -180,15 +165,9 @@ export const calculateStep = ( * @param {Number} min, max min: The minimum value, max: The maximum value * @param {Integer} tickCount The count of ticks * @param {Boolean} allowDecimals Allow the ticks to be decimals or not - * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Array} ticks */ -function getNiceTickValuesFn( - [min, max]: [number, number], - tickCount = 6, - allowDecimals = true, - stepRatioControl: StepRatioControl = 0.05, -) { +function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowDecimals = true) { // More than two ticks should be return const count = Math.max(tickCount, 2); const [cormin, cormax] = getValidInterval([min, max]); @@ -207,7 +186,7 @@ function getNiceTickValuesFn( } // Get the step between two ticks - const { step, tickMin, tickMax } = calculateStep(cormin, cormax, count, allowDecimals, 0, stepRatioControl); + const { step, tickMin, tickMax } = calculateStep(cormin, cormax, count, allowDecimals, 0); const values = rangeStep(tickMin, tickMax.add(new Decimal(0.1).mul(step)), step); diff --git a/storybook/stories/API/chart/BarChart.stories.tsx b/storybook/stories/API/chart/BarChart.stories.tsx index 6f390ee1d2..f088368f8b 100644 --- a/storybook/stories/API/chart/BarChart.stories.tsx +++ b/storybook/stories/API/chart/BarChart.stories.tsx @@ -64,7 +64,6 @@ export const Stacked = { args: { data: pageDataWithNegativeNumbers, stackOffset: 'none', - stepRatioControl: 0.05, id: 'BarChart-Stacked', }, }; diff --git a/storybook/stories/API/props/ChartProps.ts b/storybook/stories/API/props/ChartProps.ts index 88f241dcc1..09d27cbf3a 100644 --- a/storybook/stories/API/props/ChartProps.ts +++ b/storybook/stories/API/props/ChartProps.ts @@ -181,24 +181,6 @@ toggling between multiple dataKey.`, category: 'General', }, }, - stepRatioControl: { - description: `This parameter controls the domain range for the y-axis and the step increments within that domain. - A lower value for this parameter moves the maximum y-axis value closer to the highest data point in the dataset. - For example, with the dataset [0, 400, 800, 1200, 1600], a stepRatioControl value of 0.05 would set the y-axis domain to [0, 2000]. - Conversely, setting stepRatioControl to 0.1 brings the y-axis domain closer to [0, 1600]. - Regardless of the parameter chosen, the step sizes within the domain remain even and will have "nice" values. - Adjusting this parameter is recommended only when a minor change to the dataset's maximum/minimum values causes the default domain to shift dramatically.`, - options: ['0.01', '0.03', '0.05'], - control: { - type: 'select', - }, - table: { - type: { - summary: 'number', - }, - category: 'General', - }, - }, cx: { description: 'The x-coordinate of the center of the circle.', table: { diff --git a/test/cartesian/YAxis.spec.tsx b/test/cartesian/YAxis.spec.tsx index 6755f9b91a..2380137edc 100644 --- a/test/cartesian/YAxis.spec.tsx +++ b/test/cartesian/YAxis.spec.tsx @@ -295,70 +295,4 @@ describe('', () => { expect(allText).toContain('1200'); expect(allText).toContain('1600'); }); - - it('should render all labels when stepRatioControl is 0.03', () => { - const { container } = render( - - - - - - - , - ); - const allLabels = container.querySelectorAll('.recharts-yAxis .recharts-text.recharts-cartesian-axis-tick-value'); - expect.soft(allLabels).toHaveLength(5); - const allText = Array.from(allLabels).map(el => el.textContent); - expect.soft(allText).toHaveLength(5); - expect(allText).toContain('0'); - expect(allText).toContain('390'); - expect(allText).toContain('780'); - expect(allText).toContain('1170'); - expect(allText).toContain('1560'); - }); - - it('should render all labels when stepRatioControl is 0.01', () => { - const { container } = render( - - - - - - - , - ); - const allLabels = container.querySelectorAll('.recharts-yAxis .recharts-text.recharts-cartesian-axis-tick-value'); - expect.soft(allLabels).toHaveLength(5); - const allText = Array.from(allLabels).map(el => el.textContent); - expect.soft(allText).toHaveLength(5); - expect(allText).toContain('0'); - expect(allText).toContain('380'); - expect(allText).toContain('760'); - expect(allText).toContain('1140'); - expect(allText).toContain('1520'); - }); }); diff --git a/test/util/ChartUtils.spec.tsx b/test/util/ChartUtils.spec.tsx index 25cf8547f4..ec132ae497 100644 --- a/test/util/ChartUtils.spec.tsx +++ b/test/util/ChartUtils.spec.tsx @@ -349,22 +349,6 @@ describe('getTicksOfScale', () => { expect(result?.niceTicks).toEqual([0, 0.25, 0.5, 0.75, 1]); }); - - it('should generate correct tick values with stepRatioControl set to 0.03', () => { - const scale = scaleLinear(); - const opts = { - scale: 'linear', - type: 'number', - tickCount: 5, - originalDomain: ['auto', 'auto'], - allowDecimals: true, - stepRatioControl: 0.03, - }; - - const result = getTicksOfScale(scale, opts); - - expect(result?.niceTicks).toEqual([0, 0.27, 0.54, 0.81, 1.08]); - }); }); describe('calculateActiveTickIndex', () => { From 4f8927b443802195effd84b6f39483049cd53261 Mon Sep 17 00:00:00 2001 From: Sen Lin Date: Sat, 27 Apr 2024 01:52:36 +0100 Subject: [PATCH 3/4] Automatically adjust stepRatioScale --- src/util/scale/getNiceTickValues.ts | 18 +++++++++++++----- test/cartesian/ReferenceArea.spec.tsx | 2 +- test/cartesian/ReferenceDot.spec.tsx | 2 +- .../ReferenceLine/ReferenceLine.spec.tsx | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/util/scale/getNiceTickValues.ts b/src/util/scale/getNiceTickValues.ts index fca4289615..e4a76b6ed9 100644 --- a/src/util/scale/getNiceTickValues.ts +++ b/src/util/scale/getNiceTickValues.ts @@ -45,12 +45,19 @@ export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correc const digitCountValue = new Decimal(10).pow(digitCount); const stepRatio = roughStep.div(digitCountValue); // When an integer and a float multiplied, the accuracy of result may be wrong - const stepRatioScale = digitCount !== 1 ? 0.05 : 0.1; - const amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())) - .add(correctionFactor) - .mul(stepRatioScale); + let stepRatioScale = digitCount !== 1 ? 0.05 : 0.1; - const formatStep = amendStepRatio.mul(digitCountValue); + let formatStep: Decimal; + + do { + const amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())) + .add(correctionFactor) + .mul(stepRatioScale); + + formatStep = amendStepRatio.mul(digitCountValue); + + stepRatioScale -= 0.01; + } while (formatStep.mul(0.7).gt(roughStep) && stepRatioScale > 0.02); return allowDecimals ? new Decimal(formatStep.toNumber()) : new Decimal(Math.ceil(formatStep.toNumber())); }; @@ -165,6 +172,7 @@ export const calculateStep = ( * @param {Number} min, max min: The minimum value, max: The maximum value * @param {Integer} tickCount The count of ticks * @param {Boolean} allowDecimals Allow the ticks to be decimals or not + * @param {StepRatioControl} stepRatioControl The value to control the step of y domain * @return {Array} ticks */ function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowDecimals = true) { diff --git a/test/cartesian/ReferenceArea.spec.tsx b/test/cartesian/ReferenceArea.spec.tsx index 259957e094..338298d46f 100644 --- a/test/cartesian/ReferenceArea.spec.tsx +++ b/test/cartesian/ReferenceArea.spec.tsx @@ -94,7 +94,7 @@ describe('', () => { const allAreas = container.querySelectorAll('.recharts-reference-area-rect'); expect(allAreas).toHaveLength(1); const area = allAreas[0]; - expect(area).toHaveAttribute('d', 'M 20,109.44444444444444 h 960 v 25.555555555555557 h -960 Z'); + expect(area).toHaveAttribute('d', 'M 20,103.05555555555556 h 960 v 31.944444444444443 h -960 Z'); }); test("Don't render any rect in ReferenceArea when no x1, x2, y1 or y2 is set", () => { diff --git a/test/cartesian/ReferenceDot.spec.tsx b/test/cartesian/ReferenceDot.spec.tsx index 489ac9c809..f4bc487b25 100644 --- a/test/cartesian/ReferenceDot.spec.tsx +++ b/test/cartesian/ReferenceDot.spec.tsx @@ -40,7 +40,7 @@ describe('', () => { const label = labels[0]; expect.soft(label.getAttributeNames().sort()).toEqual(['class', 'fill', 'offset', 'text-anchor', 'x', 'y']); expect(label.getAttribute('x')).toEqual('472.72727272727275'); - expect(label.getAttribute('y')).toEqual('86.66666666666667'); + expect(label.getAttribute('y')).toEqual('78.33333333333334'); expect(label.getAttribute('fill')).toEqual('#808080'); expect(label.getAttribute('class')).toEqual('recharts-text recharts-label'); expect(label.getAttribute('text-anchor')).toEqual('middle'); diff --git a/test/cartesian/ReferenceLine/ReferenceLine.spec.tsx b/test/cartesian/ReferenceLine/ReferenceLine.spec.tsx index ca6c2d3b1a..18eff4c716 100644 --- a/test/cartesian/ReferenceLine/ReferenceLine.spec.tsx +++ b/test/cartesian/ReferenceLine/ReferenceLine.spec.tsx @@ -261,8 +261,8 @@ describe('', () => { x1: 65, x2: 1095, y: 20, - y1: -152.5, - y2: -152.5, + y1: -181.66666666666666, + y2: -181.66666666666666, }); }); From 935ad6a7507bba608b9befc1bfe6b64f5ad13348 Mon Sep 17 00:00:00 2001 From: Sen Lin Date: Thu, 2 May 2024 19:35:33 +0100 Subject: [PATCH 4/4] Add constants for getFormatStep --- src/util/scale/getNiceTickValues.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/util/scale/getNiceTickValues.ts b/src/util/scale/getNiceTickValues.ts index e4a76b6ed9..8fcc4cd417 100644 --- a/src/util/scale/getNiceTickValues.ts +++ b/src/util/scale/getNiceTickValues.ts @@ -7,6 +7,10 @@ import Decimal from 'decimal.js-light'; import { compose, range, memoize, map, reverse } from './util/utils'; import { getDigitCount, rangeStep } from './util/arithmetic'; +const MIN_SCALE_RATIO = 0.02; +const SCALE_RATIO_DECREMENT = 0.01; +const STEP_THRESHOLD_FACTOR = 0.7; + /** * Calculate a interval of a minimum value and a maximum value * @@ -49,15 +53,19 @@ export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correc let formatStep: Decimal; - do { + while (stepRatioScale > MIN_SCALE_RATIO) { const amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())) .add(correctionFactor) .mul(stepRatioScale); formatStep = amendStepRatio.mul(digitCountValue); - stepRatioScale -= 0.01; - } while (formatStep.mul(0.7).gt(roughStep) && stepRatioScale > 0.02); + if (formatStep.mul(STEP_THRESHOLD_FACTOR).lt(roughStep)) { + break; + } + + stepRatioScale -= SCALE_RATIO_DECREMENT; + } return allowDecimals ? new Decimal(formatStep.toNumber()) : new Decimal(Math.ceil(formatStep.toNumber())); };