fix: prevent getBandSizeOfAxis from collapsing band size due to floating-point tick gaps#7556
Conversation
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
Walkthrough
ChangesCategory band sizing
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/util/ChartUtils.ts (1)
665-687: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSimplify gap calculation and avoid array allocation.
Since
TickItem.coordinateis defined as a requirednumberin the TypeScript interface, the optional chaining (?.) and|| 0fallbacks are technically redundant.Additionally, you can avoid allocating the
gapsarray 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
📒 Files selected for processing (2)
src/util/ChartUtils.tstest/util/ChartUtils.spec.tsx
Bundle ReportChanges will increase total bundle size by 3.6kB (0.06%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: recharts/bundle-cjsAssets Changed:
view changes for bundle: recharts/bundle-treeshaking-polarAssets Changed:
view changes for bundle: recharts/bundle-es6Assets Changed:
view changes for bundle: recharts/bundle-treeshaking-cartesianAssets Changed:
view changes for bundle: recharts/bundle-umdAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
Description
getBandSizeOfAxisderives 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.00008318614652580436in 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 × maxGapwhen 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 returns0, 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?
test/util/ChartUtils.spec.tsxreproducing the issue: ticks evenly spaced 10px apart plus one tick a sub-pixel distance from another; the band size is now10instead of~0.00008.npx vitest run --config vitest.config.mts test/util/ChartUtils.spec.tsx— 68/68 tests pass.npm run check-types-libandeslintpass on the changed files.Types of changes
Checklist:
Summary by CodeRabbit
Bug Fixes
Tests