Skip to content

fix: use originalDataIndex for tooltip dispatch in Bar#7273

Merged
PavelVanecek merged 2 commits intorecharts:mainfrom
mayrang:fix/vertical-bar-tooltip-index
Apr 24, 2026
Merged

fix: use originalDataIndex for tooltip dispatch in Bar#7273
PavelVanecek merged 2 commits intorecharts:mainfrom
mayrang:fix/vertical-bar-tooltip-index

Conversation

@mayrang
Copy link
Copy Markdown
Contributor

@mayrang mayrang commented Apr 24, 2026

Fixes #7261

Description

When Bar renders with sparse data (some entries filtered out as zero-dimension), the tooltip event handlers (onMouseEnter, onMouseLeave, onClick) dispatch the render-array index i as the active tooltip index. After .filter(Boolean) removes null entries, this index no longer matches the original data array, so the tooltip resolves the wrong data entry.

For example, with this data:

const data = [
  { category: 'A', bar1: [0, 10] },
  { category: 'B', bar2: [5, 20] },
];

<Bar dataKey="bar2" /> only renders one rectangle (for row B). That rectangle gets render index i=0, but its actual position in the data array is 1. Hovering it dispatches activeIndex: "0", which resolves to row A instead of row B.

The isActive check on line 598-600 already handles this correctly by using entry.originalDataIndex instead of the render index. This PR applies the same fix to the event handlers.

Also fixed the same issue in BarBackground where isActive and event handlers had the same render-index problem.

Test Plan

Added a regression test for vertical BarChart with shared={false} and sparse data — verifies that hovering bar2 (row B) shows bar2's payload, not bar1's.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed tooltip and bar highlight inconsistencies so hover, active state, and mouse events remain consistent when bars are filtered or reordered, preventing incorrect highlights or stale tooltip content.
  • Tests

    • Added a regression test that simulates successive hovers on sparse vertical bar charts to ensure tooltips update reliably and no stale payloads are shown.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b453eeb-cb07-4e0b-9def-10b8ad95ab47

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa16de and 567237b.

📒 Files selected for processing (2)
  • src/cartesian/Bar.tsx
  • test/component/Tooltip/Tooltip.payload.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/cartesian/Bar.tsx
  • test/component/Tooltip/Tooltip.payload.spec.tsx

Walkthrough

Bar event handlers and active-state checks now use each entry's stable originalDataIndex for tooltip matching; a regression test was added to verify correct tooltip payloads when hovering bars in a vertical BarChart with sparse data.

Changes

Cohort / File(s) Summary
Bar component event wiring fix
src/cartesian/Bar.tsx
Switched background-rectangle and bar-rectangle mouse event dispatch and isActive comparisons to use entry.originalDataIndex instead of the per-render-loop index when matching against the tooltip activeIndex.
Tooltip payload regression test
test/component/Tooltip/Tooltip.payload.spec.tsx
Added timer-aware test utilities and a regression test that renders a vertical BarChart with sparse two-bar data, fires mouseOver on distinct bars (using vi timers and act), and asserts the tooltip payload matches the hovered bar's data.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Fix mouse event type #6939: Modifies mouse event handling in src/cartesian/Bar.tsx and touches related event-dispatch/typing paths; overlaps with the event-indexing changes here.

Suggested reviewers

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

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically summarizes the main fix: using originalDataIndex for tooltip dispatch in Bar, which is the primary change in this PR.
Description check ✅ Passed The description provides a clear explanation of the problem, includes a concrete example, explains the solution, mentions testing, and relates to issue #7261.
Linked Issues check ✅ Passed The PR fully addresses the objectives from issue #7261 by fixing tooltip payload resolution for sparse bar data using originalDataIndex in event handlers.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the tooltip index issue in Bar and BarBackground; the test addition validates the fix without introducing unrelated modifications.

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

✨ 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 and usage tips.

Copy link
Copy Markdown
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 (1)
src/cartesian/Bar.tsx (1)

518-521: ⚠️ Potential issue | 🟡 Minor

User-supplied event handlers for onMouseMove, onMouseDown, onMouseUp, onMouseOver, and onMouseOut still receive the render-array index.

