Skip to content

Improve tick values by snapping to nice numbers {1, 2, 2.5, 5}#7009

Merged
PavelVanecek merged 6 commits intorecharts:mainfrom
MaxGhenis:improve-nice-tick-values
Feb 21, 2026
Merged

Improve tick values by snapping to nice numbers {1, 2, 2.5, 5}#7009
PavelVanecek merged 6 commits intorecharts:mainfrom
MaxGhenis:improve-nice-tick-values

Conversation

@MaxGhenis
Copy link
Contributor

@MaxGhenis MaxGhenis commented Feb 14, 2026

Summary

Replaces the getFormatStep rounding algorithm (0.05/0.1 granularity) with D3-style nice number snapping to {1, 2, 2.5, 5} multiples at each order of magnitude. This produces human-friendly tick values.

Before → After examples

Domain Count Before After
[0, 14] 5 [0, 4, 8, 12, 16] [0, 5, 10, 15, 20]
[39.9, 42.5] 5 [39.9, 40.6, 41.3, 42, 42.7] [39, 40, 41, 42, 43]
[-4.1, 0.6] 7 [-4.25, -3.4, -2.55, -1.7, -0.85, 0, 0.85] [-5, -4, -3, -2, -1, 0, 1]
[0, 1.3e-5] 5 [0, 3.5e-6, 7e-6, 1.05e-5, 1.4e-5] [0, 5e-6, 1e-5, 1.5e-5, 2e-5]

How it works

The previous algorithm divided the step ratio by 0.05 or 0.1, ceiling-rounded, and multiplied back — producing steps like 3.5, 0.35, or 0.7 that don't feel "natural" to humans.

The new algorithm normalizes the rough step to [1, 10) and snaps to the nearest ceiling value in {1, 2, 2.5, 5}, then multiplies by the appropriate power of 10. The correctionFactor mechanism (used by calculateStep when it needs a larger step) now walks through this sequence instead of incrementing by 0.05/0.1.

Additional fix

Renamed getTickValuesFixedDomain.test.ts.spec.ts so vitest actually discovers and runs these tests. The vitest config uses include: test/**/*.spec.ts?(x), so .test.ts files were silently skipped.

Fixes #7008

Test plan

  • Updated all getFormatStep test expectations for nicer values
  • Updated getNiceTickValues test expectations (5 tests improved)
  • Updated getTickValuesFixedDomain test expectations (3 tests produce nicer but slightly fewer ticks, consistent with existing docstring: "may return less than tickCount ticks")
  • All 87 scale tests pass
  • No TypeScript errors

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added opt-in "nice ticks" toggle to axis components (optional per-axis); when enabled, tick intervals snap to rounder, human-friendly values.
  • Tests
    • Updated and extended axis/tick tests to cover the new nice-ticks behavior and default-disabled expectations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an opt-in "nice ticks" stepping algorithm and plumbing: new getFormatStepNice and StepFunction type, optional useNiceTicks flag for tick generation APIs, and new niceTicks/useNiceTicks props and default settings threaded through axis components and selectors (default false).

Changes

Cohort / File(s) Summary
Core Tick Generation
src/util/scale/getNiceTickValues.ts
Adds getFormatStepNice and StepFunction type; extends calculateStep to accept a stepFn; adds useNiceTicks flag to getNiceTickValues and getTickValuesFixedDomain and routes chosen stepFn through calculations.
Axis Components
src/cartesian/XAxis.tsx, src/cartesian/YAxis.tsx, src/polar/PolarAngleAxis.tsx, src/polar/PolarRadiusAxis.tsx
Introduces useNiceTicks/niceTicks prop to axis component interfaces, updates default props, and propagates the flag into SetAxisSettings dispatches so downstream rendering/selection receives it.
State & Selectors
src/state/cartesianAxisSlice.ts, src/state/selectors/axisSelectors.ts, src/state/selectors/polarAxisSelectors.ts
Adds niceTicks to axis/tick settings defaults (implicit axes) and updates selector calls to pass the flag into tick-generation functions.
Tests — Tick Generation
test/util/scale/getNiceTickValues.spec.ts
Adds comprehensive tests for getFormatStepNice, plus getNiceTickValues and getTickValuesFixedDomain scenarios with useNiceTicks=true (edge cases, reversed/small/large ranges).
Tests — Axis State & Components
test/cartesian/..., test/polar/..., test/state/selectors/..., test/component/...
Updates many test expectations to include niceTicks: false/useNiceTicks: false in axis setting objects across cartesian, polar, radar, radial bar, tooltip, and pie test suites.
Misc
package.json, manifest/test metadata
Test and manifest updates to reflect added exports and API changes.

Sequence Diagram(s)

