Skip to content

fix: prevent getBandSizeOfAxis from collapsing band size due to floating-point tick gaps#7556

Merged
PavelVanecek merged 2 commits into
recharts:mainfrom
xianjianlf2:fix/bandsize-precision-4043
Jul 20, 2026
Merged

fix: prevent getBandSizeOfAxis from collapsing band size due to floating-point tick gaps#7556
PavelVanecek merged 2 commits into
recharts:mainfrom
xianjianlf2:fix/bandsize-precision-4043

Conversation

@xianjianlf2

@xianjianlf2 xianjianlf2 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

getBandSizeOfAxis derives the band size from the smallest pixel gap between adjacent (sorted) axis ticks. On a numeric axis, two high-precision float values can map to nearly the same pixel, producing a spurious sub-pixel gap (e.g. 0.00008318614652580436 in the issue reproduction). That tiny gap becomes the band size, which collapses every bar in the chart to ~0px wide.

This change ignores adjacent-tick gaps smaller than 1e-4 × maxGap when computing the minimum spacing. A relative threshold (rather than an absolute pixel value) is used so the fix scales with chart size: near-coincident ticks represent the same rendering slot regardless of how large the chart is, while legitimately uneven category spacing (which is on the same order of magnitude as the widest gap) is unaffected. If every gap is below the threshold, the function returns 0, matching the previous behavior for all-coincident ticks.

Fixes #4043

Related Issue

#4043 (open, labeled bug): bars collapse to ~0 width when data contains high-precision values that place two ticks sub-pixel apart.

Motivation and Context

Charts with high-precision numeric data currently render invisible bars because a single near-duplicate tick position dictates the band size. See the issue for a reproduction.

How Has This Been Tested?

  • Added a unit test in test/util/ChartUtils.spec.tsx reproducing the issue: ticks evenly spaced 10px apart plus one tick a sub-pixel distance from another; the band size is now 10 instead of ~0.00008.
  • Ran npx vitest run --config vitest.config.mts test/util/ChartUtils.spec.tsx — 68/68 tests pass.
  • npm run check-types-lib and eslint pass on the changed files.
  • No storybook story or VR test was added.

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:

  • My change requires a change to the documentation.
  • 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

Summary by CodeRabbit

  • Bug Fixes

    • Improved chart category-band sizing when tick positions are nearly identical.
    • Prevented tiny floating-point gaps from collapsing calculated band sizes.
  • Tests

    • Added regression coverage for high-precision tick coordinates to ensure expected band sizes are preserved.

getBandSizeOfAxis derived the band size from the smallest pixel gap
between adjacent axis ticks. On a numeric axis, two high-precision float
values can map to nearly the same pixel, producing a spurious sub-pixel
gap (e.g. 0.00008318614652580436). That tiny gap became the band size,
collapsing every bar to ~0px wide.

Near-coincident ticks represent the same category slot for rendering, so
ignore gaps that are a negligible fraction (< 1e-4) of the widest gap
when computing the minimum spacing. Evenly-spaced and legitimately uneven
category positions are unaffected.

Closes recharts#4043
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

getBandSizeOfAxis now ignores negligible floating-point tick gaps when sizing category bands. A regression test covers nearly coincident tick coordinates and verifies the expected band size remains unchanged.

Changes

Category band sizing

Layer / File(s) Summary
Filter negligible tick gaps and validate sizing
src/util/ChartUtils.ts, test/util/ChartUtils.spec.tsx
Adjacent tick gaps are filtered using a threshold relative to the widest gap, and a regression test confirms high-precision near-duplicate coordinates produce a band size of 10.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ckifer

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main bug fix in getBandSizeOfAxis.
Description check ✅ Passed The description follows the template and includes the issue link, motivation, testing, and change type.
Linked Issues check ✅ Passed The code and regression test directly address #4043 by preventing near-duplicate ticks from collapsing bar width.
Out of Scope Changes check ✅ Passed The changes are limited to the bug fix and its test, with no unrelated scope added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

665-687: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify gap calculation and avoid array allocation.

