Skip to content

fix: respect user-provided textAnchor prop on XAxis/YAxis#7028

Merged
PavelVanecek merged 4 commits intorecharts:mainfrom
pierreeurope:fix/xaxis-textanchor-regression
Feb 28, 2026
Merged

fix: respect user-provided textAnchor prop on XAxis/YAxis#7028
PavelVanecek merged 4 commits intorecharts:mainfrom
pierreeurope:fix/xaxis-textanchor-regression

Conversation

@pierreeurope
Copy link
Contributor

@pierreeurope pierreeurope commented Feb 19, 2026

Fixes a regression introduced in #6990 where the textAnchor prop on XAxis/YAxis was being ignored.

The calculated textAnchor (from orientation/mirror) was overriding the user-provided value because it was assigned after spreading axisProps.

Change: Now uses user-provided textAnchor if present, otherwise falls back to the calculated value.

Fixes #7027

Summary by CodeRabbit

  • Improvements

    • Chart tick labels now respect an explicit horizontal text alignment when provided; otherwise they fall back to the previous automatic horizontal alignment. Vertical alignment behavior remains unchanged.
  • Tests

    • Added tests that verify tick label horizontal alignment when a user-specified alignment is present and when defaults are used, and confirm the expected tick count.

Fixes a regression introduced in v3.7.0 where the textAnchor prop
on XAxis/YAxis was being ignored. The calculated textAnchor (from
orientation/mirror) was overriding the user-provided value.

Now uses user-provided textAnchor if present, otherwise falls back
to the calculated value.

Fixes recharts#7027
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72231c4 and 6ad9e82.

📒 Files selected for processing (1)
  • src/cartesian/CartesianAxis.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cartesian/CartesianAxis.tsx

Walkthrough

Tick label horizontal alignment in CartesianAxis now prefers a valid axisProps.textAnchor override; if invalid or absent it falls back to the previous computed value. Vertical anchor logic and other tick processing remain unchanged.

Changes

Cohort / File(s) Summary
Axis rendering
src/cartesian/CartesianAxis.tsx
Use isValidTextAnchor(axisProps.textAnchor) to prefer user-provided horizontal text-anchor; otherwise fallback to getTickTextAnchor(orientation, mirror). Vertical anchor unchanged.
Tests
test/cartesian/CartesianAxis.spec.tsx
Adds two tests: one asserting text-anchor="start" when textAnchor="start" is passed, and one asserting default text-anchor="middle" when no textAnchor is provided.

Sequence Diagram(s)

(Skipped — change is a small rendering override and tests, not a multi-component sequential flow that requires visualization.)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Suggested reviewers

  • ckifer
  • PavelVanecek
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description identifies the regression, root cause, solution, and linked issue, but does not follow the provided template structure with sections like Related Issue, Motivation, Testing, and Types of changes. Fill out the complete description template including Related Issue, Motivation and Context, How Has This Been Tested, and Types of changes sections to provide a comprehensive overview.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: respecting the user-provided textAnchor prop on XAxis/YAxis, which is the core objective of this pull request.
Linked Issues check ✅ Passed The changes in CartesianAxis.tsx and test file directly address issue #7027 by respecting user-provided textAnchor while preserving calculated fallback behavior, with regression tests added.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the textAnchor prop regression: modifications to CartesianAxis.tsx tick rendering logic and corresponding regression tests, with no unrelated alterations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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: 1

🧹 Nitpick comments (1)
src/cartesian/CartesianAxis.tsx (1)

393-395: Add a regression test to guard the user-provided textAnchor behaviour.

No test file was included in this PR. The regression in #7027 was not caught by the existing test suite, so a targeted test (e.g., verifying that a tick <Text> renders with the explicitly-passed textAnchor rather than the orientation-derived default) would prevent it from recurring.

As per coding guidelines: "Aim for 100% unit test code coverage when writing new code."

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