(Skipped — changes are internal algorithm plus prop plumbing; no multi-component sequential flow that benefits from a diagram.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • ckifer
  • PavelVanecek
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improve tick values by snapping to nice numbers {1, 2, 2.5, 5}' clearly and concisely describes the main change in the pull request — implementing D3-style nice number snapping for tick generation.
Description check ✅ Passed The PR description provides comprehensive details: summary of the change, before/after examples, technical explanation of the algorithm, additional fix notes, and a complete test plan with verification steps.
Linked Issues check ✅ Passed The PR successfully addresses all objectives from issue #7008: implements D3-style nice number snapping to {1, 2, 2.5, 5} multiples [#7008], makes it opt-in via niceTicks boolean prop, and includes comprehensive test coverage for both legacy and new behavior.
Out of Scope Changes check ✅ Passed All changes directly support the stated objective of implementing nice tick snapping: core algorithm in getNiceTickValues.ts, prop propagation through axis components (XAxis, YAxis, PolarAngleAxis, PolarRadiusAxis), state/selector updates for niceTicks property, and test updates reflecting the new behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
test/chart/BarChart.spec.tsx (1)

2487-2492: ⚠️ Potential issue | 🔴 Critical

Bug: y attribute not updated to match the new d path.

The d attribute starts at y=27.79 and height was updated to 11.21, but y on line 2492 still reads '24.9875' (the old value). This mismatch will cause the test assertion to fail.

Proposed fix
         d: 'M 12.2571,27.79 h 90 v 11.21 h -90 Z',
         height: '11.21',
         radius: '0',
         width: '90',
         x: '12.2571',
-        y: '24.9875',
+        y: '27.79',
test/state/selectors/radarSelectors.spec.tsx (1)

407-455: ⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this suite.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

test/state/selectors/scatterSelectors.spec.tsx (1)

371-416: ⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this selector test.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

test/cartesian/Line.spec.tsx (1)

32-39: ⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this suite.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

test/cartesian/ReferenceLine/ReferenceLine.spec.tsx (1)

293-299: ⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this suite.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

test/polar/Radar.animation.spec.tsx (1)

203-211: ⚠️ Potential issue | 🟡 Minor

Enable fake timers for animation tests.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

test/cartesian/Scatter.spec.tsx (1)

34-42: ⚠️ Potential issue | 🔴 Critical

cy and transform.y mismatch in first scatter point expectation

The first scatter point has cy: '470.5' (line 37) but transform: 'translate(54, 467.77777777777777)' (line 40). Since the Symbols component renders both from the same coordinate value, they must match. All other scatter points in this expectation correctly have consistent values. Update the transform to 'translate(54, 470.5)' to align with the cy value.

test/chart/ScatterChart.spec.tsx (4)

874-882: ⚠️ Potential issue | 🔴 Critical

Bug: cy value doesn't match the transform y-coordinate.

Line 877 sets cy: '135' but line 880 still has transform: 'translate(120, 125)'. The transform y=125 doesn't match cy='135'. Same mismatch pattern as the previous scatter point issue.

🐛 Proposed fix — align cy and transform
-          cy: '135',
+          cy: '125',

or

-          transform: 'translate(120, 125)',
+          transform: 'translate(120, 135)',

1253-1261: ⚠️ Potential issue | 🔴 Critical

Bug: cx value doesn't match the transform x-coordinate.

Line 1255 sets cx: '160' but line 1259 still has transform: 'translate(186.66666666666669, 185)'. The transform x=186.66666666666669 doesn't match cx='160'.

🐛 Proposed fix — align cx and transform
-        cx: '160',
+        cx: '186.66666666666669',

or

-        transform: 'translate(186.66666666666669, 185)',
+        transform: 'translate(160, 185)',

796-803: ⚠️ Potential issue | 🔴 Critical

Bug: duplicate index: 2 — likely should remain index: 3.

Lines 798 and 799 both have index: 2, creating the sequence 0, 1, 2, 2, 4, 5, 6 (missing index 3). Compare with the similar test at lines 937–943 which has sequential indices 0, 1, 2, 3, 4. Line 799 was likely meant to keep index: 3.

🐛 Proposed fix
-        { index: 2, coordinate: 85, value: 300, offset: 0 },
+        { index: 3, coordinate: 85, value: 300, offset: 0 },

308-316: ⚠️ Potential issue | 🔴 Critical

Three cx/cy–transform coordinate mismatches in test expectations.

The expectScatterPoints helper compares SVG element attributes directly using toEqual(). In the current test expectations:

  • Line 310: cx: '91.5' but transform: 'translate(109.16666666666666, 338)' — cx doesn't match the x in transform
  • Line 877: cy: '135' but transform: 'translate(120, 125)' — cy doesn't match the y in transform
  • Line 1255: cx: '160' but transform: 'translate(186.66666666666669, 185)' — cx doesn't match the x in transform

In all other scatter point expectations throughout the file, cx matches transform x and cy matches transform y. These three inconsistencies will cause test failures if the component renders the correct values.

Mismatched expectations requiring fixes

Fix by aligning cx/cy with their corresponding transform coordinates, or vice versa. Choose the value that matches what the component actually renders:

  1. Line 310–314: Align cx: '91.5' with transform or update transform to 'translate(91.5, 338)'
  2. Line 877–880: Align cy: '135' with transform or update transform to 'translate(120, 135)'
  3. Line 1255–1259: Align cx: '160' with transform or update transform to 'translate(160, 185)'
test/cartesian/XAxis/XAxis.padding.spec.tsx (1)

665-691: ⚠️ Potential issue | 🔴 Critical

Fix non-monotonic tick sequence and remove duplicate test case.

The "Render Bars with padding on the right" test (lines 665-691) has incorrect tick expectations producing the sequence 110, 90, 90, 150, 170:

  • Ticks are non-monotonic (110 → 90 is a decrease)
  • Value 90 appears twice at different positions
  • The first tick has y: '253' while all others have y: '273', indicating a layout anomaly

All other padding tests with the same domain [90, 170] correctly produce 90, 110, 130, 150, 170 with consistent y-positioning. Align this test to match the expected pattern.

Additionally, lines 701–702 contain a duplicate test case: both specify { providedTickCount: 9, expectedTickCount: 9 }. Remove the redundant entry.

test/polar/PolarGrid.spec.tsx (1)

854-861: ⚠️ Potential issue | 🔴 Critical

Fix invalid coordinate value with two decimal points in test expectation.

Line 858 contains y2: '250.45894683899488.00000000000003' — a value with two decimal points that is mathematically invalid. In a polar grid, angle 0° and angle 360° represent the same direction and should produce identical line coordinates. The first grid line (angle 0°) correctly has y2: '150', so the last grid line (angle 360°) should also have y2: '150'. The current value appears to be an accidental string concatenation.

-          y2: '250.45894683899488.00000000000003',
+          y2: '150',
test/cartesian/YAxis/YAxis.ticks.spec.tsx (1)

249-271: ⚠️ Potential issue | 🔴 Critical

Verify test expectations for domain [0, 200] with tickCount=4 and allowDecimals=false.

The test expectations appear to be incomplete or incorrect. Investigation reveals:

  1. Algorithm mismatch: With domain [0, 200] fixed and tickCount=4, getTickValuesFixedDomain uses step=100, which produces only 3 ticks: [0, 100, 200]. The test expects 4 ticks [0, 100, 140, 200], but the algorithm cannot generate this set.

  2. Duplicate coordinates in selector test: Lines 259-260 show both value 140 and value 200 mapped to coordinate 5, which is impossible—each value can only have one coordinate.

  3. Linear scale verification failure: For scale domain [0, 200] → range [5, 265]:

    • Value 100 should map to coordinate 135 (correct in selector; rendered shows 174)
    • Value 140 should map to coordinate 83 (selector shows 5; rendered shows 135)
  4. Non-uniform spacing: The niceTicks [0, 100, 140, 200] have gaps of 100, 40, 60—unusual for a tick algorithm. The getTickValuesFixedDomain with step=100 would naturally produce uniform spacing.

These inconsistencies suggest the test expectations were not properly updated during the recent "improved nice tick values" commit (b546890), which acknowledged ~190 remaining manual test failures.

🤖 Fix all issues with AI agents
In `@test/cartesian/CartesianGrid.spec.tsx`:
- Line 264: Replace the typed last-call assertion on the scale spy by using the
testing helper expectLastCalledWith instead of
expect(scaleSpy).toHaveBeenLastCalledWith; update the assertion that currently
references scaleSpy to call expectLastCalledWith(scaleSpy, [0, 2000]) (or the
project’s helper signature) and ensure the test suite enables fake timers by
invoking vi.useFakeTimers() at the top of the describe block or beforeAll so
Redux autoBatchEnhancer and requestAnimationFrame are stubbed; locate usages of
scaleSpy and the test setup in CartesianGrid.spec.tsx to apply these changes.

In `@test/cartesian/ReferenceArea.spec.tsx`:
- Line 101: The test suite in ReferenceArea.spec.tsx is missing fake timers; add
vi.useFakeTimers() in the suite setup (e.g., in the top-level describe's
beforeAll or beforeEach) and restore with vi.useRealTimers() in
afterAll/afterEach to ensure consistent requestAnimationFrame/autoBatch behavior
(this will affect expectations like the assertion on the 'd' attribute for
area). Ensure you place these calls in the same test file's setup/teardown so
tests that reference the rendered area (the expect(area).toHaveAttribute(...)
assertion) run with fake timers enabled.

In `@test/cartesian/XAxis/XAxis.multiaxis.spec.tsx`:
- Around line 148-162: The test expectation for axis with xAxisId="d" contains
duplicate tick label '1500' at different x positions; update the first test's
expected ticks to match the valid sequence used in the second test ("should
render multiple axes with some ticks mirrored") so both axes produce identical
ticks. Specifically, replace the duplicated '1500' entries in the first test's
expected tick objects with '900' and '1350' (so the sequence becomes 0, 450,
900, 1350, 1800) and ensure the test comparing xAxisId="d" uses the same tick
values as the mirrored-axis test.

In `@test/state/selectors/axisSelectors.spec.tsx`:
- Around line 2173-2179: Add fake timers for this test suite: call
vi.useFakeTimers() in the test file's setup (e.g., in a top-level beforeAll or
beforeEach near the casesThatProduceNiceTicks definition) and restore real
timers in afterAll/afterEach with vi.useRealTimers(); this ensures consistent
RAF/autoBatch behavior required by Redux autoBatchEnhancer when running the
axisSelectors.spec.tsx tests.

In `@test/state/selectors/selectBarRectangles.stackOffset.spec.tsx`:
- Around line 265-282: The last three entries in expectedRectangles3 do not
match the corresponding entries in expectedBars (variables expectedBars and
expectedRectangles3 in
test/state/selectors/selectBarRectangles.stackOffset.spec.tsx) causing an
inconsistency between the selector-based expectations and the render-based
expectations; run the renderTestCase() used by both tests to get the
authoritative rectangle values, compare those outputs to both expectedBars and
expectedRectangles3, and then update the stale set (most likely
expectedRectangles3) so both tests assert the same numeric values for the three
blue bars (heights, widths, x, y) or adjust the selector logic if the selector
is producing incorrect values. Ensure after changing that both the DOM-render
test and the selector test pass.
🧹 Nitpick comments (1)
test/cartesian/XAxis/XAxis.padding.spec.tsx (1)

701-705: Duplicate test case entries.

Lines 701 and 702 are identical: { providedTickCount: 9, expectedTickCount: 9 }. This runs the same test twice with no additional coverage.

♻️ Suggested fix — deduplicate and restore coverage for distinct tick counts
-      { providedTickCount: 9, expectedTickCount: 9 },
-      { providedTickCount: 9, expectedTickCount: 9 },
+      { providedTickCount: 9, expectedTickCount: 9 },
+      { providedTickCount: 11, expectedTickCount: 11 },

or, if the old entries 11→11 and 13→13 were intentionally replaced, keep distinct inputs:

-      { providedTickCount: 9, expectedTickCount: 9 },
-      { providedTickCount: 9, expectedTickCount: 9 },
+      { providedTickCount: 9, expectedTickCount: 9 },
+      { providedTickCount: 13, expectedTickCount: 13 },

);