adaptEventsOfChild(restOfAllOtherProps, entry, i) on Line 730 (and Line 518 in the background layer) continues to pass i — the post-filter render index — to user-provided handlers for these event types. In sparse/zero-dimension filtered scenarios, this index won't match the original data array (e.g., when zero-height bars are filtered out).

Note: onClick, onMouseEnter, and onMouseLeave are already correctly wired to use entry.originalDataIndex via their respective context dispatch functions, so they are unaffected.

Since the BarMouseEvent signature is part of the public Bar API, changing it is a semver concern. However, existing tests do not verify the event handler index behavior for onMouseMove, onMouseDown, onMouseUp, onMouseOver, and onMouseOut in sparse/filtered data scenarios, so the impact of aligning them to originalDataIndex is unclear.

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

In `@src/cartesian/Bar.tsx` around lines 518 - 521, adaptEventsOfChild is passing
the post-filter render index (i) into user event handlers for onMouseMove,
onMouseDown, onMouseUp, onMouseOver, and onMouseOut, which causes mismatches
when bars are filtered; update adaptEventsOfChild (or its call sites in Bar.tsx
where adaptEventsOfChild(restOfAllOtherProps, entry, i) is used) so that the
event payload delivered to those five handlers uses entry.originalDataIndex
instead of i (keep onClick, onMouseEnter, onMouseLeave as-is), ensuring the
BarMouseEvent index matches the original data index without changing the public
event signature.
🧹 Nitpick comments (2)
test/component/Tooltip/Tooltip.payload.spec.tsx (2)

1523-1525: Minor: index-based lookup of the second .recharts-bar group is fragile.

container.querySelectorAll('.recharts-bar')[1] assumes rendering order and class uniqueness that currently hold but aren't enforced. Consider targeting by dataKey (e.g., a class/selector derived from bar2) or by the <g>’s id set via the Bar id prop to make the test resilient to internal layer reordering.

🤖 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 1523 - 1525,
The test currently uses index-based lookup
(container.querySelectorAll('.recharts-bar')[1]) which is brittle; update the
selection to target the second Bar more robustly by selecting the group by a
stable identifier tied to the Bar (e.g., use the Bar's id or a dataKey-derived
selector) instead of relying on order — replace the index lookup that produces
bar2Group with a querySelector that matches the Bar id or data-key for "bar2"
and then query that group for barMouseHoverTooltipSelector to get bar2Rect.

1505-1533: Consider also asserting the bar1 (row A) payload to guard against symmetric regressions.

The test exercises only the second bar group where the original bug manifested. A companion assertion that hovers bar1 (row A) and verifies its payload is ['bar1 : 0 ~ 10'] would pin down both ends of the index mapping and catch a future regression where, for example, indices end up swapped or clamped. Cheap to add given the setup is already in place.

🧪 Proposed additional assertion
         const barGroups = container.querySelectorAll('.recharts-bar');
-        const bar2Group = barGroups[1]; // second Bar component = bar2
+        const bar1Group = barGroups[0];
+        const bar1Rect = bar1Group.querySelector(barMouseHoverTooltipSelector);
+        assertNotNull(bar1Rect);
+        fireEvent.mouseOver(bar1Rect, { clientX: 100, clientY: 100 });
+        act(() => {
+          vi.runOnlyPendingTimers();
+        });
+        expectTooltipPayload(container, '', ['bar1 : 0 ~ 10']);
+
+        const bar2Group = barGroups[1]; // second Bar component = bar2
         const bar2Rect = bar2Group.querySelector(barMouseHoverTooltipSelector);
         assertNotNull(bar2Rect);
         fireEvent.mouseOver(bar2Rect, { clientX: 200, clientY: 200 });
         act(() => {
           vi.runOnlyPendingTimers();
         });
 
         expectTooltipPayload(container, '', ['bar2 : 5 ~ 20']);
