Skip to content

Fix/tooltip deactivate outside cartesian plot#7115

Open
AbishekRaj2007 wants to merge 3 commits intorecharts:mainfrom
AbishekRaj2007:fix/tooltip-deactivate-outside-cartesian-plot
Open

Fix/tooltip deactivate outside cartesian plot#7115
AbishekRaj2007 wants to merge 3 commits intorecharts:mainfrom
AbishekRaj2007:fix/tooltip-deactivate-outside-cartesian-plot

Conversation

@AbishekRaj2007
Copy link
Contributor

@AbishekRaj2007 AbishekRaj2007 commented Mar 9, 2026

Description

When clicking on XAxis tick labels (which render outside the cartesian plot area), the tooltip was incorrectly activating and displaying data for index 0. This was misleading because the user is interacting with an axis element, not a data point.

Fixes #6961

Root Cause

The mouseClickMiddleware had no bounds check to verify whether the click originated inside the cartesian plot area. When clicking outside (e.g. on XAxis ticks), coordinate resolution failed silently and fell back to defaultIndex ?? 0, triggering a misleading tooltip.

Changes

mouseEventsMiddleware.ts

  • Added isInCartesianRange bounds check in mouseClickMiddleware
  • If click is outside the plot area → dispatch mouseClickOutsidePlot() and return early
  • If click is inside → proceed normally as before

tooltipSlice.ts

  • Added mouseClickOutsidePlot reducer
  • Sets axisInteraction.click.active = false when triggered

mouseEventsMiddleware.spec.ts

  • Added test: clicking outside does NOT activate tooltip
  • Added test: clicking outside deactivates an already-active tooltip
  • Added test: clicking inside still activates tooltip normally

Testing

  • All existing tests pass
  • 3 new tests added in mouseEventsMiddleware.spec.ts
  • Manually verified on LineChart, BarChart, and ComposedChart

Type of Change

-Bug Fix

Summary by CodeRabbit

  • Bug Fixes

    • Clicking outside the plot area now properly deactivates active axis interactions.
  • Tests

    • Expanded test suite to verify click behavior across plot boundary regions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Walkthrough

This change adds detection for clicks outside the SVG/plot bounds in the mouse events middleware, dispatching a new mouseClickOutsidePlot action that deactivates the tooltip's axis click interaction state. When a user clicks outside the cartesian plot area, the tooltip is prevented from becoming active with an incorrect activeIndex.

Changes

Cohort / File(s) Summary
Click bounds detection and state deactivation
src/state/mouseEventsMiddleware.ts, src/state/tooltipSlice.ts
Added logic to detect clicks outside the plot area using chart offset and inner bounds calculations, dispatching mouseClickOutsidePlot() action. New reducer sets axisInteraction.click.active = false to deactivate tooltip when clicking outside the plot.
Test suite for bounds detection
test/state/mouseEventsMiddleware.spec.ts
Created new mouseClickMiddleware – plot bounds guard test suite with helper function createStoreWithChartDimensions() and tests verifying click behavior inside/outside SVG bounds and across different plot regions.
Dependency and UI updates
package.json, www/src/components/Playground/AnnotationsPanel.tsx
Removed @codecov/bundle-analyzer from devDependencies; updated AnnotationsPanel to import RechartsDevtoolsPortal instead of RechartsAnnotationsPortal.

Sequence Diagram