expect(scaleSpy).toHaveBeenLastCalledWith([0, 1200]);
expect(scaleSpy).toHaveBeenLastCalledWith([0, 2000]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for the scale spy (and enable fake timers).

Guidelines prefer expectLastCalledWith for typed last-call assertions. Also ensure this suite enables vi.useFakeTimers() per the testing guidelines.

🛠️ Suggested change
-    expect(scaleSpy).toHaveBeenLastCalledWith([0, 2000]);
+    expectLastCalledWith(scaleSpy, [0, 2000]);

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion" and "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(scaleSpy).toHaveBeenLastCalledWith([0, 2000]);
expectLastCalledWith(scaleSpy, [0, 2000]);
🤖 Prompt for AI Agents
In `@test/cartesian/CartesianGrid.spec.tsx` at line 264, Replace the typed
last-call assertion on the scale spy by using the testing helper
expectLastCalledWith instead of expect(scaleSpy).toHaveBeenLastCalledWith;
update the assertion that currently references scaleSpy to call
expectLastCalledWith(scaleSpy, [0, 2000]) (or the project’s helper signature)
and ensure the test suite enables fake timers by invoking vi.useFakeTimers() at
the top of the describe block or beforeAll so Redux autoBatchEnhancer and
requestAnimationFrame are stubbed; locate usages of scaleSpy and the test setup
in CartesianGrid.spec.tsx to apply these changes.

expect(allAreas).toHaveLength(1);
const area = allAreas[0];
expect(area).toHaveAttribute('d', 'M 20,109.4444 h 960 v 25.5556 h -960 Z');
expect(area).toHaveAttribute('d', 'M 20,104.3333 h 960 v 30.6667 h -960 Z');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this suite.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
In `@test/cartesian/ReferenceArea.spec.tsx` at line 101, The test suite in
ReferenceArea.spec.tsx is missing fake timers; add vi.useFakeTimers() in the
suite setup (e.g., in the top-level describe's beforeAll or beforeEach) and
restore with vi.useRealTimers() in afterAll/afterEach to ensure consistent
requestAnimationFrame/autoBatch behavior (this will affect expectations like the
assertion on the 'd' attribute for area). Ensure you place these calls in the
same test file's setup/teardown so tests that reference the rendered area (the
expect(area).toHaveAttribute(...) assertion) run with fake timers enabled.

Comment on lines 148 to 162
{
textContent: '900',
textContent: '1500',
x: '350',
y: '67',
},
{
textContent: '1350',
textContent: '1500',
x: '522.5',
y: '67',
},
{
textContent: '1800',
textContent: '2000',
x: '695',
y: '67',
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the test file structure around lines 148-162 and 190-326
cat -n test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | sed -n '140,170p'

Repository: recharts/recharts

Length of output: 832


🏁 Script executed:

# Also check the second test around lines 190-326
cat -n test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | sed -n '180,220p'

Repository: recharts/recharts

Length of output: 1512


🏁 Script executed:

# Look for pageData import
rg -n "pageData" test/cartesian/XAxis/XAxis.multiaxis.spec.tsx -B 2 -A 2

Repository: recharts/recharts

Length of output: 912


🏁 Script executed:

# Find where pageData is defined
fd -g "*.ts" -g "*.tsx" | xargs grep -l "export.*pageData" | head -5

Repository: recharts/recharts

Length of output: 153


🏁 Script executed:

# Find pageData definition
fd pageData storybook/stories/data/ -type f

Repository: recharts/recharts

Length of output: 231


🏁 Script executed:

# Check the second test's expectations for axis d ticks (lines 190-326)
cat -n test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | sed -n '290,330p'

Repository: recharts/recharts

Length of output: 927


🏁 Script executed:

# Get the full second test to understand if axes are identical
cat -n test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | sed -n '190,210p'

Repository: recharts/recharts

Length of output: 1021


🏁 Script executed:

# Find and display pageData definition
find storybook -name "*data*" -type f | head -10

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Check common data file locations
fd -e ts -e tsx . storybook/stories/data/

Repository: recharts/recharts

Length of output: 596


🏁 Script executed:

# Search for pageData export
rg -n "export.*pageData" storybook/

Repository: recharts/recharts

Length of output: 566


🏁 Script executed:

# Read Page.ts to find pageData definition
cat -n storybook/stories/data/Page.ts | head -120

Repository: recharts/recharts

Length of output: 2788


🏁 Script executed:

# Let's verify if this test file was recently modified and what the git history shows
cd test/cartesian/XAxis && git log --oneline XAxis.multiaxis.spec.tsx | head -10

Repository: recharts/recharts

Length of output: 123


🏁 Script executed:

# Check if there's any comment or explanation in the test about why these specific values were chosen
cat -n test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | sed -n '1,50p'

Repository: recharts/recharts

Length of output: 2601


🏁 Script executed:

# Check what changed in the commit "Update test expectations for improved nice tick values"
git show b546890 --stat

Repository: recharts/recharts

Length of output: 50374


🏁 Script executed:

# See the actual diff for this file in that commit
git show b546890 -- test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | head -150

Repository: recharts/recharts

Length of output: 4610


🏁 Script executed:

# Let's check the specific lines in the diff for axis d ticks
git show b546890 -- test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | grep -A 300 "XAxis dataKey=\"amt\"" | head -200

Repository: recharts/recharts

Length of output: 4311


🏁 Script executed:

# Let's verify the second test's axis d expectations to see if it truly shows 0, 450, 900, 1350, 1800
git show b546890 -- test/cartesian/XAxis/XAxis.multiaxis.spec.tsx | grep -A 350 "should render multiple axes with some ticks mirrored" | grep -A 120 "y: '53'"

Repository: recharts/recharts

Length of output: 455


Fix duplicate tick value '1500' and inconsistency between tests.

Lines 149 and 154 both specify textContent: '1500' at different x positions (350 and 522.5). A number axis cannot generate duplicate tick values at different positions—this is a data integrity error in the test expectations.

Additionally, the second test should render multiple axes with some ticks mirrored (lines 190–328) uses the same data and axis configuration for xAxisId="d" (except for the mirror attribute), yet expects different ticks: 0, 450, 900, 1350, 1800 instead of the duplicate-containing 0, 450, 1500, 1500, 2000. The mirror attribute only affects label orientation, not tick generation. Both axes should produce identical tick values.

Correct the first test's axis d tick expectations to match the second test's valid sequence, or verify if a different domain/configuration is intended.

🤖 Prompt for AI Agents
In `@test/cartesian/XAxis/XAxis.multiaxis.spec.tsx` around lines 148 - 162, The
test expectation for axis with xAxisId="d" contains duplicate tick label '1500'
at different x positions; update the first test's expected ticks to match the
valid sequence used in the second test ("should render multiple axes with some
ticks mirrored") so both axes produce identical ticks. Specifically, replace the
duplicated '1500' entries in the first test's expected tick objects with '900'
and '1350' (so the sequence becomes 0, 450, 900, 1350, 1800) and ensure the test
comparing xAxisId="d" uses the same tick values as the mirrored-axis test.

Comment on lines 2173 to 2179
const casesThatProduceNiceTicks: ReadonlyArray<{ domain: AxisDomain; expectedTicks: ReadonlyArray<number> }> = [
// @ts-expect-error typescript is correct here, but we are testing runtime behavior with invalid input
{ domain: undefined, expectedTicks: [0, 100, 200, 300, 400] },
{ domain: ['auto', 'auto'], expectedTicks: [180, 240, 300, 360, 420] },
{ domain: ['auto', 'auto'], expectedTicks: [100, 200, 300, 400, 500] },
{ domain: [-500, 'auto'], expectedTicks: [-500, -250, 0, 250, 500] },
{ domain: ['auto', 3000], expectedTicks: [0, 750, 1500, 2250, 3000] },
{ domain: ['auto', 3000], expectedTicks: [0, 1000, 2000, 2250, 3000] },
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fake timers setup for this suite.

This spec doesn’t enable vi.useFakeTimers(), which the test guidelines require for consistent RAF/autoBatch behavior.

As per coding guidelines, "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
In `@test/state/selectors/axisSelectors.spec.tsx` around lines 2173 - 2179, Add
fake timers for this test suite: call vi.useFakeTimers() in the test file's
setup (e.g., in a top-level beforeAll or beforeEach near the
casesThatProduceNiceTicks definition) and restore real timers in
afterAll/afterEach with vi.useRealTimers(); this ensures consistent
RAF/autoBatch behavior required by Redux autoBatchEnhancer when running the
axisSelectors.spec.tsx tests.

@MaxGhenis MaxGhenis force-pushed the improve-nice-tick-values branch from b546890 to c586d69 Compare February 16, 2026 05:15
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/cartesian/YAxis/YAxis.spec.tsx (1)

85-113: Tick range seems quite loose for the "Area with range" case.

For a data domain of [-3, 16] (range of 19), the new nice-number algorithm produces ticks [-10, 0, 10, 20, 30] (range of 40) — more than 2× the actual data spread. While this is technically valid "nice numbers" behavior, it may result in the chart data being compressed into a small portion of the visible axis. Worth confirming this is acceptable UX for range-type data with small domains.

@PavelVanecek
Copy link
Collaborator

The new ticks look much better (judging from the unit tests alone). I wonder how many people are going to be mad that we're changing their charts. @MaxGhenis is there a way to opt-in to the previous algorithm for people who want to keep it that way? Or perhaps even should we keep the old behaviour the default, leave the new as opt-in and then swap them in the next major version?

@MaxGhenis
Copy link
Contributor Author

Great point on backward compatibility. I'll make the new algorithm opt-in for now — users can enable it via a prop, and we can swap the default in the next major version. Will update the PR.

Instead of replacing the default tick step algorithm, keep the original
getFormatStep as the default and add the new nice number algorithm
(getFormatStepNice) as an opt-in feature. Users can enable it by passing
useNiceTicks={true} on XAxis, YAxis, PolarAngleAxis, or PolarRadiusAxis.

The new algorithm snaps to {1, 2, 2.5, 5} at each order of magnitude,
producing human-friendly intervals like 0, 5, 10, 15, 20 instead of
0, 4, 8, 12, 16. This can be made the default in the next major version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MaxGhenis MaxGhenis force-pushed the improve-nice-tick-values branch from c586d69 to 8565e20 Compare February 16, 2026 15:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
test/cartesian/XAxis/XAxis.vertical.spec.tsx (1)

198-228: ⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for last-call assertions.

Please replace the last-call assertion in this block (and keep the file consistent) with the helper to match the test guidelines.

♻️ Suggested update
-import { describe, expect, it, vi } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
+import { expectLastCalledWith } from '../../helper/expectLastCalledWith';
...
-      expect(axisSettingsSpy).toHaveBeenLastCalledWith(expectedSettings);
+      expectLastCalledWith(axisSettingsSpy, expectedSettings);

As per coding guidelines: Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion.

src/util/scale/getNiceTickValues.ts (2)

152-169: ⚠️ Potential issue | 🟡 Minor

Update calculateStep JSDoc for the new stepFn parameter.

The signature now accepts stepFn, but the JSDoc doesn’t document it. Please add it so omnidoc stays correct.

📚 Suggested doc update
- * `@param`  correctionFactor A correction factor
+ * `@param`  correctionFactor A correction factor
+ * `@param`  stepFn           Function to compute the step size

As per coding guidelines: JSDoc comments and TypeScript definitions in source files must be kept up-to-date as they are used to autogenerate API documentation via 'npm run omnidoc'.


220-276: ⚠️ Potential issue | 🟡 Minor

Document the new useNiceTicks parameter.

Both tick helpers now accept useNiceTicks, but it’s missing from their JSDoc blocks. Please add it for accurate API docs.

📚 Suggested doc update
  * `@param` tickCount     The count of ticks
  * `@param` allowDecimals Allow the ticks to be decimals or not
+ * `@param` useNiceTicks  When true, snap steps to nice numbers
  * `@return` array of ticks
  * `@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
+ * `@param` useNiceTicks  When true, snap steps to nice numbers
  * `@return` array of ticks

As per coding guidelines: JSDoc comments and TypeScript definitions in source files must be kept up-to-date as they are used to autogenerate API documentation via 'npm run omnidoc'.

test/util/scale/getNiceTickValues.spec.ts (1)

1-11: ⚠️ Potential issue | 🟡 Minor

Initialize fake timers for this spec.

This file doesn’t call vi.useFakeTimers(), which is required for tests in this repo.

⏱️ Suggested update
-import { describe } from 'vitest';
+import { afterEach, beforeEach, describe, vi } from 'vitest';
...
+beforeEach(() => {
+  vi.useFakeTimers();
+});
+
+afterEach(() => {
+  vi.useRealTimers();
+});

As per coding guidelines: Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame.

test/chart/RadarChart.spec.tsx (1)

156-173: ⚠️ Potential issue | 🟡 Minor

Switch to expectLastCalledWith for last-call assertions

These assertions still use toHaveBeenLastCalledWith; please use the helper for consistency and better typings.

Suggested update
-    expect(angleAxisSettingsSpy).toHaveBeenLastCalledWith({
+    expectLastCalledWith(angleAxisSettingsSpy, {
       allowDataOverflow: false,
       allowDecimals: false,
       allowDuplicatedCategory: false,
       dataKey: undefined,
       domain: undefined,
       id: 0,
       includeHidden: false,
       name: undefined,
       reversed: false,
       useNiceTicks: false,
       scale: 'auto',
       tick: true,
       tickCount: undefined,
       ticks: undefined,
       type: 'category',
       unit: undefined,
-    });
+    });
-    expect(angleAxisSettingsSpy).toHaveBeenLastCalledWith({
+    expectLastCalledWith(angleAxisSettingsSpy, {
       allowDataOverflow: false,
       allowDecimals: false,
       allowDuplicatedCategory: false,
       dataKey: undefined,
       domain: undefined,
       id: 0,
       includeHidden: false,
       name: undefined,
       reversed: false,
       useNiceTicks: false,
       scale: 'auto',
       tick: true,
       tickCount: undefined,
       ticks: undefined,
       type: 'category',
       unit: undefined,
-    });
+    });

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".

Also applies to: 206-223

test/state/selectors/axisSelectors.spec.tsx (1)

2286-2316: ⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for last-call assertions

This block still uses toHaveBeenLastCalledWith. Please switch to the helper.

Suggested update
-    expect(xAxisSpy).toHaveBeenLastCalledWith({
+    expectLastCalledWith(xAxisSpy, {
       allowDataOverflow: false,
       allowDecimals: true,
       allowDuplicatedCategory: true,
       angle: 0,
       dataKey: undefined,
       domain: undefined,
       height: 30,
       hide: true,
       id: 0,
       includeHidden: false,
       interval: 'preserveEnd',
       minTickGap: 5,
       mirror: false,
       name: undefined,
       orientation: 'bottom',
       padding: {
         left: 0,
         right: 0,
       },
       reversed: false,
       useNiceTicks: false,
       scale: expect.toBeRechartsScale({ domain: [0, 1, 2, 3, 4, 5], range: [5, 95] }),
       tick: true,
       tickCount: 5,
       tickFormatter: undefined,
       ticks: undefined,
       type: 'category',
       unit: undefined,
-    });
-    expect(yAxisSpy).toHaveBeenLastCalledWith(undefined);
+    });
+    expectLastCalledWith(yAxisSpy, undefined);

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".

@MaxGhenis
Copy link
Contributor Author

Updated — the new algorithm is now opt-in via a useNiceTicks boolean prop on XAxis/YAxis/PolarAngleAxis/PolarRadiusAxis (defaults to false). The original getFormatStep is unchanged; getFormatStepNice is added alongside it. 22 new tests cover the opt-in behavior. All 5477 tests pass.

@ckifer
Copy link
Member

ckifer commented Feb 17, 2026

@MaxGhenis useNiceTicks sounds like a hook. Can you just make it niceTicks?

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MaxGhenis
Copy link
Contributor Author

Done — renamed useNiceTicks to niceTicks across all source and test files (24 files). All tests pass.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
test/polar/PolarRadiusAxis.spec.tsx (2)

1189-1207: ⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for the state integration assertion.

The expected axis settings check should use the helper for typing consistency.

Suggested fix
-      expect(radiusAxisSpy).toHaveBeenLastCalledWith(expectedAxis);
+      expectLastCalledWith(radiusAxisSpy, expectedAxis);

As per coding guidelines "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/polar/PolarRadiusAxis.spec.tsx` around lines 1189 - 1207, Replace the
direct Jest assertion that uses toHaveBeenLastCalledWith with the typed helper
expectLastCalledWith: update the test assertion that currently calls
expect(radiusAxisSpy).toHaveBeenLastCalledWith(expectedAxis) to use
expectLastCalledWith(radiusAxisSpy, expectedAxis) so typing and autocompletion
are preserved; this change should be made where radiusAxisSpy and expectedAxis
are referenced in PolarRadiusAxis.spec.tsx and ensure the expectLastCalledWith
helper is imported/available in the test file.

1-4: ⚠️ Potential issue | 🟡 Minor

Add fake timers for this test file.

The suite runs React tests without enabling fake timers, which is required by repo guidelines.

Suggested fix
-import { describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 ...
 describe('<PolarRadiusAxis />', () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+  afterEach(() => {
+    vi.useRealTimers();
+  });

As per coding guidelines "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/polar/PolarRadiusAxis.spec.tsx` around lines 1 - 4, The test suite in
PolarRadiusAxis.spec.tsx must enable fake timers; call vi.useFakeTimers() before
the tests run (e.g., in a top-level beforeAll or immediately after imports) and
restore timers with vi.useRealTimers() in afterAll to satisfy the repo guideline
about Redux autoBatchEnhancer and requestAnimationFrame; update the file where
describe/it/vi are used to ensure vi.useFakeTimers() is active for the suite and
vi.useRealTimers() is called in teardown.
test/polar/PolarAngleAxis/PolarAngleAxis.spec.tsx (2)

858-878: ⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for the spy assertions here.

Line 858 and Line 877 use toHaveBeenLastCalledWith, which the test guidelines discourage.

Suggested fix
-      expect(angleAxisSettingsSpy).toHaveBeenLastCalledWith({
+      expectLastCalledWith(angleAxisSettingsSpy, {
         allowDataOverflow: false,
         allowDecimals: false,
         allowDuplicatedCategory: false,
         dataKey: 'angle',
         domain: [0, 360],
         id: 0,
         includeHidden: false,
         name: undefined,
         reversed: false,
         niceTicks: false,
         scale: 'auto',
         tick: true,
         tickCount: 9,
         ticks: undefined,
         type: 'number',
         unit: undefined,
-      });
+      });
 
-      expect(niceTicksSpy).toHaveBeenLastCalledWith([0, 45, 90, 135, 180, 225, 270, 315, 360]);
+      expectLastCalledWith(niceTicksSpy, [0, 45, 90, 135, 180, 225, 270, 315, 360]);

As per coding guidelines "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/polar/PolarAngleAxis/PolarAngleAxis.spec.tsx` around lines 858 - 878,
Replace the two assertions that call toHaveBeenLastCalledWith on the spies with
the project's expectLastCalledWith helper: locate the assertions against
angleAxisSettingsSpy and niceTicksSpy in PolarAngleAxis.spec.tsx and change them
to use expectLastCalledWith(angleAxisSettingsSpy, { ...expected settings... })
and expectLastCalledWith(niceTicksSpy, [0,45,90,135,180,225,270,315,360])
respectively so the tests follow the guideline and gain the improved
typing/autocomplete behavior.

117-120: ⚠️ Potential issue | 🟡 Minor

Add fake timers for this test file.

The current suite doesn’t enable fake timers, which is required here.

Suggested fix
-import { describe, expect, it, Mock, test, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, Mock, test, vi } from 'vitest';
 ...
 describe('<PolarAngleAxis />', () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+  afterEach(() => {
+    vi.useRealTimers();
+  });

As per coding guidelines "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/polar/PolarAngleAxis/PolarAngleAxis.spec.tsx` around lines 117 - 120,
The test suite in PolarAngleAxis.spec.tsx needs fake timers enabled; add a
vi.useFakeTimers() call at the start of the suite (e.g., inside the outer
describe('<PolarAngleAxis />') or a beforeAll block) and pair it with
vi.useRealTimers() in an afterAll/afterEach cleanup to avoid leaking timers;
update the file where createSelectorTestCase/renderTestCase are defined so the
fake timers are active for tests that depend on requestAnimationFrame/Redux
autoBatchEnhancer.
test/component/Tooltip/Tooltip.payload.spec.tsx (1)

443-446: ⚠️ Potential issue | 🟡 Minor

Add fake timers to the shared test setup.

This file’s tests should enable fake timers per repository guidelines.

Suggested fix
-import { beforeEach, describe, expect, it } from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
 ...
   beforeEach(() => {
+    vi.useFakeTimers();
     mockGetBoundingClientRect({ width: 100, height: 100 });
   });

As per coding guidelines "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/component/Tooltip/Tooltip.payload.spec.tsx` around lines 443 - 446, Add
fake timers to the test suite by invoking vi.useFakeTimers() in the shared setup
and cleaning up with vi.useRealTimers() afterwards; update the describe('Tooltip
payload') setup by adding vi.useFakeTimers() inside the beforeEach handler
(where mockGetBoundingClientRect is called) and add an afterEach that calls
vi.useRealTimers() to restore real timers so tests using
requestAnimationFrame/Redux autoBatchEnhancer behave deterministically.
src/util/scale/getNiceTickValues.ts (1)

152-170: ⚠️ Potential issue | 🟡 Minor

Update JSDoc to cover the new parameters.

stepFn and niceTicks were added but are missing from the function docs.

Suggested fix
 /**
  * Calculate the step
  *
  * `@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
+ * `@param`  stepFn           Step function used to round the rough step
  * `@return` The step, minimum value of ticks, maximum value of ticks
  */
 /**
  * Calculate the ticks of an interval. Ticks can appear outside the interval
  * if it makes them more rounded and nice.
  *
  * `@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
+ * `@param` niceTicks     When true, use nice-number step snapping
  * `@return` array of ticks
  */
 /**
  * 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` 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
+ * `@param` niceTicks     When true, use nice-number step snapping
  * `@return` array of ticks
  */

As per coding guidelines "JSDoc comments and TypeScript definitions in source files must be kept up-to-date as they are used to autogenerate API documentation via 'npm run omnidoc'".

Also applies to: 220-276

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/scale/getNiceTickValues.ts` around lines 152 - 170, The JSDoc for
calculateStep is out of date: add entries for the new parameters stepFn
(StepFunction used to compute/formats steps) and niceTicks (boolean controlling
whether tick extents are adjusted to "nice" values) and update the
`@return/description` if the returned shape changed; locate the calculateStep
function and its signature with parameters min, max, tickCount, allowDecimals,
correctionFactor, stepFn and any use of niceTicks, then add clear JSDoc `@param`
lines for stepFn and niceTicks (briefly describing purpose and types) and ensure
the `@return` block matches the actual returned object shape.
test/cartesian/XAxis/XAxis.vertical.spec.tsx (2)

198-230: ⚠️ Potential issue | 🟡 Minor

Use expectLastCalledWith for the axis settings assertion.

Line 228 still uses toHaveBeenLastCalledWith, which the test guidelines discourage for typing reasons.

Suggested fix
 import { ExpectAxisDomain, expectXAxisTicks } from '../../helper/expectAxisTicks';
+import { expectLastCalledWith } from '../../helper/expectLastCalledWith';
 ...
-      expect(axisSettingsSpy).toHaveBeenLastCalledWith(expectedSettings);
+      expectLastCalledWith(axisSettingsSpy, expectedSettings);

As per coding guidelines "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cartesian/XAxis/XAxis.vertical.spec.tsx` around lines 198 - 230, Replace
the typed assertion that uses Jest's toHaveBeenLastCalledWith with the project
helper expectLastCalledWith: change the call that currently reads
expect(axisSettingsSpy).toHaveBeenLastCalledWith(expectedSettings) to use
expectLastCalledWith(axisSettingsSpy, expectedSettings). Keep the remaining
assertions for itemDataSpy unchanged; if the test helper expectLastCalledWith is
not already imported in the test file, add the import so the helper is
available.

16-27: ⚠️ Potential issue | 🟡 Minor

Add fake timers to the test setup.

This file runs React tests but does not enable fake timers, which is required in this repo’s test guidelines.

Suggested fix
-import { describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 ...
 describe('<XAxis />', () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+  afterEach(() => {
+    vi.useRealTimers();
+  });

As per coding guidelines "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cartesian/XAxis/XAxis.vertical.spec.tsx` around lines 16 - 27, The test
suite for XAxis (describe('<XAxis />') / inner describe('layout=vertical'))
lacks fake timers; add vi.useFakeTimers() at the start of the test setup (e.g.,
in a beforeAll or beforeEach inside the describe('<XAxis />') block) and restore
real timers after tests with vi.useRealTimers() in afterAll/afterEach to satisfy
the repo guideline about Redux autoBatchEnhancer and requestAnimationFrame;
update the test file XAxis.vertical.spec.tsx by locating the top-level
describe('<XAxis />') and adding these setup/teardown calls.
test/util/scale/getNiceTickValues.spec.ts (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Add fake timers for this test suite.

The repo’s test guidelines require fake timers for all tests.

Suggested fix
-import { describe } from 'vitest';
+import { afterEach, beforeEach, describe, vi } from 'vitest';
 ...
+beforeEach(() => {
+  vi.useFakeTimers();
+});
+
+afterEach(() => {
+  vi.useRealTimers();
+});

As per coding guidelines "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/util/scale/getNiceTickValues.spec.ts` around lines 1 - 3, The test suite
in getNiceTickValues.spec.ts must enable fake timers per repo guidelines: inside
the top-level describe block for this file, add setup/teardown to call
vi.useFakeTimers() before tests (e.g., in beforeAll or beforeEach) and
vi.useRealTimers() after tests (afterAll or afterEach) so timers and
requestAnimationFrame are stubbed during tests; locate the describe(...) block
in the file and add the before/after hooks referencing vi.useFakeTimers and
vi.useRealTimers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/util/scale/getNiceTickValues.ts`:
- Around line 271-276: The function getTickValuesFixedDomain currently relies on
return type inference; explicitly annotate its return type (e.g., number[] or
the appropriate TickValue[]/Number[] type used across the scale utilities) to
satisfy the coding guideline. Update the function signature for
getTickValuesFixedDomain to include the explicit return type matching the values
it produces (and import or reference any existing alias like NumberDomain or
TickValue if used elsewhere) so callers and the type checker see the concrete
output type.

---

Outside diff comments:
In `@src/util/scale/getNiceTickValues.ts`:
- Around line 152-170: The JSDoc for calculateStep is out of date: add entries
for the new parameters stepFn (StepFunction used to compute/formats steps) and
niceTicks (boolean controlling whether tick extents are adjusted to "nice"
values) and update the `@return/description` if the returned shape changed; locate
the calculateStep function and its signature with parameters min, max,
tickCount, allowDecimals, correctionFactor, stepFn and any use of niceTicks,
then add clear JSDoc `@param` lines for stepFn and niceTicks (briefly describing
purpose and types) and ensure the `@return` block matches the actual returned
object shape.

In `@test/cartesian/XAxis/XAxis.vertical.spec.tsx`:
- Around line 198-230: Replace the typed assertion that uses Jest's
toHaveBeenLastCalledWith with the project helper expectLastCalledWith: change
the call that currently reads
expect(axisSettingsSpy).toHaveBeenLastCalledWith(expectedSettings) to use
expectLastCalledWith(axisSettingsSpy, expectedSettings). Keep the remaining
assertions for itemDataSpy unchanged; if the test helper expectLastCalledWith is
not already imported in the test file, add the import so the helper is
available.
- Around line 16-27: The test suite for XAxis (describe('<XAxis />') / inner
describe('layout=vertical')) lacks fake timers; add vi.useFakeTimers() at the
start of the test setup (e.g., in a beforeAll or beforeEach inside the
describe('<XAxis />') block) and restore real timers after tests with
vi.useRealTimers() in afterAll/afterEach to satisfy the repo guideline about
Redux autoBatchEnhancer and requestAnimationFrame; update the test file
XAxis.vertical.spec.tsx by locating the top-level describe('<XAxis />') and
adding these setup/teardown calls.

In `@test/component/Tooltip/Tooltip.payload.spec.tsx`:
- Around line 443-446: Add fake timers to the test suite by invoking
vi.useFakeTimers() in the shared setup and cleaning up with vi.useRealTimers()
afterwards; update the describe('Tooltip payload') setup by adding
vi.useFakeTimers() inside the beforeEach handler (where
mockGetBoundingClientRect is called) and add an afterEach that calls
vi.useRealTimers() to restore real timers so tests using
requestAnimationFrame/Redux autoBatchEnhancer behave deterministically.

In `@test/polar/PolarAngleAxis/PolarAngleAxis.spec.tsx`:
- Around line 858-878: Replace the two assertions that call
toHaveBeenLastCalledWith on the spies with the project's expectLastCalledWith
helper: locate the assertions against angleAxisSettingsSpy and niceTicksSpy in
PolarAngleAxis.spec.tsx and change them to use
expectLastCalledWith(angleAxisSettingsSpy, { ...expected settings... }) and
expectLastCalledWith(niceTicksSpy, [0,45,90,135,180,225,270,315,360])
respectively so the tests follow the guideline and gain the improved
typing/autocomplete behavior.
- Around line 117-120: The test suite in PolarAngleAxis.spec.tsx needs fake
timers enabled; add a vi.useFakeTimers() call at the start of the suite (e.g.,
inside the outer describe('<PolarAngleAxis />') or a beforeAll block) and pair
it with vi.useRealTimers() in an afterAll/afterEach cleanup to avoid leaking
timers; update the file where createSelectorTestCase/renderTestCase are defined
so the fake timers are active for tests that depend on
requestAnimationFrame/Redux autoBatchEnhancer.

In `@test/polar/PolarRadiusAxis.spec.tsx`:
- Around line 1189-1207: Replace the direct Jest assertion that uses
toHaveBeenLastCalledWith with the typed helper expectLastCalledWith: update the
test assertion that currently calls
expect(radiusAxisSpy).toHaveBeenLastCalledWith(expectedAxis) to use
expectLastCalledWith(radiusAxisSpy, expectedAxis) so typing and autocompletion
are preserved; this change should be made where radiusAxisSpy and expectedAxis
are referenced in PolarRadiusAxis.spec.tsx and ensure the expectLastCalledWith
helper is imported/available in the test file.
- Around line 1-4: The test suite in PolarRadiusAxis.spec.tsx must enable fake
timers; call vi.useFakeTimers() before the tests run (e.g., in a top-level
beforeAll or immediately after imports) and restore timers with
vi.useRealTimers() in afterAll to satisfy the repo guideline about Redux
autoBatchEnhancer and requestAnimationFrame; update the file where
describe/it/vi are used to ensure vi.useFakeTimers() is active for the suite and
vi.useRealTimers() is called in teardown.

In `@test/util/scale/getNiceTickValues.spec.ts`:
- Around line 1-3: The test suite in getNiceTickValues.spec.ts must enable fake
timers per repo guidelines: inside the top-level describe block for this file,
add setup/teardown to call vi.useFakeTimers() before tests (e.g., in beforeAll
or beforeEach) and vi.useRealTimers() after tests (afterAll or afterEach) so
timers and requestAnimationFrame are stubbed during tests; locate the
describe(...) block in the file and add the before/after hooks referencing
vi.useFakeTimers and vi.useRealTimers.

Comment on lines +271 to +276
export const getTickValuesFixedDomain = (
[min, max]: NumberDomain,
tickCount: number,
allowDecimals = true,
niceTicks = false,
) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an explicit return type for getTickValuesFixedDomain.

This function now has a modified signature but still relies on inference.

Suggested fix
 export const getTickValuesFixedDomain = (
   [min, max]: NumberDomain,
   tickCount: number,
   allowDecimals = true,
   niceTicks = false,
-) => {
+): number[] => {

As per coding guidelines "Type function parameters and return values explicitly. Do not rely on implicit any or inference, except for React components and trivial functions".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getTickValuesFixedDomain = (
[min, max]: NumberDomain,
tickCount: number,
allowDecimals = true,
niceTicks = false,
) => {
export const getTickValuesFixedDomain = (
[min, max]: NumberDomain,
tickCount: number,
allowDecimals = true,
niceTicks = false,
): number[] => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/scale/getNiceTickValues.ts` around lines 271 - 276, The function
getTickValuesFixedDomain currently relies on return type inference; explicitly
annotate its return type (e.g., number[] or the appropriate TickValue[]/Number[]
type used across the scale utilities) to satisfy the coding guideline. Update
the function signature for getTickValuesFixedDomain to include the explicit
return type matching the values it produces (and import or reference any
existing alias like NumberDomain or TickValue if used elsewhere) so callers and
the type checker see the concrete output type.

@ckifer
Copy link
Member

ckifer commented Feb 17, 2026

@MaxGhenis PR is failing the build, looks like a formatting issue

MaxGhenis and others added 4 commits February 18, 2026 13:07
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The omnidoc validator checks componentMetaMap defaultProps objects,
not inline ?? fallbacks. Add niceTicks: false to both polar axis
default props so the @DefaultValue JSDoc matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The omnidoc API doc generator embeds JSDoc text into JSX. Curly braces
{1, 2, 2.5, 5} become JSX expressions triggering TS2695 comma operator
errors. Replace with parentheses (1, 2, 2.5, 5).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

❌ Patch coverage is 95.45455% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.12%. Comparing base (871e872) to head (ebef8e2).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
src/util/scale/getNiceTickValues.ts 93.61% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7009      +/-   ##
==========================================
+ Coverage   90.11%   90.12%   +0.01%     
==========================================
  Files         524      526       +2     
  Lines       39073    39241     +168     
  Branches     5399     5437      +38     
==========================================
+ Hits        35211    35367     +156     
- Misses       3853     3865      +12     
  Partials        9        9              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MaxGhenis
Copy link
Contributor Author

@ckifer Fixed — there were three issues: a prettier formatting violation, missing niceTicks in the polar axis default props (omnidoc validation), and curly braces in JSDoc that became invalid JSX expressions in the auto-generated API docs. All resolved, CI is green now.

Copy link
Collaborator

@PavelVanecek PavelVanecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use it in couple examples on the website, to show off the new prop

@PavelVanecek PavelVanecek merged commit d748806 into recharts:main Feb 21, 2026
41 checks passed
PavelVanecek added a commit that referenced this pull request Feb 21, 2026
…7034)

## Description

Adds the `@since 3.8` JSDoc annotation to the `niceTicks` prop on
`XAxis` and `YAxis`, demonstrates the prop across multiple website
example components, and fixes a pre-existing CI race condition that
caused stale VR artifacts to be deployed.

**JSDoc update (`src/cartesian/XAxis.tsx`, `src/cartesian/YAxis.tsx`):**
- Added `@since 3.8` to the `niceTicks` prop documentation

**Website examples** — added `niceTicks` to the numerical axis/axes in:
- `AreaChart/SimpleAreaChart.tsx` — both `XAxis` and `YAxis`
- `AreaChart/StackedAreaChart.tsx` — both `XAxis` and `YAxis`
- `BarChart/StackedBarChart.tsx` — both `XAxis` and `YAxis`
- `BarChart/MixBarChart.tsx` — `YAxis` (numerical axis)
- `BarChart/BrushBarChart.tsx` — `YAxis` (numerical axis)
- `Legend/LegendEffectOpacity.tsx` — both `XAxis` and `YAxis`
- `Tooltip/CustomContentOfTooltip.tsx` — both `XAxis` and `YAxis`
- `ComposedChart/LineBarAreaComposedChart.tsx` — `YAxis` (numerical
axis)
- `ComposedChart/ScatterAndLineOfBestFit.tsx` — both `XAxis` and `YAxis`
(both are `type="number"`)
- `ComposedChart/VerticalComposedChart.tsx` — `XAxis` only (the
numerical axis in this chart)

**CI workflow fix (`.github/workflows/ci.yml`):**
- Added `concurrency: cancel-in-progress: true` to cancel in-progress
workflow runs when a new commit is pushed to the same branch. This fixes
a pre-existing race condition where two simultaneous CI runs for the
same PR could result in an older run's VR artifact being deployed after
a newer run's, causing the deployed report to show stale results.

## Related Issue

https://github.com/recharts/recharts/issues — `niceTicks` introduced in
PR #7009

## Motivation and Context

`niceTicks` was added in 3.8 but lacked a `@since` annotation and had no
visible usage in the website examples, reducing its discoverability.

The CI race condition occurred because the workflow had no `concurrency`
setting, allowing multiple pipeline runs for the same PR branch to
proceed simultaneously. When commit 1 (affecting 2 charts) deployed its
6-failure VR report to gh-pages, and commit 2 (affecting 7 charts) then
ran its VR tests, the CI log would show 21 failures while the deployed
artifact still showed the stale 6-failure report from the earlier run.

## How Has This Been Tested?

ESLint (`react/jsx-boolean-value: "never"`) passes on all changed files.
Existing example component tests cover the modified files.

## Screenshots (if appropriate):

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] I have added a storybook story or VR test, or extended an existing
story or VR test to show my changes

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Add @SInCE annotation to niceTicks, use it in website
examples</issue_title>
> <issue_description>In #7009
we have added a new prop `niceTicks` to XAxis and YAxis. Two tasks:
> 
> - Add `@since 3.8` annotation to the JSDoc
> - Locate 5 website examples (www/src/docs/exampleComponents) that use
XAxis and 5 that use YAxis, and put niceTicks={true} prop to them so
that we show off the new prop.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #7033

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Co-authored-by: Pavel Vanecek <corkscreewe@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@PavelVanecek PavelVanecek mentioned this pull request Feb 22, 2026
PavelVanecek added a commit that referenced this pull request Mar 2, 2026
…icks (#7042)

Okay so I added couple changes here on my own:

- Changed `nicecTicks` from boolean to enum ('adaptive' | 'snap125') -
let me know if you can think of better names
- Added support for log and symlog scales
- Added guide page and playground
- Fixed a bug where ticks were not aligned in composed chart (this
wasn't intentional but a happy coincidence, I'll take it)
- Updated scale type definition to better match what is actually going
on

See
https://recharts.github.io/recharts/pr-7042/www/en-US/guide/axisTicks/

- [x] Define `NiceTicks` type: `'none' | 'auto' | 'equidistant' |
'nice'` in `cartesianAxisSlice.ts`
- [x] Update `TicksSettings.niceTicks` from `boolean` to `NiceTicks`
- [x] Update `combineNiceTicks` in `axisSelectors.ts` to handle enum
values
- [x] Update defaults from `false` → `'auto'` in all axis settings and
default props files
- [x] Update component prop types in `XAxis.tsx`, `YAxis.tsx`,
`PolarAngleAxis.tsx`, `PolarRadiusAxis.tsx`
- [x] Export `NiceTicks` type from `src/index.ts`
- [x] Update tests that referenced `niceTicks: false` to use `niceTicks:
'auto'`
- [x] Add 3 new tests for `'none'`, `'equidistant'`, and `'nice'` enum
values
- [x] Add `NiceTicks` storybook story for visual documentation
- [x] Add `NiceTicks` to the `exportsThatNeedExamples` list in
`verifyExamples.spec.ts`
- [x] Update `www/` example components using `niceTicks` (boolean
shorthand) → `niceTicks="nice"`
- [x] TypeScript checks pass, all 14000+ tests pass, ESLint clean,
CodeQL clean

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Clarify niceTicks</issue_title>
> <issue_description>Recharts now has three ways of rendering axis
ticks:
> 
> 1. ticks decided by d3, evenly spaced and not rounded
> 2. "nice ticks A" calculated using `getFormatStep` (defined in
`getNiceTickValues.ts`)
> 3. "nice ticks B" calculated using `getFormatStepNice` (unreleased,
scheduled for 3.8)
> 
> Recharts sometimes decides to calculate ticks using variant 1 and
sometimes variant 2, the conditions for selecting are not at all
intuitive (only for numerical axes with linear scales of type "auto"
with tickCount > 0 where at least one of the domain definitions contains
the keyword "auto", how's that sound? see `combineNiceTicks` in
`axisSelectors.ts`).
> 
> Then [just recently](#7009)
we have added variant 3 which is only activated if all of those rules
above are true, AND there is `niceTicks=true` prop set on the axis.
> 
> The initial idea was to replace the "nice ticks A" algorithm
completely, only use the "B" variant and delete "A" variant for good.
Turns out that the old A is _sometimes_ nicer than B! It uses the space
more efficiently, at the expense of making some numbers look less
rounded. The B on the other hand uses nice rounded numbers, but
sometimes wastes space. Depending on user data.
> 
> So let's do this:
> 
> - Keep both A and B nice ticks variants
> - Come up with some meaningful names ("nice nice ticks" and "nice
ticks" don't cut it, A and B is also not great naming)
> - Swap the `niceTicks` prop from boolean to an enum
> - Set the magical selecting algorithm behind "niceTicks=auto" prop
(default)
> - Set the other two A/B using their own props
> - If that prop is set explicitly then Recharts won't apply the magical
selector, it will just calculate nice ticks using the selected algorithm
> - One of the enum options will also be "none" where Recharts will not
attempt any tick niceties
> - Document what is going on</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> <comment_new><author>@PavelVanecek</author><body>
> @ckifer @MaxGhenis thoughts?</body></comment_new>
> <comment_new><author>@ckifer</author><body>
> Sounds good to me, better than what we have going on if we release
as-is for sure</body></comment_new>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #7040

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/recharts/recharts/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Co-authored-by: Pavel Vanecek <corkscreewe@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve default tick generation to produce round numbers

3 participants