Skip to content

Feat: Implement 'equidistantPreserveEnd' interval option for XAxis and YAxis (#6642)#6661

Merged
ckifer merged 5 commits intorecharts:mainfrom
Om-Mishra09:feature/equidistant-preserve-end-6642
Nov 28, 2025
Merged

Feat: Implement 'equidistantPreserveEnd' interval option for XAxis and YAxis (#6642)#6661
ckifer merged 5 commits intorecharts:mainfrom
Om-Mishra09:feature/equidistant-preserve-end-6642

Conversation

@Om-Mishra09
Copy link
Contributor

@Om-Mishra09 Om-Mishra09 commented Nov 20, 2025

Fixes #6642

Description

This PR introduces a new string literal 'equidistantPreserveEnd' for the interval prop on XAxis and YAxis. This option addresses the request in Issue #6642 by ensuring that ticks are displayed equidistantly while guaranteeing the last tick is always visible.

Implementation Details

  • Added a new utility function, getEquidistantPreserveEndTicks, which modifies the collision detection logic to prioritize the display of the final tick (index === ticks.length - 1).
  • Integrated the new function into src/cartesian/getTicks.ts.
  • Updated the AxisInterval type definition in src/util/types.ts.

Motivation and Context

This enhancement provides users with greater control over tick placement, allowing for non-overlapping, equidistant spacing with the certainty that the end of the axis range is marked.

Summary by CodeRabbit

  • New Features

    • Added a new axis interval "equidistantPreserveEnd" that spaces ticks equidistantly while ensuring the end tick is preserved; existing equidistant behavior unchanged.
  • Documentation / Examples

    • Added a Storybook example demonstrating the new interval.
  • Tests

    • Added tests for the new interval, including empty-input behavior and preserving the end tick when spacing would otherwise skip it.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 20, 2025

Walkthrough

Adds a new AxisInterval value equidistantPreserveEnd, implements getEquidistantPreserveEndTicks(...) to compute equidistant ticks while preserving the final tick, integrates it into the tick-resolution switch, and adds tests and a Storybook example for the new behavior.

Changes

Cohort / File(s) Summary
Type Definition
src/util/types.ts
Extend AxisInterval union to include 'equidistantPreserveEnd'.
Tick Calculation
src/cartesian/getEquidistantTicks.ts
Add exported getEquidistantPreserveEndTicks(...) that iterates with a dynamic stepsize, evaluates tick visibility using the end boundary (including an isLastTick check), and preserves the final tick when applicable.
Tick Resolution Integration
src/cartesian/getTicks.ts
Import getEquidistantPreserveEndTicks and dispatch the 'equidistantPreserveEnd' interval to it.
Tests
test/cartesian/getEquidistantTicks.spec.ts
Import the new function; add describe('getEquidistantPreserveEndTicks', ...) tests covering empty input and end-preservation behavior; adjust related existing tests.
Examples / Storybook
storybook/stories/Examples/EquidistantPreserveEnd.stories.tsx
New Storybook story demonstrating a chart using interval="equidistantPreserveEnd".

Sequence Diagram(s)

sequenceDiagram
  participant Caller as getTicks(...)
  participant Resolver as getEquidistantPreserveEndTicks
  participant Helpers as isVisible / getTickSize / getEveryNthWithCondition

  Caller->>Resolver: interval === "equidistantPreserveEnd", pass sign, boundaries, ticks, params
  Resolver->>Helpers: compute initial tick size & minTickGap
  loop iterate ticks with dynamic stepsize
    Resolver->>Helpers: isVisible(tick, { start, end }) [checks isLastTick for end]
    alt visible
      Resolver->>Resolver: select tick, advance by stepsize
    else not visible
      Resolver->>Resolver: reset/adjust stepsize, skip tick
    end
  end
  Resolver->>Helpers: fallback -> getEveryNthWithCondition if needed
  Resolver-->>Caller: return selected ticks (ensuring end tick preserved when applicable)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to the dynamic stepsize loop and the isLastTick / end-boundary checks in src/cartesian/getEquidistantTicks.ts.
  • Verify src/cartesian/getTicks.ts routing and import consistency.
  • Confirm tests in test/cartesian/getEquidistantTicks.spec.ts accurately assert end-preserve behavior and Storybook example compiles.

Suggested reviewers

  • PavelVanecek
  • ckifer

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature being implemented: adding the 'equidistantPreserveEnd' interval option for XAxis and YAxis.
Description check ✅ Passed The PR description covers all key required template sections: it references the related issue (#6642), explains motivation, implementation details, and confirms testing has been added.
Linked Issues check ✅ Passed The implementation fully addresses issue #6642 by adding the requested 'equidistantPreserveEnd' interval option with equidistant tick spacing while preserving the end tick.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the 'equidistantPreserveEnd' feature: new function, type definition, integration point, tests, and storybook story.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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: 1

🧹 Nitpick comments (1)
src/util/types.ts (1)

747-759: Update AxisInterval docs to describe equidistantPreserveEnd semantics

You’ve extended AxisInterval with 'equidistantPreserveEnd', but the preceding comment only documents the equidistantPreserveStart variant. It would be good to explicitly describe how equidistantPreserveEnd behaves (e.g., “selects an N and pattern so that ticks are equidistant and the end tick is preserved”), to keep the type docs self‑contained and discoverable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbbadaf and 3da931a.

📒 Files selected for processing (3)
  • src/cartesian/getEquidistantTicks.ts (1 hunks)
  • src/cartesian/getTicks.ts (2 hunks)
  • src/util/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/cartesian/getEquidistantTicks.ts (4)
src/cartesian/getTicks.ts (1)
  • Sign (9-9)
src/util/types.ts (1)
  • CartesianTickItem (775-779)
src/util/getEveryNthWithCondition.ts (1)
  • getEveryNthWithCondition (12-24)
src/util/TickUtils.ts (1)
  • isVisible (26-42)
src/cartesian/getTicks.ts (1)
src/cartesian/getEquidistantTicks.ts (1)
  • getEquidistantPreserveEndTicks (66-126)
🔇 Additional comments (1)
src/cartesian/getTicks.ts (1)

6-7: Wiring for new equidistantPreserveEnd interval looks consistent

The import of getEquidistantPreserveEndTicks and the new interval === 'equidistantPreserveEnd' branch are correctly aligned with the existing equidistantPreserveStart handling and keep the rest of the control flow unchanged. No issues from the integration side; behavior now depends entirely on the correctness of getEquidistantPreserveEndTicks.

If you have tests around XAxis/YAxis interval="equidistantPreserveStart", consider cloning them for "equidistantPreserveEnd" to validate the new path end‑to‑end.

Also applies to: 171-176

@Om-Mishra09
Copy link
Contributor Author

Thanks for the detailed review and guidance! I've reworked the getEquidistantPreserveEndTicks function to use the end-anchored stride pattern as suggested, ensuring the last tick is correctly preserved. The updated code is pushed to the branch and should be ready for another review.

@PavelVanecek
Copy link
Collaborator

Hi @Om-Mishra09 , thanks for the PR. We're going to need a unit test, and I would like to see a visual regression test too: https://github.com/recharts/recharts/blob/main/DEVELOPING.md

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 have couple of tests. Is the AxisInterval mentioned somewhere in docs, where we should add this new variant? Is it somewhere in storybook?

@codecov
Copy link

codecov bot commented Nov 21, 2025

Codecov Report

❌ Patch coverage is 98.03922% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.03%. Comparing base (bbbadaf) to head (3cc2c7d).
⚠️ Report is 28 commits behind head on main.

Files with missing lines Patch % Lines
src/cartesian/getTicks.ts 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6661      +/-   ##
==========================================
- Coverage   94.04%   94.03%   -0.02%     
==========================================
  Files         497      497              
  Lines       42564    42659      +95     
  Branches     4859     4885      +26     
==========================================
+ Hits        40031    40115      +84     
- Misses       2528     2539      +11     
  Partials        5        5              

☔ 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.

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: 0

🧹 Nitpick comments (1)
test/cartesian/getEquidistantTicks.spec.ts (1)

36-75: Good test coverage for the new function.

The test suite covers the empty array case and multiple end-anchored scenarios. The parameterized tests clearly demonstrate the end-preservation behavior.

Consider adding test cases for:

  • Mixed scenarios where early ticks collide but later ones don't
  • Boundary conditions where minTickGap is non-zero
  • Negative sign parameter to verify reversed direction handling
  • Single tick in array (edge case)

These would further strengthen confidence in the implementation, though current coverage validates core functionality.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe5b408 and 00733da.

📒 Files selected for processing (3)
  • src/util/types.ts (1 hunks)
  • storybook/stories/Examples/EquidistantPreserveEnd.stories.tsx (1 hunks)
  • test/cartesian/getEquidistantTicks.spec.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/cartesian/getEquidistantTicks.spec.ts (2)
src/util/types.ts (2)
  • CartesianTickItem (776-780)
  • TickItem (769-774)
src/cartesian/getEquidistantTicks.ts (1)
  • getEquidistantPreserveEndTicks (66-137)
🔇 Additional comments (3)
src/util/types.ts (1)

752-760: LGTM! Documentation updated as requested.

The JSDoc comment clearly describes the new equidistantPreserveEnd option, and the multi-line union format improves readability. This addresses the previous review comment requesting documentation updates.

storybook/stories/Examples/EquidistantPreserveEnd.stories.tsx (1)

1-33: Verify visual regression test coverage.

The Storybook example effectively demonstrates the new equidistantPreserveEnd interval option. However, please confirm whether this story fulfills the visual regression test requirement mentioned in the PR comments, or if additional test infrastructure is needed per DEVELOPING.md guidelines.

Consider adding additional story variants to demonstrate edge cases:

  • Very narrow container width (many ticks need to be hidden)
  • Few data points (all ticks fit)
  • Different tick label sizes
test/cartesian/getEquidistantTicks.spec.ts (1)

1-1: LGTM!

The import correctly adds the new getEquidistantPreserveEndTicks function for testing.

@Om-Mishra09
Copy link
Contributor Author

I've addressed all the feedback!

Unit Tests: Added comprehensive tests in test/cartesian/getEquidistantTicks.spec.ts covering edge cases.

Visual Test: Added a Storybook example in storybook/stories/Examples/EquidistantPreserveEnd.stories.tsx for visual regression testing.

Documentation: Updated the JSDoc in src/util/types.ts to describe the new option.

Ready for review!

@Om-Mishra09
Copy link
Contributor Author

Great catch regarding the unit tests! You were right that the previous tests relied too heavily on manipulating tick size.

I have updated the test case in getEquidistantTicks.spec.ts to use standard tick sizes and rely purely on minTickGap to force the step calculation logic (e.g. forcing a step size of 2). This properly verifies the math without manually engineering the tick sizes.

Ready for review!

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: 0

🧹 Nitpick comments (2)
test/cartesian/getEquidistantTicks.spec.ts (2)

36-74: Test suite properly validates the new function and addresses past review concerns.

The test at lines 46-73 correctly validates that the function skips ticks to satisfy minTickGap while preserving the end tick. Tracing through the logic confirms that with the given parameters (10px coordinates apart, 10px tick size, 8px minTickGap), stepsize=1 would cause collisions while stepsize=2 succeeds, returning indices 0, 2, 4 as expected. This would NOT pass with an identity function, which addresses the previous reviewer's concern.

Consider adding test cases for better coverage:

  • All ticks fit: When minTickGap allows all ticks to be shown, verify all are returned.
  • Only last tick fits: When minTickGap is very large, verify only the last tick is returned.
  • Sign = -1: Validate the function works correctly with negative sign (reverse direction).
  • Edge cases: Single tick, two ticks, etc.

Example test case for "all ticks fit":

it('should return all ticks when they all fit within minTickGap constraints', () => {
  const ticks = [
    { value: 'A', coordinate: 0, index: 0 },
    { value: 'B', coordinate: 50, index: 1 },
    { value: 'C', coordinate: 100, index: 2 },
  ];
  const getTickSizeStatic = () => 10;
  
  const result = getEquidistantPreserveEndTicks(
    1,
    { start: 0, end: 150 },
    getTickSizeStatic,
    ticks,
    5, // minTickGap - ticks are far enough apart
  );
  
  expect(result.map(t => t.value)).toEqual(['A', 'B', 'C']);
});

59-61: Clarify comment for better readability.

The comment could be more precise:

  • Line 59: "The ticks are 10px apart" is slightly ambiguous. Consider: "The tick coordinates are 10px apart."
  • Lines 60-61: "we expect indices 4, 2, 0" suggests reverse order, but the result is in forward order [0, 2, 4]. Consider: "Since we anchor at the end (Index 4) with stepsize 2 and offset 0, the sequence includes indices 0, 2, 4."

Apply this diff for clarity:

-    // The ticks are 10px apart. We set minTickGap to 8px.
+    // The tick coordinates are 10px apart. We set minTickGap to 8px.
     // This forces the algorithm to skip every other tick (Step Size 2) to avoid overlap.
-    // Since we anchor at the end (Index 4), we expect indices 4, 2, 0.
+    // Since we anchor at the end (Index 4) with stepsize 2, the sequence includes indices 0, 2, 4.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 818c619 and 3cc2c7d.

📒 Files selected for processing (1)
  • test/cartesian/getEquidistantTicks.spec.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/cartesian/getEquidistantTicks.spec.ts (2)
src/util/types.ts (1)
  • CartesianTickItem (776-780)
src/cartesian/getEquidistantTicks.ts (1)
  • getEquidistantPreserveEndTicks (66-137)

@ckifer
Copy link
Member

ckifer commented Nov 23, 2025

Going to hold off on merging this until I can make a quick change and get a patch out for 4.5.1

@ckifer ckifer merged commit 7e3950c into recharts:main Nov 28, 2025
21 of 22 checks passed
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.

[Feature request] [XAxis] [YAxis] Add equidistantPreserveEnd interval

3 participants