Skip to content

fix: bound tooltip coordinates to chart container in index sync method#6263

Merged
ckifer merged 5 commits intorecharts:mainfrom
shreedharbhat98:fix/tooltip-sync-different-chart-widths-6253
Sep 5, 2025
Merged

fix: bound tooltip coordinates to chart container in index sync method#6263
ckifer merged 5 commits intorecharts:mainfrom
shreedharbhat98:fix/tooltip-sync-different-chart-widths-6253

Conversation

@shreedharbhat98
Copy link
Contributor

@shreedharbhat98 shreedharbhat98 commented Aug 30, 2025

Fix: Bound tooltip coordinates to chart container in index sync method

Ensure synchronized tooltips stay within chart boundaries by clamping coordinates to the receiving chart's viewBox dimensions. Fixes tooltip positioning issues when synchronizing charts with different widths.

Description

When using syncMethod='index' for chart synchronization, tooltip coordinates were passed through directly without considering the receiving chart's container boundaries. This caused tooltips to appear outside chart bounds when synchronizing between charts with different dimensions.

This PR adds coordinate bounding to the index sync method:

  • X coordinates are clamped between viewBox.x and viewBox.x + viewBox.width
  • Y coordinates are clamped between viewBox.y and viewBox.y + viewBox.height
  • All other coordinate properties (for radial charts: angle, clockWise, cx, cy, etc.) are preserved

The fix ensures tooltips always appear within their respective chart containers when synchronizing between charts of different sizes, while maintaining compatibility with all chart types including RadialBarChart.

Related Issue

Fixes #6253 - Tooltip sync for different chart widths

Motivation and Context

When multiple synchronized charts have different dimensions, the tooltip coordinates from one chart may fall outside the boundaries of another chart. This results in tooltips appearing in incorrect positions or outside the visible chart area.

The issue was particularly problematic when:

  • Synchronizing charts with significantly different widths
  • Charts positioned at different locations on the page
  • Using the default syncMethod='index' which previously did no coordinate validation