🤖 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 1505 - 1533,
Add a companion assertion that also hovers the bar1 rect and verifies its
tooltip payload to guard against symmetric index-mapping regressions: locate the
first Bar group from container.querySelectorAll('.recharts-bar') (barGroups[0]),
query its rect using the same barMouseHoverTooltipSelector, fire a mouseOver on
that rect (with appropriate clientX/clientY), run pending timers like the
existing act/vi.runOnlyPendingTimers call, and call
expectTooltipPayload(container, '', ['bar1 : 0 ~ 10']) to validate the bar1
payload; reuse the existing barGroups, bar2Rect logic and expectTooltipPayload
helper to keep the change minimal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/cartesian/Bar.tsx`:
- Around line 518-521: adaptEventsOfChild is passing the post-filter render
index (i) into user event handlers for onMouseMove, onMouseDown, onMouseUp,
onMouseOver, and onMouseOut, which causes mismatches when bars are filtered;
update adaptEventsOfChild (or its call sites in Bar.tsx where
adaptEventsOfChild(restOfAllOtherProps, entry, i) is used) so that the event
payload delivered to those five handlers uses entry.originalDataIndex instead of
i (keep onClick, onMouseEnter, onMouseLeave as-is), ensuring the BarMouseEvent
index matches the original data index without changing the public event
signature.

---

Nitpick comments:
In `@test/component/Tooltip/Tooltip.payload.spec.tsx`:
- Around line 1523-1525: The test currently uses index-based lookup
(container.querySelectorAll('.recharts-bar')[1]) which is brittle; update the
selection to target the second Bar more robustly by selecting the group by a
stable identifier tied to the Bar (e.g., use the Bar's id or a dataKey-derived
selector) instead of relying on order — replace the index lookup that produces
bar2Group with a querySelector that matches the Bar id or data-key for "bar2"
and then query that group for barMouseHoverTooltipSelector to get bar2Rect.
- Around line 1505-1533: Add a companion assertion that also hovers the bar1
rect and verifies its tooltip payload to guard against symmetric index-mapping
regressions: locate the first Bar group from
container.querySelectorAll('.recharts-bar') (barGroups[0]), query its rect using
the same barMouseHoverTooltipSelector, fire a mouseOver on that rect (with
appropriate clientX/clientY), run pending timers like the existing
act/vi.runOnlyPendingTimers call, and call expectTooltipPayload(container, '',
['bar1 : 0 ~ 10']) to validate the bar1 payload; reuse the existing barGroups,
bar2Rect logic and expectTooltipPayload helper to keep the change minimal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1fc966cd-6866-402c-a480-3618aa643ce9

📥 Commits

Reviewing files that changed from the base of the PR and between b80912c and f10756b.

📒 Files selected for processing (2)
  • src/cartesian/Bar.tsx
  • test/component/Tooltip/Tooltip.payload.spec.tsx

@mayrang
Copy link
Copy Markdown
Contributor Author

mayrang commented Apr 24, 2026

Re: adaptEventsOfChild still passing render index — I agree this is a related inconsistency, but changing the index passed to user-facing event handlers would affect the public API. I think it's better handled in a separate PR.

Re: index-based selector — I kept it consistent with the existing tests in this file. Happy to change if preferred.

Re: bar1 assertion — Added in the latest push.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.07%. Comparing base (b80912c) to head (567237b).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7273   +/-   ##
=======================================
  Coverage   89.07%   89.07%           
=======================================
  Files         538      538           
  Lines       41020    41020           
  Branches     5563     5564    +1     
=======================================
+ Hits        36538    36540    +2     
+ Misses       4474     4472    -2     
  Partials        8        8           

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

@mayrang mayrang force-pushed the fix/vertical-bar-tooltip-index branch from 9aa16de to 567237b Compare April 24, 2026 05:52
@PavelVanecek PavelVanecek merged commit 39702f2 into recharts:main Apr 24, 2026
51 checks passed
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Bundle Report

Changes will increase total bundle size by 588 bytes (0.01%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.3MB 154 bytes (0.01%) ⬆️
recharts/bundle-es6 1.13MB 154 bytes (0.01%) ⬆️
recharts/bundle-umd 551.73kB 126 bytes (0.02%) ⬆️
recharts/bundle-treeshaking-cartesian 644.67kB 154 bytes (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 126 bytes 551.73kB 0.02%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Bar.js 154 bytes 28.96kB 0.53%
view changes for bundle: recharts/bundle-treeshaking-cartesian

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js 154 bytes 644.67kB 0.02%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Bar.js 154 bytes 27.21kB 0.57%

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.

Tooltip payload incorrect when hovering bars beyond the first data entry in a vertical BarChart

2 participants