In `@src/cartesian/CartesianAxis.tsx` around lines 393 - 395, Add a regression
unit test that mounts/renders the CartesianAxis component and verifies that when
axisProps.textAnchor is explicitly provided the tick <Text> element uses that
value instead of the orientation-derived default from getTickTextAnchor; locate
the behavior around the textAnchor assignment in CartesianAxis (the const
textAnchor = (axisProps.textAnchor as TextAnchor) ??
getTickTextAnchor(orientation, mirror);) and write an assertion that a tick's
rendered text element has the expected textAnchor attribute when
axisProps.textAnchor is set, plus a complementary case where
axisProps.textAnchor is undefined to ensure the fallback to getTickTextAnchor
still works.
🤖 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/cartesian/CartesianAxis.tsx`:
- Around line 393-394: Replace the unsafe type assertion on axisProps.textAnchor
in CartesianAxis by using the existing isValidTextAnchor type guard: check
axisProps.textAnchor with isValidTextAnchor and use it when truthy, otherwise
fall back to getTickTextAnchor(orientation, mirror); update the textAnchor
assignment in the CartesianAxis component to use this predicate (referencing
axisProps.textAnchor, isValidTextAnchor, getTickTextAnchor, orientation, and
mirror).

---

Nitpick comments:
In `@src/cartesian/CartesianAxis.tsx`:
- Around line 393-395: Add a regression unit test that mounts/renders the
CartesianAxis component and verifies that when axisProps.textAnchor is
explicitly provided the tick <Text> element uses that value instead of the
orientation-derived default from getTickTextAnchor; locate the behavior around
the textAnchor assignment in CartesianAxis (the const textAnchor =
(axisProps.textAnchor as TextAnchor) ?? getTickTextAnchor(orientation, mirror);)
and write an assertion that a tick's rendered text element has the expected
textAnchor attribute when axisProps.textAnchor is set, plus a complementary case
where axisProps.textAnchor is undefined to ensure the fallback to
getTickTextAnchor still works.

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.

Can you add a test please? VR or unit

@pierreeurope
Copy link
Contributor Author

Added regression tests to verify user-provided textAnchor is respected (with both custom and default cases). All tests passing locally.

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 (2)
test/cartesian/CartesianAxis.spec.tsx (2)

360-403: Consider adding a vertical-axis (left/right orientation) textAnchor test to match PR scope.

The PR title and linked issue mention both XAxis and YAxis. The fix in CartesianAxis.tsx applies to all orientations, but both new tests only cover orientation="bottom". A parallel test asserting that a user-provided textAnchor on a vertical axis (e.g., orientation="left") is also respected would give complete regression coverage for the stated fix.

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

In `@test/cartesian/CartesianAxis.spec.tsx` around lines 360 - 403, Add a parallel
test in CartesianAxis.spec.tsx that renders <CartesianAxis> with a vertical
orientation (e.g., orientation="left" or "right") inside <Surface> using the
same ticks, exampleScale and viewBox, passing a user-provided textAnchor (for
example "start"), then query the tick elements by the
'.recharts-cartesian-axis-tick-value' selector and assert each tick has the
expected 'text-anchor' attribute; mirror the existing horizontal tests (the ones
using orientation="bottom") but change orientation and positioning props so the
test targets the vertical-axis code paths in CartesianAxis.

360-403: Missing vi.useFakeTimers() and vi.runOnlyPendingTimers() in both new tests.

The coding guidelines require vi.useFakeTimers() in all tests and vi.runOnlyPendingTimers() after renders (when not using createSelectorTestCase). Both new tests omit these. While none of the pre-existing tests in this file include them either, that is a pre-existing gap — the new tests being added now should follow the guideline.

♻️ Proposed fix
+import { beforeEach, afterEach } from 'vitest';

+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+
+  afterEach(() => {
+    vi.useRealTimers();
+  });

   it('Respects user-provided textAnchor prop on horizontal axis', () => {
     const { container } = render(
       <Surface width={500} height={500}>
         <CartesianAxis
           orientation="bottom"
           y={100}
           width={400}
           height={50}
           viewBox={{ x: 0, y: 0, width: 500, height: 500 }}
           ticks={ticks}
           textAnchor="start"
           scale={exampleScale}
         />
       </Surface>,
     );
+    vi.runOnlyPendingTimers();

     const tickTexts = container.querySelectorAll('.recharts-cartesian-axis-tick-value');
     expect(tickTexts).toHaveLength(5);
     tickTexts.forEach(text => {
       expect(text).toHaveAttribute('text-anchor', 'start');
     });
   });

   it('Uses default textAnchor when not provided', () => {
     const { container } = render(
       <Surface width={500} height={500}>
         <CartesianAxis
           orientation="bottom"
           y={100}
           width={400}
           height={50}
           viewBox={{ x: 0, y: 0, width: 500, height: 500 }}
           ticks={ticks}
           scale={exampleScale}
         />
       </Surface>,
     );
+    vi.runOnlyPendingTimers();

     const tickTexts = container.querySelectorAll('.recharts-cartesian-axis-tick-value');
     expect(tickTexts).toHaveLength(5);
     tickTexts.forEach(text => {
       expect(text).toHaveAttribute('text-anchor', 'middle');
     });
   });

As per coding guidelines: "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame" and "Call vi.runOnlyPendingTimers() to advance timers after renders when not using createSelectorTestCase helper".

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

In `@test/cartesian/CartesianAxis.spec.tsx` around lines 360 - 403, Add test timer
setup and advancement to both new tests ("Respects user-provided textAnchor prop
on horizontal axis" and "Uses default textAnchor when not provided"): call
vi.useFakeTimers() at the start of each it block before render and call
vi.runOnlyPendingTimers() immediately after the render(...) completes so pending
timers/RAF are flushed; ensure this is applied inside the same it blocks
surrounding the render of <CartesianAxis ... /> to satisfy the Redux
autoBatchEnhancer and requestAnimationFrame requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/cartesian/CartesianAxis.spec.tsx`:
- Around line 360-403: Add a parallel test in CartesianAxis.spec.tsx that
renders <CartesianAxis> with a vertical orientation (e.g., orientation="left" or
"right") inside <Surface> using the same ticks, exampleScale and viewBox,
passing a user-provided textAnchor (for example "start"), then query the tick
elements by the '.recharts-cartesian-axis-tick-value' selector and assert each
tick has the expected 'text-anchor' attribute; mirror the existing horizontal
tests (the ones using orientation="bottom") but change orientation and
positioning props so the test targets the vertical-axis code paths in
CartesianAxis.
- Around line 360-403: Add test timer setup and advancement to both new tests
("Respects user-provided textAnchor prop on horizontal axis" and "Uses default
textAnchor when not provided"): call vi.useFakeTimers() at the start of each it
block before render and call vi.runOnlyPendingTimers() immediately after the
render(...) completes so pending timers/RAF are flushed; ensure this is applied
inside the same it blocks surrounding the render of <CartesianAxis ... /> to
satisfy the Redux autoBatchEnhancer and requestAnimationFrame requirements.

@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.19%. Comparing base (c3b308c) to head (6ad9e82).
⚠️ Report is 28 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7028      +/-   ##
==========================================
+ Coverage   90.12%   90.19%   +0.07%     
==========================================
  Files         526      526              
  Lines       39183    39630     +447     
  Branches     5423     5440      +17     
==========================================
+ Hits        35312    35746     +434     
- Misses       3862     3875      +13     
  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.

@codecov
Copy link

codecov bot commented Feb 20, 2026

Bundle Report

Changes will increase total bundle size by 12.44kB (0.43%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.27MB 5.77kB (0.46%) ⬆️
recharts/bundle-es6 1.1MB 5.55kB (0.51%) ⬆️
recharts/bundle-umd 543.02kB 1.12kB (0.21%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 1.12kB 543.02kB 0.21%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 88 bytes 55.13kB 0.16%
cartesian/Bar.js 329 bytes 26.8kB 1.24%
cartesian/Line.js 1.9kB 26.75kB 7.63% ⚠️
cartesian/CartesianAxis.js 176 bytes 17.43kB 1.02%
polar/PolarAngleAxis.js 144 bytes 11.51kB 1.27%
shape/Rectangle.js 23 bytes 11.07kB 0.21%
util/scale/getNiceTickValues.js 2.29kB 10.58kB 27.69% ⚠️
polar/PolarRadiusAxis.js 144 bytes 10.33kB 1.41%
cartesian/YAxis.js 70 bytes 9.64kB 0.73%
cartesian/XAxis.js 70 bytes 8.24kB 0.86%
state/selectors/polarAxisSelectors.js 40 bytes 7.06kB 0.57%
shape/Trapezoid.js 23 bytes 6.7kB 0.34%
component/DefaultLegendContent.js 165 bytes 6.69kB 2.53%
state/selectors/barStackSelectors.js 45 bytes 2.14kB 2.14%
polar/defaultPolarAngleAxisProps.js 20 bytes 597 bytes 3.47%
polar/defaultPolarRadiusAxisProps.js 20 bytes 488 bytes 4.27%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 88 bytes 64.96kB 0.14%
cartesian/Bar.js 329 bytes 28.55kB 1.17%
cartesian/Line.js 1.9kB 28.36kB 7.17% ⚠️
cartesian/CartesianAxis.js 168 bytes 18.75kB 0.9%
polar/PolarAngleAxis.js 144 bytes 12.95kB 1.12%
shape/Rectangle.js 23 bytes 12.34kB 0.19%
polar/PolarRadiusAxis.js 144 bytes 11.78kB 1.24%
util/scale/getNiceTickValues.js 2.42kB 11.51kB 26.56% ⚠️
cartesian/YAxis.js 85 bytes 11.07kB 0.77%
cartesian/XAxis.js 85 bytes 9.63kB 0.89%
state/selectors/polarAxisSelectors.js 40 bytes 8.77kB 0.46%
shape/Trapezoid.js 23 bytes 7.87kB 0.29%
component/DefaultLegendContent.js 244 bytes 7.65kB 3.29%
state/selectors/barStackSelectors.js 45 bytes 2.44kB 1.88%
polar/defaultPolarAngleAxisProps.js 20 bytes 769 bytes 2.67%
polar/defaultPolarRadiusAxisProps.js 20 bytes 662 bytes 3.12%

Replaced the 'as TextAnchor' type assertion with the existing isValidTextAnchor helper function as requested in code review. This provides better type safety and follows the project's established patterns.
The ternary expression was too long for prettier. Split it across
multiple lines to satisfy the formatter.
@PavelVanecek PavelVanecek merged commit ca5f9ec into recharts:main Feb 28, 2026
41 of 42 checks passed
@PavelVanecek
Copy link
Collaborator

The VR test failure is fixed on main branch already

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.

textAnchor XAxis prop not respected

3 participants