Repository: recharts/recharts (2025-07-06T15:03:00Z)

Issue:
Problems with specifying domain as ['dataMin', 'dataMax'], the ticks are not calculated correctly, allowDecimals does not work, tickCount is not respected.
- [x] I have searched the [issues](https://github.com/recharts/recharts/issues) of this repository and believe that this is not a duplicate.

### Reproduction link
[https://jsfiddle.net/up3a1t92/1/](https://jsfiddle.net/up3a1t92/1/)

### Steps to reproduce
check the YAxis:

domain={['dataMin', 'dataMax']}
allowDecimals={false}
tickCount={9}

it still shows decimals, which is something I can get around with specifying a tickFormatter.
it does not respect tickCount correctly, so, number of ticks change when tickCount is changed, but they are unevenly spaced and never equal to tickCount.

if I change this to:

domain={['dataMin', 'auto']}
allowDecimals={false}
tickCount={9}

then ticks are calculated correctly, evenly spaced and respect the tickCount, but the y axis does not start from dataMin anymore.

### What is expected?
There should be some way of making tickCount work with provided domain.

### What is actually happening?
When explicitly specifying the domain, ticks are unevenly spaced and do not respect tickCount

| Environment | Info |
|---|---|
| Recharts | v1.8.5 |
| React | 16 |
| System | MacOS |
| Browser | Chrome |


<!-- generated by reccharts-issue-helper. DO NOT REMOVE -->


Discussion:
Experiencing the exact same issue here. 🙋🏼‍♂️

@ckifer I see that you've marked this as a **high priority issue**, which is cool! 😄 Do you have any rough estimate on when a bugfix for this will be released?

Thanks in advance!
Hi @wencakisa,

I can't really give an estimate for this. All of the current contributors to recharts are working on it as a side project (including myself). We will finish the jest migration and go to issues with the most reactions after that. This one already has some analysis done so it _might_ be an easy win within the next month or two. Can't say much more than that. 

Thanks!
Just to clarify this @wencakisa do you mind making a codesandbox repro of your current issue?
https://stackblitz.com/edit/react-i9cn127d?file=src%2FExample.jsx

This renders 1 less tick than requested, and ignores the decimals (or, more specifically, uses the allowDecimals to calculate the step. So the distances between ticks are always integers. But not the ticks themselves). I will fix.



Patch:
diff --git a/src/util/scale/getNiceTickValues.ts b/src/util/scale/getNiceTickValues.ts
index e4941698b0..ff5a54136b 100644
--- a/src/util/scale/getNiceTickValues.ts
+++ b/src/util/scale/getNiceTickValues.ts
@@ -28,11 +28,10 @@ export const getValidInterval = ([min, max]: [number, number]) => {
 /**
  * Calculate the step which is easy to understand between ticks, like 10, 20, 25
  *
- * @param  {Decimal} roughStep        The rough step calculated by dividing the
- * difference by the tickCount
- * @param  {Boolean} allowDecimals    Allow the ticks to be decimals or not
- * @param  {Integer} correctionFactor A correction factor
- * @return {Decimal} The step which is easy to understand between two ticks
+ * @param  roughStep        The rough step calculated by dividing the difference by the tickCount
+ * @param  allowDecimals    Allow the ticks to be decimals or not
+ * @param  correctionFactor A correction factor
+ * @return The step which is easy to understand between two ticks
  */
 export const getFormatStep = (roughStep: Decimal, allowDecimals: boolean, correctionFactor: number) => {
   if (roughStep.lte(0)) {
@@ -99,20 +98,24 @@ export const getTickOfSingleValue = (value: number, tickCount: number, allowDeci
 /**
  * Calculate the step
  *
- * @param  {Number}  min              The minimum value of an interval
- * @param  {Number}  max              The maximum value of an interval
- * @param  {Integer} tickCount        The count of ticks
- * @param  {Boolean} allowDecimals    Allow the ticks to be decimals or not
- * @param  {Number}  correctionFactor A correction factor
- * @return {Object}  The step, minimum value of ticks, maximum value of ticks
+ * @param  min              The minimum value of an interval
+ * @param  max              The maximum value of an interval
+ * @param  tickCount        The count of ticks
+ * @param  allowDecimals    Allow the ticks to be decimals or not
+ * @param  correctionFactor A correction factor
+ * @return The step, minimum value of ticks, maximum value of ticks
  */
 export const calculateStep = (
   min: number,
   max: number,
   tickCount: number,
   allowDecimals: boolean,
-  correctionFactor = 0,
-): any => {
+  correctionFactor: number = 0,
+): {
+  step: Decimal;
+  tickMin: Decimal;
+  tickMax: Decimal;
+} => {
   // dirty hack (for recharts' test)
   if (!Number.isFinite((max - min) / (tickCount - 1))) {
     return {
@@ -160,12 +163,13 @@ export const calculateStep = (
 };
 
 /**
- * Calculate the ticks of an interval, the count of ticks will be guaranteed
+ * Calculate the ticks of an interval. Ticks can appear outside the interval
+ * if it makes them more rounded and nice.
  *
- * @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
- * @return {Array}   ticks
+ * @param tuple of [min,max] min: The minimum value, max: The maximum value
+ * @param tickCount     The count of ticks
+ * @param allowDecimals Allow the ticks to be decimals or not
+ * @return array of ticks
  */
 function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowDecimals = true): number[] {
   // More than two ticks should be return
@@ -194,13 +198,13 @@ function getNiceTickValuesFn([min, max]: [number, number], tickCount = 6, allowD
 }
 
 /**
- * Calculate the ticks of an interval, the count of ticks won't be guraranteed,
- * but the domain will be guaranteed
+ * Calculate the ticks of an interval.
+ * Ticks will be constrained to the interval [min, max] even if it makes them less rounded and nice.
  *
- * @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
- * @return {Array}   ticks
+ * @param tuple of [min,max] min: The minimum value, max: The maximum value
+ * @param tickCount     The count of ticks. This function may return less than tickCount ticks if the interval is too small.
+ * @param allowDecimals Allow the ticks to be decimals or not
+ * @return array of ticks
  */
 function getTickValuesFixedDomainFn([min, max]: readonly [number, number], tickCount: number, allowDecimals = true) {
   // More than two ticks should be return
@@ -216,10 +220,17 @@ function getTickValuesFixedDomainFn([min, max]: readonly [number, number], tickC
 
   const count = Math.max(tickCount, 2);
   const step = getFormatStep(new Decimal(cormax).sub(cormin).div(count - 1), allowDecimals, 0);
-  const values = [
-    ...rangeStep(new Decimal(cormin), new Decimal(cormax).sub(new Decimal(0.99).mul(step)), step),
-    cormax,
-  ];
+  let values = [...rangeStep(new Decimal(cormin), new Decimal(cormax), step), cormax];
+
+  if (allowDecimals === false) {
+    /*
+     * allowDecimals is false means that we want to have integer ticks.
+     * The step is guaranteed to be an integer in the code above which is great start
+     * but when the first step is not an integer, it will start stepping from a decimal value anyway.
+     * So we need to round all the values to integers after the fact.
+     */
+    values = values.map(value => Math.round(value));
+  }
 
   return min > max ? reverse(values) : values;
 }
diff --git a/src/util/scale/util/arithmetic.ts b/src/util/scale/util/arithmetic.ts
index da176b2a21..88653d4bb5 100644
--- a/src/util/scale/util/arithmetic.ts
+++ b/src/util/scale/util/arithmetic.ts
@@ -1,5 +1,5 @@
 /**
- * @fileOverview 一些公用的运算方法
+ * @fileOverview Some common arithmetic methods
  * @author xile611
  * @date 2015-09-17
  */
@@ -7,13 +7,13 @@ import Decimal from 'decimal.js-light';
 import { curry } from './utils';
 
 /**
- * 获取数值的位数
- * 其中绝对值属于区间[0.1, 1)， 得到的值为0
- * 绝对值属于区间[0.01, 0.1)，得到的位数为 -1
- * 绝对值属于区间[0.001, 0.01)，得到的位数为 -2
+ * Get the digit count of a number.
+ * If the absolute value is in the interval [0.1, 1), the result is 0.
+ * If the absolute value is in the interval [0.01, 0.1), the digit count is -1.
+ * If the absolute value is in the interval [0.001, 0.01), the digit count is -2.
  *
- * @param  {Number} value 数值
- * @return {Integer} 位数
+ * @param  {Number} value The number
+ * @return {Integer}      Digit count
  */
 function getDigitCount(value: number) {
   let result;
@@ -28,18 +28,18 @@ function getDigitCount(value: number) {
 }
 
 /**
- * 按照固定的步长获取[start, end)这个区间的数据
- * 并且需要处理js计算精度的问题
+ * Get the data in the interval [start, end) with a fixed step.
+ * Also handles JS calculation precision issues.
  *
- * @param  {Decimal} start 起点
- * @param  {Decimal} end   终点，不包含该值
- * @param  {Decimal} step  步长
- * @return {Array}         若干数值
+ * @param  {Decimal} start Start point
+ * @param  {Decimal} end   End point, not included
+ * @param  {Decimal} step  Step size
+ * @return {Array}         Array of numbers
  */
-function rangeStep(start: Decimal, end: Decimal, step: Decimal) {
+function rangeStep(start: Decimal, end: Decimal, step: Decimal): Array<number> {
   let num = new Decimal(start);
   let i = 0;
-  const result = [];
+  const result: Array<number> = [];
 
   // magic number to prevent infinite loop
   while (num.lt(end) && i < 100000) {
@@ -51,13 +51,14 @@ function rangeStep(start: Decimal, end: Decimal, step: Decimal) {
 
   return result;
 }
+
 /**
- * 对数值进行线性插值
+ * Linear interpolation of numbers.
  *
- * @param  {Number} a  定义域的极点
- * @param  {Number} b  定义域的极点
- * @param  {Number} t  [0, 1]内的某个值
- * @return {Number}    定义域内的某个值
+ * @param  {Number} a  Endpoint of the domain
+ * @param  {Number} b  Endpoint of the domain
+ * @param  {Number} t  A value in [0, 1]
+ * @return {Number}    A value in the domain
  */
 const interpolateNumber = curry((a: number, b: number, t: number) => {
   const newA = +a;
@@ -65,13 +66,14 @@ const interpolateNumber = curry((a: number, b: number, t: number) => {
 
   return newA + t * (newB - newA);
 });
+
 /**
- * 线性插值的逆运算
+ * Inverse operation of linear interpolation.
  *
- * @param  {Number} a 定义域的极点
- * @param  {Number} b 定义域的极点
- * @param  {Number} x 可以认为是插值后的一个输出值
- * @return {Number}   当x在 a ~ b这个范围内时，返回值属于[0, 1]
+ * @param  {Number} a Endpoint of the domain
+ * @param  {Number} b Endpoint of the domain
+ * @param  {Number} x Can be considered as an output value after interpolation
+ * @return {Number}   When x is in the range a ~ b, the return value is in [0, 1]
  */
 const uninterpolateNumber = curry((a: number, b: number, x: number) => {
   let diff = b - +a;
@@ -80,14 +82,15 @@ const uninterpolateNumber = curry((a: number, b: number, x: number) => {
 
   return (x - a) / diff;
 });
+
 /**
- * 线性插值的逆运算，并且有截断的操作
+ * Inverse operation of linear interpolation with truncation.
  *
- * @param  {Number} a 定义域的极点
- * @param  {Number} b 定义域的极点
- * @param  {Number} x 可以认为是插值后的一个输出值
- * @return {Number}   当x在 a ~ b这个区间内时，返回值属于[0, 1]，
- * 当x不在 a ~ b这个区间时，会截断到 a ~ b 这个区间
+ * @param  {Number} a Endpoint of the domain
+ * @param  {Number} b Endpoint of the domain
+ * @param  {Number} x Can be considered as an output value after interpolation
+ * @return {Number}   When x is in the interval a ~ b, the return value is in [0, 1].
+ *                    When x is not in the interval a ~ b, it will be truncated to the interval a ~ b.
  */
 const uninterpolateTruncation = curry((a: number, b: number, x: number) => {
   let diff = b - +a;


Test patch:
diff --git a/test/cartesian/XAxis.spec.tsx b/test/cartesian/XAxis.spec.tsx
index 631fc707ea..48578bb2a1 100644
--- a/test/cartesian/XAxis.spec.tsx
+++ b/test/cartesian/XAxis.spec.tsx
@@ -748,16 +748,16 @@ describe('<XAxis />', () => {
   });
 
   describe.each(['gap', 'no-gap', { left: 3, right: 5 }] as const)('padding: %s', padding => {
-    /* I am not entirely certain what is the relationship between the tickCount prop, and the actual tick count */
+    /* The tickCount is only a suggestion; if the domain is too small, it will render fewer ticks than requested but will try to keep them nice */
     it.each([
       { providedTickCount: 3, expectedTickCount: 3 },
       { providedTickCount: 5, expectedTickCount: 5 },
-      { providedTickCount: 7, expectedTickCount: 6 },
+      { providedTickCount: 7, expectedTickCount: 7 },
       { providedTickCount: 11, expectedTickCount: 11 },
-      { providedTickCount: 13, expectedTickCount: 12 },
+      { providedTickCount: 13, expectedTickCount: 13 },
       { providedTickCount: 17, expectedTickCount: 17 },
       { providedTickCount: 19, expectedTickCount: 17 },
-      { providedTickCount: 29, expectedTickCount: 27 },
+      { providedTickCount: 29, expectedTickCount: 28 },
     ])(
       'renders $expectedTickCount ticks when tickCount=$providedTickCount',
       ({ providedTickCount, expectedTickCount }) => {
@@ -855,26 +855,11 @@ describe('<XAxis />', () => {
     ]);
     expect(axisDomainSpy).toHaveBeenLastCalledWith([90, 400]);
     expectXAxisTicks(container, [
-      {
-        textContent: '90',
-        x: '68.70967741935483',
-        y: '273',
-      },
-      {
-        textContent: '170',
-        x: '126.14984391259105',
-        y: '273',
-      },
-      {
-        textContent: '250',
-        x: '183.59001040582726',
-        y: '273',
-      },
-      {
-        textContent: '400',
-        x: '291.2903225806452',
-        y: '273',
-      },
+      { textContent: '90', x: '68.70967741935483', y: '273' },
+      { textContent: '170', x: '126.14984391259105', y: '273' },
+      { textContent: '250', x: '183.59001040582726', y: '273' },
+      { textContent: '330', x: '241.0301768990635', y: '273' },
+      { textContent: '400', x: '291.2903225806452', y: '273' },
     ]);
   });
 
@@ -2532,95 +2517,45 @@ describe('<XAxis />', () => {
         const { container, rerender } = render(
           <Component>
             <XAxis dataKey="x" type="number" domain={[-100, 100]} />
-            <Customized component={<ExpectAxisDomain assert={spy} axisType="xAxis" />} />
+            <ExpectAxisDomain assert={spy} axisType="xAxis" />
           </Component>,
         );
         expectXAxisTicks(container, [
-          {
-            textContent: '-100',
-            x: '5',
-            y: '273',
-          },
-          {
-            textContent: '-30',
-            x: '80.18518518518519',
-            y: '273',
-          },
-          {
-            textContent: '40',
-            x: '155.37037037037038',
-            y: '273',
-          },
-          {
-            textContent: '170',
-            x: '295',
-            y: '273',
-          },
+          { textContent: '-100', x: '5', y: '273' },
+          { textContent: '-30', x: '80.18518518518519', y: '273' },
+          { textContent: '40', x: '155.37037037037038', y: '273' },
+          { textContent: '110', x: '230.55555555555557', y: '273' },
+          { textContent: '170', x: '295', y: '273' },
         ]);
         expect(spy).toHaveBeenLastCalledWith([-100, 170]);
 
         rerender(
           <Component>
             <XAxis dataKey="x" type="number" domain={[130, 175]} />
-            <Customized component={<ExpectAxisDomain assert={spy} axisType="xAxis" />} />
+            <ExpectAxisDomain assert={spy} axisType="xAxis" />
           </Component>,
         );
         expectXAxisTicks(container, [
-          {
-            textContent: '90',
-            x: '5',
-            y: '273',
-          },
-          {
-            textContent: '115',
-            x: '90.29411764705883',
-            y: '273',
-          },
-          {
-            textContent: '140',
-            x: '175.58823529411765',
-            y: '273',
-          },
-          {
-            textContent: '175',
-            x: '295',
-            y: '273',
-          },
+          { textContent: '90', x: '5', y: '273' },
+          { textContent: '115', x: '90.29411764705883', y: '273' },
+          { textContent: '140', x: '175.58823529411765', y: '273' },
+          { textContent: '165', x: '260.88235294117646', y: '273' },
+          { textContent: '175', x: '295', y: '273' },
         ]);
         expect(spy).toHaveBeenLastCalledWith([90, 175]);
 
         rerender(
           <Component>
             <XAxis dataKey="x" type="number" domain={[130, 150]} />
-            <Customized component={<ExpectAxisDomain assert={spy} axisType="xAxis" />} />
+            <ExpectAxisDomain assert={spy} axisType="xAxis" />
           </Component>,
         );
         expectXAxisTicks(container, [
-          {
-            textContent: '90',
-            x: '5',
-            y: '273',
-          },
-          {
-            textContent: '110',
-            x: '77.5',
-            y: '273',
-          },
-          {
-            textContent: '130',
-            x: '150',
-            y: '273',
-          },
-          {
-            textContent: '150',
-            x: '222.5',
-            y: '273',
-          },
-          {
-            textContent: '170',
-            x: '295',
-            y: '273',
-          },
+          { textContent: '90', x: '5', y: '273' },
+          { textContent: '110', x: '77.5', y: '273' },
+          { textContent: '130', x: '150', y: '273' },
+          { textContent: '150', x: '222.5', y: '273' },
+          { textContent: '170', x: '295', y: '273' },
         ]);
         expect(spy).toHaveBeenLastCalledWith([90, 170]);
       });
@@ -3301,26 +3236,11 @@ describe('<XAxis />', () => {
         const allXAxes = container.querySelectorAll('.recharts-xAxis');
         expect(allXAxes).toHaveLength(2);
         expectXAxisTicks(allXAxes[0], [
-          {
-            textContent: '90',
-            x: '5',
-            y: '243',
-          },
-          {
-            textContent: '98',
-            x: '82.33333333333334',
-            y: '243',
-          },
-          {
-            textContent: '106',
-            x: '159.66666666666669',
-            y: '243',
-          },
-          {
-            textContent: '120',
-            x: '295',
-            y: '243',
-          },
+          { textContent: '90', x: '5', y: '243' },
+          { textContent: '98', x: '82.33333333333334', y: '243' },
+          { textContent: '106', x: '159.66666666666669', y: '243' },
+          { textContent: '114', x: '237', y: '243' },
+          { textContent: '120', x: '295', y: '243' },
         ]);
         expectXAxisTicks(allXAxes[1], [
           {
@@ -4910,26 +4830,11 @@ describe('<XAxis />', () => {
         </LineChart>,
       );
       expectXAxisTicks(container, [
-        {
-          textContent: '0',
-          x: '80',
-          y: '273',
-        },
-        {
-          textContent: '650',
-          x: '180.59523809523813',
-          y: '273',
-        },
-        {
-          textContent: '1300',
-          x: '281.1904761904762',
-          y: '273',
-        },
-        {
-          textContent: '2520',
-          x: '470',
-          y: '273',
-        },
+        { textContent: '0', x: '80', y: '273' },
+        { textContent: '650', x: '180.59523809523813', y: '273' },
+        { textContent: '1300', x: '281.1904761904762', y: '273' },
+        { textContent: '1950', x: '381.7857142857143', y: '273' },
+        { textContent: '2520', x: '470', y: '273' },
       ]);
       const expectedSettings: XAxisSettings = {
         angle: 0,
diff --git a/test/cartesian/YAxis/YAxis.spec.tsx b/test/cartesian/YAxis/YAxis.spec.tsx
index d8792690c1..fac0fe878d 100644
--- a/test/cartesian/YAxis/YAxis.spec.tsx
+++ b/test/cartesian/YAxis/YAxis.spec.tsx
@@ -127,8 +127,8 @@ describe('<YAxis />', () => {
   const casesThatShowTicks: [number, AxisDomain, string][] = [
     // [ticksLength, domain, textContentOfTickElement]
     [5, [0, 10000], '10000'],
-    [4, [0, 'dataMax'], '9800'],
-    [4, [0, 'dataMax - 100'], '9800'],
+    [5, [0, 'dataMax'], '9800'],
+    [5, [0, 'dataMax - 100'], '9800'],
   ];
 
   test.each(casesThatShowTicks)('Should render %s ticks when domain={%s}', (length, domain, textContent) => {
diff --git a/test/cartesian/YAxis/YAxis.ticks.spec.tsx b/test/cartesian/YAxis/YAxis.ticks.spec.tsx
new file mode 100644
index 0000000000..de640cbfe3
--- /dev/null
+++ b/test/cartesian/YAxis/YAxis.ticks.spec.tsx
@@ -0,0 +1,269 @@
+import React from 'react';
+import { describe, it, expect } from 'vitest';
+import { createSelectorTestCase } from '../../helper/createSelectorTestCase';
+import { PageData } from '../../_data';
+import { Line, LineChart, XAxis, YAxis } from '../../../src';
+import { expectYAxisTicks } from '../../helper/expectAxisTicks';
+import {
+  AxisWithTicksSettings,
+  selectAxisScale,
+  selectAxisSettings,
+  selectNiceTicks,
+  selectTicksOfAxis,
+} from '../../../src/state/selectors/axisSelectors';
+
+const dataWithDecimals = PageData.map(item => ({
+  ...item,
+  pv: item.pv / 100,
+}));
+
+const defaultExpectedYAxisSettings: AxisWithTicksSettings = {
+  allowDataOverflow: false,
+  allowDecimals: true,
+  allowDuplicatedCategory: true,
+  angle: 0,
+  dataKey: undefined,
+  domain: undefined,
+  hide: false,
+  id: 0,
+  includeHidden: false,
+  interval: 'preserveEnd',
+  minTickGap: 5,
+  mirror: false,
+  name: undefined,
+  orientation: 'left',
+  padding: {
+    bottom: 0,
+    top: 0,
+  },
+  reversed: false,
+  scale: 'auto',
+  tick: true,
+  tickCount: 5,
+  tickFormatter: undefined,
+  ticks: undefined,
+  type: 'number',
+  unit: undefined,
+  width: 60,
+};
+
+describe('YAxis ticks', () => {
+  describe('with default props', () => {
+    const renderTestCase = createSelectorTestCase(({ children }) => (
+      <LineChart width={500} height={300} data={dataWithDecimals}>
+        <XAxis dataKey="name" />
+        <YAxis />
+        <Line dataKey="pv" />
+        {children}
+      </LineChart>
+    ));
+
+    it('should select YAxis settings', () => {
+      const { spy } = renderTestCase(state => selectAxisSettings(state, 'yAxis', 0));
+      expect(spy).toHaveBeenLastCalledWith(defaultExpectedYAxisSettings);
+    });
+
+    it('should select scale', () => {
+      const { spy } = renderTestCase(state => selectAxisScale(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith(
+        expect.toBeRechartsScale({
+          domain: [0, 100],
+          range: [265, 5],
+        }),
+      );
+    });
+
+    it('should select niceTicks', () => {
+      const { spy } = renderTestCase(state => selectNiceTicks(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([0, 25, 50, 75, 100]);
+    });
+
+    it('should select ticks', () => {
+      const { spy } = renderTestCase(state => selectTicksOfAxis(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([
+        { coordinate: 265, index: 0, offset: 0, value: 0 },
+        { coordinate: 200, index: 1, offset: 0, value: 25 },
+        { coordinate: 135, index: 2, offset: 0, value: 50 },
+        { coordinate: 70, index: 3, offset: 0, value: 75 },
+        { coordinate: 5, index: 4, offset: 0, value: 100 },
+      ]);
+    });
+
+    it('should render 5 ticks', () => {
+      const { container } = renderTestCase();
+      expectYAxisTicks(container, [
+        { textContent: '0', x: '57', y: '265' },
+        { textContent: '25', x: '57', y: '200' },
+        { textContent: '50', x: '57', y: '135' },
+        { textContent: '75', x: '57', y: '70' },
+        { textContent: '100', x: '57', y: '5' },
+      ]);
+    });
+  });
+
+  describe('with domain=[dataMin, dataMax]', () => {
+    const renderTestCase = createSelectorTestCase(({ children }) => (
+      <LineChart width={500} height={300} data={dataWithDecimals}>
+        <XAxis dataKey="name" />
+        <YAxis domain={['dataMin', 'dataMax']} />
+        <Line dataKey="pv" />
+        {children}
+      </LineChart>
+    ));
+
+    it('should select YAxis settings all the same except for the domain', () => {
+      const { spy } = renderTestCase(state => selectAxisSettings(state, 'yAxis', 0));
+      expect(spy).toHaveBeenLastCalledWith({
+        ...defaultExpectedYAxisSettings,
+        domain: ['dataMin', 'dataMax'],
+      });
+    });
+
+    it('should select scale', () => {
+      const { spy } = renderTestCase(state => selectAxisScale(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith(
+        expect.toBeRechartsScale({
+          domain: [13.98, 98],
+          range: [265, 5],
+        }),
+      );
+    });
+
+    it('should select niceTicks', () => {
+      const { spy } = renderTestCase(state => selectNiceTicks(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([13.98, 38.98, 63.98, 88.98, 98]);
+    });
+
+    it('should select ticks', () => {
+      const { spy } = renderTestCase(state => selectTicksOfAxis(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([
+        { coordinate: 265, index: 0, offset: 0, value: 13.98 },
+        { coordinate: 187.6374672696977, index: 1, offset: 0, value: 38.98 },
+        { coordinate: 110.27493453939539, index: 2, offset: 0, value: 63.98 },
+        { coordinate: 32.91240180909307, index: 3, offset: 0, value: 88.98 },
+        { coordinate: 5, index: 4, offset: 0, value: 98 },
+      ]);
+    });
+
+    it('should render 5 ticks', () => {
+      const { container } = renderTestCase();
+      expectYAxisTicks(container, [
+        { textContent: '13.98', x: '57', y: '265' },
+        { textContent: '38.98', x: '57', y: '187.6374672696977' },
+        { textContent: '63.98', x: '57', y: '110.27493453939539' },
+        { textContent: '88.98', x: '57', y: '32.91240180909307' },
+        { textContent: '98', x: '57', y: '5' },
+      ]);
+    });
+  });
+
+  describe('with domain=[dataMin, dataMax] and tickCount=3 and allowDecimals=false', () => {
+    const renderTestCase = createSelectorTestCase(({ children }) => (
+      <LineChart width={500} height={300} data={dataWithDecimals}>
+        <XAxis dataKey="name" />
+        <YAxis domain={['dataMin', 'dataMax']} tickCount={3} allowDecimals={false} />
+        <Line dataKey="pv" />
+        {children}
+      </LineChart>
+    ));
+
+    it('should select YAxis settings all the same except for the domain, and tick count', () => {
+      const { spy } = renderTestCase(state => selectAxisSettings(state, 'yAxis', 0));
+      expect(spy).toHaveBeenLastCalledWith({
+        ...defaultExpectedYAxisSettings,
+        domain: ['dataMin', 'dataMax'],
+        tickCount: 3,
+        allowDecimals: false,
+      });
+    });
+
+    it('should select scale', () => {
+      const { spy } = renderTestCase(state => selectAxisScale(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith(
+        expect.toBeRechartsScale({
+          domain: [13.98, 98],
+          range: [265, 5],
+        }),
+      );
+    });
+
+    it('should select niceTicks', () => {
+      const { spy } = renderTestCase(state => selectNiceTicks(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([14, 59, 98]);
+    });
+
+    it('should select ticks', () => {
+      const { spy } = renderTestCase(state => selectTicksOfAxis(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([
+        { coordinate: 264.93810997381576, index: 0, offset: 0, value: 14 },
+        { coordinate: 125.68555105927162, index: 1, offset: 0, value: 59 },
+        { coordinate: 5, index: 2, offset: 0, value: 98 },
+      ]);
+    });
+
+    it('should render 3 ticks', () => {
+      const { container } = renderTestCase();
+      expectYAxisTicks(container, [
+        { textContent: '14', x: '57', y: '264.93810997381576' },
+        { textContent: '59', x: '57', y: '125.68555105927162' },
+        { textContent: '98', x: '57', y: '5' },
+      ]);
+    });
+  });
+
+  describe('with domain=[0, 200] and tickCount=4 and allowDecimals=false', () => {
+    const renderTestCase = createSelectorTestCase(({ children }) => (
+      <LineChart width={500} height={300} data={dataWithDecimals}>
+        <XAxis dataKey="name" />
+        <YAxis domain={[0, 200]} tickCount={4} allowDecimals={false} />
+        <Line dataKey="pv" />
+        {children}
+      </LineChart>
+    ));
+
+    it('should select YAxis settings all the same except for the domain, and tick count', () => {
+      const { spy } = renderTestCase(state => selectAxisSettings(state, 'yAxis', 0));
+      expect(spy).toHaveBeenLastCalledWith({
+        ...defaultExpectedYAxisSettings,
+        domain: [0, 200],
+        tickCount: 4,
+        allowDecimals: false,
+      });
+    });
+
+    it('should select scale', () => {
+      const { spy } = renderTestCase(state => selectAxisScale(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith(
+        expect.toBeRechartsScale({
+          domain: [0, 200],
+          range: [265, 5],
+        }),
+      );
+    });
+
+    it('should select niceTicks', () => {
+      const { spy } = renderTestCase(state => selectNiceTicks(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([0, 70, 140, 200]);
+    });
+
+    it('should select ticks', () => {
+      const { spy } = renderTestCase(state => selectTicksOfAxis(state, 'yAxis', 0, false));
+      expect(spy).toHaveBeenLastCalledWith([
+        { coordinate: 265, index: 0, offset: 0, value: 0 },
+        { coordinate: 174, index: 1, offset: 0, value: 70 },
+        { coordinate: 83.00000000000001, index: 2, offset: 0, value: 140 },
+        { coordinate: 5, index: 3, offset: 0, value: 200 },
+      ]);
+    });
+
+    it('should render 4 ticks', () => {
+      const { container } = renderTestCase();
+      expectYAxisTicks(container, [
+        { textContent: '0', x: '57', y: '265' },
+        { textContent: '70', x: '57', y: '174' },
+        { textContent: '140', x: '57', y: '83.00000000000001' },
+        { textContent: '200', x: '57', y: '5' },
+      ]);
+    });
+  });
+});
diff --git a/test/util/scale/getTickValuesFixedDomain.test.ts b/test/util/scale/getTickValuesFixedDomain.test.ts
index 39bc125731..f94777d125 100644
--- a/test/util/scale/getTickValuesFixedDomain.test.ts
+++ b/test/util/scale/getTickValuesFixedDomain.test.ts
@@ -1,59 +1,84 @@
 import { getTickValuesFixedDomain } from '../../../src/util/scale/getNiceTickValues';
 
-test('of unequal values of positive integer', () => {
+test('returns fewer ticks than requested for close values', () => {
   const [min, max, count] = [1, 7, 5];
+  // we asked for 5 ticks, but the range is too small to accommodate that many
   const scales = getTickValuesFixedDomain([min, max], count);
 
   expect(scales).toEqual([1, 3, 5, 7]);
+  expect(scales.length).toBeLessThan(count);
+});
+
+test('of unequal values of positive integer', () => {
+  const [min, max, count] = [1, 70, 5];
+  const scales = getTickValuesFixedDomain([min, max], count);
+
+  expect(scales).toEqual([1, 21, 41, 61, 70]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of negative to positive integer & has odd ticks', () => {
-  const [min, max, count] = [-5, 95, 7];
+  const [min, max, count] = [-50, 95, 7];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([-5, 15, 35, 55, 75, 95]);
+  expect(scales).toEqual([-50, -25, +0, 25, 50, 75, 95]);
+  expect(scales.length).toBe(count);
 });
 
-test('of unequal values of negative integerr', () => {
-  const [min, max, count] = [-105, -25, 6];
+test('of unequal values of negative integer', () => {
+  const [min, max, count] = [-205, -25, 6];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([-105, -85, -65, -45, -25]);
+  expect(scales).toEqual([-205, -165, -125, -85, -45, -25]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of min is bigger than max & has odd ticks', () => {
   const [min, max, count] = [67, 5, 5];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([67, 45, 25, 5]);
+  expect(scales).toEqual([67, 65, 45, 25, 5]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of min is bigger than max & has even ticks', () => {
   const [min, max, count] = [67, 5, 4];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([67, 30, 5]);
+  expect(scales).toEqual([67, 55, 30, 5]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of float [-4.10389, 0.59414, 7]', () => {
   const [min, max, count] = [-4.10389, 0.59414, 7];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([-4.10389, -3.30389, -2.50389, -1.70389, -0.90389, 0.59414]);
+  expect(scales).toEqual([-4.10389, -3.30389, -2.50389, -1.70389, -0.90389, -0.10389, 0.59414]);
+  expect(scales.length).toBe(count);
+});
+
+test('when allowDecimals is false of unequal values of float [-0.10389, 0.59414, 7] should not produce duplicates in the output', () => {
+  const [min, max, count] = [-0.10389, 0.59414, 7];
+  const scales = getTickValuesFixedDomain([min, max], count, false);
+
+  expect(scales).toEqual([-0, 1]);
+  expect(scales.length).toBe(2); // one less than count because can't fit 7 ticks in the domain
 });
 
 test('of unequal values of float [-4.10389, 0.59414, 7] not allow decimals', () => {
   const [min, max, count] = [-4.10389, 0.59414, 7];
   const scales = getTickValuesFixedDomain([min, max], count, false);
 
-  expect(scales).toEqual([-4.10389, -3.10389, -2.10389, -1.10389, 0.59414]);
+  expect(scales).toEqual([-4, -3, -2, -1, -0, 1]);
+  expect(scales.length).toBe(6); // one less than count because can't fit 7 ticks in the domain
 });
 
 test('of unequal values of integers [0, 14, 5]', () => {
   const [min, max, count] = [0, 14, 5];
   const scales = getTickValuesFixedDomain([min, max], count);
 
-  expect(scales).toEqual([0, 4, 8, 14]);
+  expect(scales).toEqual([0, 4, 8, 12, 14]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of integers [0, 1e+100, 6]', () => {
@@ -61,6 +86,7 @@ test('of unequal values of integers [0, 1e+100, 6]', () => {
   const scales = getTickValuesFixedDomain([min, max], count);
 
   expect(scales).toEqual([0, 2e99, 4e99, 6e99, 8e99, 1e100]);
+  expect(scales.length).toBe(count);
 });
 
 test('of unequal values of Infinity values [-Infinity, Infinity, 5]', () => {