How Has This Been Tested?

  1. Manual Testing: Verified with the provided CodeSandbox example (https://codesandbox.io/p/sandbox/lm684y) showing tooltip synchronization between charts of different widths

Unit Tests Added

Comprehensive test suite covering:

  1. All Chart Types: LineChart, AreaChart, BarChart, ComposedChart, RadarChart, RadialBarChart
  2. Coordinate Bounding Scenarios:
    • Coordinates exceeding chart bounds are clamped to chart dimensions
    • Coordinates within bounds remain unchanged
    • Property preservation for complex coordinate objects (RadialBarChart)
  3. Cross-Chart Type Synchronization:
    • AreaChart ↔ LineChart synchronization with different dimensions
    • LineChart → RadialBarChart with property preservation
    • BarChart → ComposedChart coordinate bounding
    • ComposedChart → AreaChart synchronization
    • RadarChart → LineChart coordinate transformation
    • RadialBarChart → BarChart with property preservation
    • AreaChart → RadarChart coordinate bounding
    • Multi-chart synchronization with mixed chart types (LineChart + ComposedChart)
  4. Edge Cases:
    • Undefined coordinates handled gracefully
    • Zero-sized viewBox handling
    • Identical chart sizes (should behave normally)
    • Extreme coordinate values with large dimension differences

Screenshots (if appropriate):

Before After
Screenshot 2025-08-30 at 6 13 19 PM Screenshot 2025-08-30 at 6 11 49 PM

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

Ensure synchronized tooltips stay within chart boundaries by clamping
coordinates to the receiving chart's viewBox dimensions. Fixes tooltip
positioning issues when synchronizing charts with different
@shreedharbhat98
Copy link
Contributor Author

@ckifer Hey, Feel free to take a look when available.
Thanks in advance

@codecov
Copy link

codecov bot commented Aug 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.62%. Comparing base (9e7fee9) to head (6168e1f).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6263      +/-   ##
==========================================
- Coverage   96.63%   96.62%   -0.02%     
==========================================
  Files         221      221              
  Lines       20147    20178      +31     
  Branches     4129     4136       +7     
==========================================
+ Hits        19470    19496      +26     
- Misses        670      675       +5     
  Partials        7        7              

☔ 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

Hi @shreedharbhat98 , thanks for the PR. Please add tests - the more the better. There's tons of edge cases here to cover.

@shreedharbhat98
Copy link
Contributor Author

Hi @shreedharbhat98 , thanks for the PR. Please add tests - the more the better. There's tons of edge cases here to cover.

@PavelVanecek Have added some tests to cover all kinds of charts. Feel free to mention if I left any.

Thanks

Comment on lines +1111 to +1114
if (lastCallA && typeof lastCallA === 'object' && 'x' in lastCallA) {
expect(lastCallA.x).toBeLessThanOrEqual(200);
expect(lastCallA.x).toBeGreaterThanOrEqual(0);
}
Copy link
Contributor

@vmizg vmizg Sep 3, 2025

Choose a reason for hiding this comment

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

let's avoid conditionals in tests: if for whatever reason the call is falsy or any of the conditions fail, the test will continue to pass.

not sure what's the preferred method in the codebase, but I'd do something like this:

 const lastCallA = spyA.mock.calls[spyA.mock.calls.length - 1][0];

// assuming lastCallA is of type `any`
expect(lastCallA?.x).toBeLessThanOrEqual(200);
expect(lastCallA?.x).toBeGreaterThanOrEqual(0);

...as well as in all the rest of the test cases

the exception would be of course if we're testing the opposite behaviour, i.e. if we want it to be called without arguments or with undefined, I'd then separate that into a different set of test cases that assert expect(lastCallA.x).toBeUndefined(), etc

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1. If the conditions are necessary for typescript then please use type assertions and throw. See assertNotNull for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@PavelVanecek I have refactored the tests and also the logic. Back to you for now. 🏓

…ove coordinate bounding

- Replace conditional assertions with explicit expect statements
- Remove all if/else blocks that could cause silent test failures
- Use expect().toBeDefined(), expect().not.toBeNull(), and expect().toHaveProperty() for robust assertions
@PavelVanecek PavelVanecek requested a review from Copilot September 5, 2025 12:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes tooltip positioning issues during chart synchronization by ensuring tooltip coordinates are bounded within the receiving chart's container dimensions. The fix specifically addresses the syncMethod='index' case where tooltips could appear outside chart boundaries when synchronizing between charts of different sizes.

Key changes:

  • Added coordinate bounding logic for x and y coordinates in the index sync method
  • Preserved all other coordinate properties for compatibility with radial charts
  • Added comprehensive test coverage for various chart type combinations and edge cases

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/synchronisation/useChartSynchronisation.tsx Implements coordinate bounding logic to clamp x/y coordinates within viewBox dimensions while preserving other properties
test/component/Tooltip/Tooltip.sync.spec.tsx Adds extensive test suite covering coordinate bounding for all chart types and cross-chart synchronization scenarios

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

const boundedCoordinate = {
...otherCoordinateProps,
...(typeof x === 'number' && {
x: Math.max(viewBox.x, Math.min(x, viewBox.x + viewBox.width)),
Copy link

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

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

The x coordinate bounding logic is incorrect. It should clamp between viewBox.x and viewBox.x + viewBox.width - 1, or use <= comparison, since viewBox.x + viewBox.width represents the first pixel outside the bounds.

Suggested change
x: Math.max(viewBox.x, Math.min(x, viewBox.x + viewBox.width)),
x: Math.max(viewBox.x, Math.min(x, viewBox.x + viewBox.width - 1)),

Copilot uses AI. Check for mistakes.
x: Math.max(viewBox.x, Math.min(x, viewBox.x + viewBox.width)),
}),
...(typeof y === 'number' && {
y: Math.max(viewBox.y, Math.min(y, viewBox.y + viewBox.height)),
Copy link

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

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

The y coordinate bounding logic is incorrect. It should clamp between viewBox.y and viewBox.y + viewBox.height - 1, or use <= comparison, since viewBox.y + viewBox.height represents the first pixel outside the bounds.

Suggested change
y: Math.max(viewBox.y, Math.min(y, viewBox.y + viewBox.height)),
y: Math.max(viewBox.y, Math.min(y, viewBox.y + viewBox.height - 1)),

Copilot uses AI. Check for mistakes.
@PavelVanecek
Copy link
Collaborator

Is the -1 important as Copilot says? Tooltip will bleed into the margins a little I suppose.

@ckifer
Copy link
Member

ckifer commented Sep 5, 2025

Should be an improvement either way for now

@ckifer ckifer merged commit 21a7bf5 into recharts:main Sep 5, 2025
19 of 20 checks passed
@shreedharbhat98
Copy link
Contributor Author

shreedharbhat98 commented Sep 5, 2025

Is the -1 important as Copilot says? Tooltip will bleed into the margins a little I suppose.

@PavelVanecek Not sure, While testing it, I did not feel it. Tooltip perfectly appeared with in the viewbox itself.
Let me know if you want me to dig deeper and test it out again. Would be happy to do that.
Thanks!

@shreedharbhat98 shreedharbhat98 deleted the fix/tooltip-sync-different-chart-widths-6253 branch September 5, 2025 14:06
@PavelVanecek
Copy link
Collaborator

If you couldn't see it while testing then it's all good 👍 the AI is often hallucinating.

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.

Regression: tooltip position synced incorrectly for varying width charts

5 participants