sequenceDiagram
    participant User
    participant MouseEventsMiddleware
    participant ChartOffsetSelector
    participant TooltipReducer
    participant AppState

    User->>MouseEventsMiddleware: Click on SVG/Chart
    MouseEventsMiddleware->>ChartOffsetSelector: Get chart offset and dimensions
    ChartOffsetSelector-->>MouseEventsMiddleware: Return innerWidth/innerHeight/offset
    MouseEventsMiddleware->>MouseEventsMiddleware: Calculate chart-space coords<br/>(chartX, chartY)
    MouseEventsMiddleware->>MouseEventsMiddleware: Check if click is outside<br/>inner bounds + margins
    alt Click is Outside Plot Bounds
        MouseEventsMiddleware->>TooltipReducer: Dispatch mouseClickOutsidePlot()
        TooltipReducer->>AppState: Set axisInteraction.click.active = false
        AppState-->>TooltipReducer: State updated
    else Click is Inside Plot Bounds
        MouseEventsMiddleware->>MouseEventsMiddleware: Compute activeProps from<br/>relative coordinates
        MouseEventsMiddleware->>TooltipReducer: Dispatch setMouseClickAxisIndex
        TooltipReducer->>AppState: Update tooltip state
        AppState-->>TooltipReducer: State updated
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • PavelVanecek
  • ckifer
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning One change appears unrelated: replacing RechartsAnnotationsPortal with RechartsDevtoolsPortal in AnnotationsPanel.tsx is not mentioned in the PR description or linked issues. Verify if the portal change in AnnotationsPanel.tsx is intentional and necessary for this fix, or move it to a separate PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: preventing tooltip activation when clicks occur outside the cartesian plot area.
Description check ✅ Passed The description covers all required sections: root cause analysis, code changes, testing, and type classification. It clearly explains the bug, the solution, and how it was tested.
Linked Issues check ✅ Passed The PR fully addresses issue #6961 by implementing a bounds check to prevent tooltip activation when clicks occur outside the plot area, matching the primary expectation.

✏️ 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.

Tip

CodeRabbit can generate a title for your PR based on the changes.

Add @coderabbitai placeholder anywhere in the title of your PR and CodeRabbit will replace it with a title based on the changes in the PR. You can change the placeholder by changing the reviews.auto_title_placeholder setting.

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/state/mouseEventsMiddleware.spec.ts (2)

98-106: Missing vi.useFakeTimers() in the new test suite.

Per coding guidelines, tests should use vi.useFakeTimers() due to Redux autoBatchEnhancer dependency on timers. The existing mouseClickMiddleware describe block at line 67 doesn't explicitly call it either, but it references vi.getTimerCount() which suggests fake timers are configured globally (likely in a setup file). However, for consistency and explicitness, consider adding beforeEach(() => vi.useFakeTimers()) to ensure the test suite is self-contained.

Also, the afterEach comment mentions clearing RAF handles from autoBatchEnhancer, which is good practice.