Since TickItem.coordinate is defined as a required number in the TypeScript interface, the optional chaining (?.) and || 0 fallbacks are technically redundant.

Additionally, you can avoid allocating the gaps array by computing the gaps on the fly in a second pass. This is a minor micro-optimization that slightly reduces memory overhead.

♻️ Proposed refactor
-    // Collect the pixel gaps between adjacent category positions and track the
-    // widest one.
-    const gaps: number[] = [];
     let maxGap = 0;
     for (let i = 1, len = orderedTicks.length; i < len; i++) {
-      const gap = (orderedTicks[i]?.coordinate || 0) - (orderedTicks[i - 1]?.coordinate || 0);
-      gaps.push(gap);
+      const gap = orderedTicks[i].coordinate - orderedTicks[i - 1].coordinate;
       maxGap = Math.max(gap, maxGap);
     }
 
     // High-precision float values can place two ticks at (almost) the same pixel,
     // producing a spurious sub-pixel gap. Such near-coincident positions are the
     // same category slot for rendering purposes and must not define the band
     // size, otherwise every bar collapses to ~0px wide (issue `#4043`). Ignore gaps
     // that are a negligible fraction of the widest gap so a single near-duplicate
     // value cannot dictate the spacing.
     const minMeaningfulGap = maxGap * 1e-4;
     let bandSize = Infinity;
-    for (const gap of gaps) {
+    for (let i = 1, len = orderedTicks.length; i < len; i++) {
+      const gap = orderedTicks[i].coordinate - orderedTicks[i - 1].coordinate;
       if (gap > minMeaningfulGap) {
         bandSize = Math.min(gap, bandSize);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/ChartUtils.ts` around lines 665 - 687, In the gap-sizing logic
around orderedTicks, use the required coordinate values directly without
optional chaining or || 0 fallbacks. Remove the intermediate gaps array, retain
the first pass to compute maxGap, then compute each adjacent gap in a second
pass while applying the existing minMeaningfulGap filter to determine bandSize.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/util/ChartUtils.ts`:
- Around line 665-687: In the gap-sizing logic around orderedTicks, use the
required coordinate values directly without optional chaining or || 0 fallbacks.
Remove the intermediate gaps array, retain the first pass to compute maxGap,
then compute each adjacent gap in a second pass while applying the existing
minMeaningfulGap filter to determine bandSize.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 329a3903-ebe8-4f4c-b7ea-27bca0bd2cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 48d4f2f and 3d4a672.

📒 Files selected for processing (2)
  • src/util/ChartUtils.ts
  • test/util/ChartUtils.spec.tsx

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.45MB 874 bytes (0.06%) ⬆️
recharts/bundle-es6 1.27MB 874 bytes (0.07%) ⬆️
recharts/bundle-umd 586.59kB 104 bytes (0.02%) ⬆️
recharts/bundle-treeshaking-cartesian 726.9kB 874 bytes (0.12%) ⬆️
recharts/bundle-treeshaking-polar 477.71kB 874 bytes (0.18%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
util/ChartUtils.js 874 bytes 21.33kB 4.27%
view changes for bundle: recharts/bundle-treeshaking-polar

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js 874 bytes 477.71kB 0.18%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
util/ChartUtils.js 874 bytes 19.31kB 4.74%
view changes for bundle: recharts/bundle-treeshaking-cartesian

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js 874 bytes 726.9kB 0.12%
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 104 bytes 586.59kB 0.02%

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.20%. Comparing base (48d4f2f) to head (3d4a672).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7556   +/-   ##
=======================================
  Coverage   88.20%   88.20%           
=======================================
  Files         615      615           
  Lines       14272    14278    +6     
  Branches     3587     3589    +2     
=======================================
+ Hits        12588    12594    +6     
  Misses       1494     1494           
  Partials      190      190           

☔ View full report in Codecov by Harness.
📢 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.

@PavelVanecek
PavelVanecek merged commit 2ea9ca0 into recharts:main Jul 20, 2026
58 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.

Bar width gets shrinked when value contains very precise numbers

2 participants