💡 Optional: Add explicit fake timers setup
 describe('mouseClickMiddleware – plot bounds guard', () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+
   afterEach(() => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/state/mouseEventsMiddleware.spec.ts` around lines 98 - 106, The new
"mouseClickMiddleware – plot bounds guard" test suite is missing an explicit
call to vi.useFakeTimers(), so add a beforeEach hook inside the describe block
(the same block that contains the existing afterEach with vi.clearAllTimers())
to call vi.useFakeTimers() before each test; keep the existing afterEach that
clears RAF timers (vi.clearAllTimers()) so autoBatchEnhancer leftovers are
cleaned up.

146-166: Test coverage is adequate, but consider adding a note about the test setup.

This test correctly verifies that clicking inside the plot area does not dispatch mouseClickOutsidePlot. However, without tooltip ticks/data configured, selectActivePropsFromChartPointer returns undefined, so setMouseClickAxisIndex is never dispatched either. The tooltip remains active simply because no deactivating action occurs.

This is fine for testing the bounds guard itself, but a comment clarifying this behavior would help future maintainers understand the test's intent.

📝 Optional: Add clarifying comment
   it('should NOT deactivate the tooltip when clicking inside the plot area', () => {
     const store = createStoreWithChartDimensions();

-    // Pre-activate the tooltip
+    // Pre-activate the tooltip. Note: without tooltip ticks/data, the middleware
+    // won't dispatch setMouseClickAxisIndex, but the key assertion is that
+    // mouseClickOutsidePlot is NOT dispatched for in-range clicks.
     store.dispatch(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/state/mouseEventsMiddleware.spec.ts` around lines 146 - 166, Add an
inline clarifying comment in the test (near the pre-activation and the
mouseClickAction(INSIDE_PLOT) dispatch) explaining that because no tooltip
ticks/data are configured, selectActivePropsFromChartPointer will return
undefined so setMouseClickAxisIndex is not dispatched and the tooltip remains
active only because no deactivating action occurs; reference the related symbols
setMouseClickAxisIndex, selectActivePropsFromChartPointer, mouseClickAction and
mouseClickOutsidePlot in the comment so future maintainers understand the test's
intent.
🤖 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/state/mouseEventsMiddleware.spec.ts`:
- Around line 98-106: The new "mouseClickMiddleware – plot bounds guard" test
suite is missing an explicit call to vi.useFakeTimers(), so add a beforeEach
hook inside the describe block (the same block that contains the existing
afterEach with vi.clearAllTimers()) to call vi.useFakeTimers() before each test;
keep the existing afterEach that clears RAF timers (vi.clearAllTimers()) so
autoBatchEnhancer leftovers are cleaned up.
- Around line 146-166: Add an inline clarifying comment in the test (near the
pre-activation and the mouseClickAction(INSIDE_PLOT) dispatch) explaining that
because no tooltip ticks/data are configured, selectActivePropsFromChartPointer
will return undefined so setMouseClickAxisIndex is not dispatched and the
tooltip remains active only because no deactivating action occurs; reference the
related symbols setMouseClickAxisIndex, selectActivePropsFromChartPointer,
mouseClickAction and mouseClickOutsidePlot in the comment so future maintainers
understand the test's intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 974fcb12-8dfa-4532-b611-bc0b89caff09

📥 Commits

Reviewing files that changed from the base of the PR and between 4ad1097 and 8b49300.

📒 Files selected for processing (5)
  • src/state/keyboardEventsMiddleware.ts
  • src/state/mouseEventsMiddleware.ts
  • src/state/tooltipSlice.ts
  • test/chart/AccessibilityLayer.spec.tsx
  • test/state/mouseEventsMiddleware.spec.ts

@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.63%. Comparing base (201d060) to head (1189949).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7115   +/-   ##
=======================================
  Coverage   89.63%   89.63%           
=======================================
  Files         536      536           
  Lines       40484    40509   +25     
  Branches     5523     5528    +5     
=======================================
+ Hits        36286    36311   +25     
  Misses       4190     4190           
  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.

@PavelVanecek
Copy link
Collaborator

I can reproduce the same problem even on this branch

@AbishekRaj2007
Copy link
Contributor Author

Thanks for testing, Could you share which chart type you were using (Line/Bar/Composed) and a minimal reproduction or steps? I want to make sure I'm testing the exact same scenario.

@PavelVanecek
Copy link
Collaborator

I tried the first I found that has a tooltip: https://recharts.github.io/en-US/api/

I can reproduce both on the main branch, and on your branch

@AbishekRaj2007
Copy link
Contributor Author

Thanks, I'll test against that example and update the fix. Could you confirm if you're clicking directly on chart margins/padding vs. completely outside the SVG boundary? That'll help me narrow down which coordinate check is failing.

@PavelVanecek
Copy link
Collaborator

I'm clicking on axis labels

@AbishekRaj2007
Copy link
Contributor Author

Got it axis labels sit in the margin/offset zone outside the inner plot area. My boundary check was using inner dimensions, so those clicks were being treated as outside. Working on including the offset in the check , will push an update shortly.

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/state/mouseEventsMiddleware.ts (1)

29-29: Remove debug logging from middleware path.

Line 29 leaves a console.log in production event handling.

Suggested fix
-    console.log({ chartX, chartY, outerWidth, outerHeight, offset });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/state/mouseEventsMiddleware.ts` at line 29, Remove the leftover
console.log call that prints { chartX, chartY, outerWidth, outerHeight, offset }
from the middleware path in mouseEventsMiddleware.ts; either delete the
console.log or replace it with a proper logger call gated by a debug/verbose
flag (e.g., use existing logging utility or process.env.DEBUG) so production
doesn't emit these event logs, locating the statement that references chartX,
chartY, outerWidth, outerHeight, and offset and removing or wrapping it
accordingly.
🤖 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/state/mouseEventsMiddleware.ts`:
- Around line 31-35: The current isOutside check uses innerWidth + offset.right
and innerHeight + offset.bottom which can be larger than the true SVG when
offset.width/height are clamped; instead compare the pointer's relative
coordinates against the actual outer dimensions. Replace the comparisons that
use chartX/chartY vs innerWidth + offset.right and innerHeight + offset.bottom
with checks that compare relativeCoord.x to outerWidth and relativeCoord.y to
outerHeight (or compute outerWidth = innerWidth + offset.left + offset.right and
outerHeight = innerHeight + offset.top + offset.bottom using the true outer
sizes), keeping the existing left/top negative checks (offset.left/offset.top)
so isOutside uses relativeCoord and outerWidth/outerHeight rather than clamped
inner+offset values.

---

Nitpick comments:
In `@src/state/mouseEventsMiddleware.ts`:
- Line 29: Remove the leftover console.log call that prints { chartX, chartY,
outerWidth, outerHeight, offset } from the middleware path in
mouseEventsMiddleware.ts; either delete the console.log or replace it with a
proper logger call gated by a debug/verbose flag (e.g., use existing logging
utility or process.env.DEBUG) so production doesn't emit these event logs,
locating the statement that references chartX, chartY, outerWidth, outerHeight,
and offset and removing or wrapping it accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b4d23f4-571d-4a8a-a56e-6d696b2e1a30

📥 Commits

Reviewing files that changed from the base of the PR and between 8b49300 and 59d7e72.

📒 Files selected for processing (2)
  • src/state/mouseEventsMiddleware.ts
  • test/state/mouseEventsMiddleware.spec.ts

@AbishekRaj2007
Copy link
Contributor Author

Hi @PavelVanecek, the 33 failing checks appear to be a CI infrastructure issue rather than a problem with this change. All failures occur within ~10 seconds at the pnpm cache restoration step, This seems to be a transient issue with pnpm/action-setup@v4 on GitHub's runners. Could you re-run the failed jobs when you get a chance?

@PavelVanecek
Copy link
Collaborator

Yeah that started yesterday on all builds. I'll fix it

@AbishekRaj2007
Copy link
Contributor Author

AbishekRaj2007 commented Mar 13, 2026

Thanks for confirming! I've pushed the fix , happy to address any review comments once the CI is back up.

@PavelVanecek
Copy link
Collaborator

@AbishekRaj2007 can you please rebase on, or merge latest main? I fixed the CI.

@AbishekRaj2007 AbishekRaj2007 force-pushed the fix/tooltip-deactivate-outside-cartesian-plot branch from 59d7e72 to 1189949 Compare March 22, 2026 12:39
@AbishekRaj2007
Copy link
Contributor Author

@PavelVanecek I have merged with the latest main and pushed

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 (1)
www/src/components/Playground/AnnotationsPanel.tsx (1)

14-16: Update JSDoc to match the new portal/component naming.

The comment still refers to the annotations portal/controller, but the component now renders RechartsDevtoolsPortal. Aligning this text will reduce confusion during maintenance.

✏️ Suggested doc-only update
 /**
- * A panel that renders the Recharts Annotations portal.
- * This is the container where the AnnotationsController UI will be rendered.
+ * A panel that renders the Recharts Devtools portal.
+ * This is the container where the devtools UI will be rendered.
  */

Based on learnings: When making changes to Recharts code, prefer current best practices as described in CONTRIBUTING.md and try to improve code style where relevant to the current task.

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

In `@www/src/components/Playground/AnnotationsPanel.tsx` around lines 14 - 16,
Update the JSDoc at the top of the AnnotationsPanel component so it describes
rendering the RechartsDevtoolsPortal rather than the old "AnnotationsController"
or "annotations portal"; locate the JSDoc block above the AnnotationsPanel
component declaration and replace the outdated wording with a concise
description that this panel is the container where the RechartsDevtoolsPortal UI
will be rendered, keeping tone and style consistent with surrounding comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@www/src/components/Playground/AnnotationsPanel.tsx`:
- Around line 14-16: Update the JSDoc at the top of the AnnotationsPanel
component so it describes rendering the RechartsDevtoolsPortal rather than the
old "AnnotationsController" or "annotations portal"; locate the JSDoc block
above the AnnotationsPanel component declaration and replace the outdated
wording with a concise description that this panel is the container where the
RechartsDevtoolsPortal UI will be rendered, keeping tone and style consistent
with surrounding comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48a606ea-204e-49e9-b574-92ad4b0e9ae3

📥 Commits

Reviewing files that changed from the base of the PR and between 59d7e72 and 1189949.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json
  • src/state/mouseEventsMiddleware.ts
  • src/state/tooltipSlice.ts
  • test/state/mouseEventsMiddleware.spec.ts
  • www/src/components/Playground/AnnotationsPanel.tsx
💤 Files with no reviewable changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/state/tooltipSlice.ts
  • src/state/mouseEventsMiddleware.ts
  • test/state/mouseEventsMiddleware.spec.ts

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.

XAxis interaction triggers tooltip with incorrect activeIndex

2 participants