Skip to content

Conversation

JCQuintas
Copy link
Member

@JCQuintas JCQuintas commented Jul 2, 2025

The boundaries of some elements were outside of the drawing area, which made the isElementInside consider them outside.

@JCQuintas JCQuintas self-assigned this Jul 2, 2025
@JCQuintas JCQuintas added type: bug It doesn't behave as expected. plan: Pro Impact at least one Pro user. scope: charts Changes related to the charts. labels Jul 2, 2025
@mui-bot
Copy link

mui-bot commented Jul 2, 2025

Deploy preview: https://deploy-preview-18651--material-ui-x.netlify.app/

Bundle size report

Total Size Change: ▼-6.1KB(-0.05%) - Total Gzip Change: ▼-1.89KB(-0.05%)
Files: 122 total (0 added, 0 removed, 21 changed)

Show details for 100 more bundles (22 more not shown)

@mui/x-charts-pro/ChartZoomSliderparsed: 🔺+29B(+0.04%) gzip: 🔺+13B(+0.05%)
@mui/x-charts-pro/ChartContainerProparsed: ▼-320B(-0.18%) gzip: ▼-99B(-0.18%)
@mui/x-charts-pro/ChartDataProviderProparsed: ▼-320B(-0.19%) gzip: ▼-97B(-0.18%)
@mui/x-charts-pro/FunnelChartparsed: ▼-320B(-0.13%) gzip: ▼-119B(-0.16%)
@mui/x-charts-pro/Heatmapparsed: ▼-320B(-0.14%) gzip: ▼-93B(-0.13%)
@mui/x-charts-pro/PieChartProparsed: ▼-320B(-0.14%) gzip: ▼-103B(-0.14%)
@mui/x-chartsparsed: ▼-306B(-0.10%) gzip: ▼-92B(-0.10%)
@mui/x-charts-pro/RadarChartProparsed: ▼-306B(-0.15%) gzip: ▼-105B(-0.17%)
@mui/x-charts/BarChartparsed: ▼-306B(-0.15%) gzip: ▼-90B(-0.14%)
@mui/x-charts/ChartContainerparsed: ▼-306B(-0.21%) gzip: ▼-95B(-0.21%)
@mui/x-charts/ChartDataProviderparsed: ▼-306B(-0.22%) gzip: ▼-88B(-0.20%)
@mui/x-charts/Gaugeparsed: ▼-306B(-0.22%) gzip: ▼-99B(-0.22%)
@mui/x-charts/LineChartparsed: ▼-306B(-0.14%) gzip: ▼-91B(-0.13%)
@mui/x-charts/PieChartparsed: ▼-306B(-0.16%) gzip: ▼-97B(-0.16%)
@mui/x-charts/RadarChartparsed: ▼-306B(-0.16%) gzip: ▼-99B(-0.17%)
@mui/x-charts/ScatterChartparsed: ▼-306B(-0.16%) gzip: ▼-104B(-0.17%)
@mui/x-charts/SparkLineChartparsed: ▼-306B(-0.15%) gzip: ▼-87B(-0.14%)
@mui/x-charts-proparsed: ▼-291B(-0.08%) gzip: ▼-99B(-0.09%)
@mui/x-charts-pro/BarChartProparsed: ▼-291B(-0.12%) gzip: ▼-80B(-0.10%)
@mui/x-charts-pro/LineChartProparsed: ▼-291B(-0.11%) gzip: ▼-78B(-0.09%)
@mui/x-charts-pro/ScatterChartProparsed: ▼-291B(-0.12%) gzip: ▼-83B(-0.11%)
@mui/x-charts-pro/ChartsToolbarProparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsAxisHighlightparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsClipPathparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsGridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLabelparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLegendparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsOverlayparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsReferenceLineparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsSurfaceparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsTextparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsTooltipparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsXAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsYAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/Toolbarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-gridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-premiumparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-premium/DataGridPremiumparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-proparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-pro/DataGridProparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid/DataGridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickersparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-proparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDateFnsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDateFnsJalaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDayjsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterLuxonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentHijriparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentJalaaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangeCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerDayparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerDay2parsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopDateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/LocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileDateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputDateRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputDateTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/PickersRangeCalendarHeaderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputDateRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputDateTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/StaticDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/TimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsBaseparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsJalaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDayjsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterLuxonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentHijriparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentJalaaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateTimeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DayCalendarSkeletonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopDatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopDateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DigitalClockparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/LocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileDatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileDateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MonthCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MultiSectionDigitalClockparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickerDay2parsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickersActionBarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickersCalendarHeaderparsed: 0B(0.00%) gzip: 0B(0.00%)

Details of bundle changes

Generated by 🚫 dangerJS against 11a0890

Copy link

codspeed-hq bot commented Jul 2, 2025

CodSpeed Performance Report

Merging #18651 will not alter performance

Comparing JCQuintas:fix-area-chart (11a0890) with master (ce72e9b)

Summary

✅ 9 untouched benchmarks

@alexfauquette
Copy link
Member

I didn't re-use data-drawing-container as that currently has other meanings as well

Which part of the meaning is an issue?

@JCQuintas
Copy link
Member Author

I didn't re-use data-drawing-container as that currently has other meanings as well

Which part of the meaning is an issue?

I thought it was going to allow the elements to overflow the drawing area, but we clip them regardless 😆

Will use the original one instead then.

@JCQuintas
Copy link
Member Author

threw in a test to prevent regressions 😄

@alexfauquette
Copy link
Member

I don't get why we need the isElementInside() method. I tried removing it, and it seems to work #18654

@JCQuintas
Copy link
Member Author

I don't get why we need the isElementInside() method. I tried removing it, and it seems to work #18654

It is to make sure the slider handlers work. Else they interfere with the charts pan handlers

@JCQuintas
Copy link
Member Author

Long story short-ish, in the chart we release pointer capture in some instances to ensure touch users can move the finger and get multiple tooltips.

This can cause interference with both the zoom&pan and the sliders.

We can't use isPointInside(pointer.x/y) because it would disable interactions where we move the pointer outside, eg: pan/pinch

So we need a way to know that the current target element is inside the drawing area.

@JCQuintas
Copy link
Member Author

An alternative is to have the data-attributes on the sliders instead. As was the original solution here: #17480 (comment)

@@ -81,7 +81,7 @@ function BarPlot(props: BarPlotProps) {
const classes = useUtilityClasses();

return (
<BarPlotRoot className={classes.root}>
<BarPlotRoot data-drawing-container className={classes.root}>
Copy link
Member

Choose a reason for hiding this comment

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

Should this be set on the parent? Otherwise when using composition data-drawing-container will always be included, even if you don't want it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why wouldn't you want it?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure, but couldn't this cause a breaking change if someone isn't expecting this behavior to change? From now on, everything inside BarPlot, LinePlot and AreaPlot will be considered inside the drawing area, right?

Also, it's different from the pattern set for MarkPlot and ScatterPlot.

@bernardobelchior
Copy link
Member

It is to make sure the slider handlers work. Else they interfere with the charts pan handlers

What was preventing them from working when using isPointInside?

@bernardobelchior
Copy link
Member

bernardobelchior commented Jul 2, 2025

Also, I have a small question regarding isElementInside: as far as I understand, it checks whether element is entirely within the drawing area, why?

For example, in handlePanStart from usePanOnDrag, why do we need to check if the element, which in this case is the SVG element, is entirely within the drawing area? Wouldn't it be enough to check if the pointer itself is within the drawing area?

@JCQuintas
Copy link
Member Author

@bernardobelchior some of your questions were answered in this: #18651 (comment)

But to be more exact:

Also, I have a small question regarding isElementInside: as far as I understand, it checks whether element is entirely within the drawing area, why?

Initially I had it as a partial, though I ran into some troubles which are probably my own fault 😅
We could indeed do a partial check if points at top-right || top-left || bottom-right || bottom-left is inside.

For example, in handlePanStart from usePanOnDrag, why do we need to check if the element, which in this case is the SVG element, is entirely within the drawing area?

It is only the SVG when you grab the svg, otherwise it is the bar, or the mark, or anything that can capture a pointer.

@bernardobelchior
Copy link
Member

It is only the SVG when you grab the svg, otherwise it is the bar, or the mark, or anything that can capture a pointer.

Ah, you're right. In the case of the area, it seems it's trying to check whether the area is entirely within the drawing area, which it isn't because it's zoomed in. That's the reason this bug exists, right?

Can't we check if the pointer itself is within the drawing area? Why do we need to check the element instead?

@JCQuintas
Copy link
Member Author

Can't we check if the pointer itself is within the drawing area? Why do we need to check the element instead?

We can't use isPointInside(pointer.x/y) because it would disable interactions where we move the pointer outside, eg: pan/pinch

@bernardobelchior
Copy link
Member

Can't we check if the pointer itself is within the drawing area? Why do we need to check the element instead?

We can't use isPointInside(pointer.x/y) because it would disable interactions where we move the pointer outside, eg: pan/pinch

I see, makes sense 👍 thanks for the explanation.

@alexfauquette
Copy link
Member

If I resume what I understood:

  • The isElementInside is used at two places:
    • One in the usePanOnDrag for the handlePanStart. In that case, the isPointInside(pointer.x/y) could be enough, because the pointer is in the drawing area when pan starts.
    • One in the useChartCartesianAxis for removing the event capture done by the zoom slider which does not rely on the gesture library.

For the second point, I don't get the reason why we need this releasePointerCapture(). I tried removing it and did not see anything weird on my desktop or my phone.

I don't get what is a "section" and how to reproduce this bug

// Release the pointer capture if we are panning, as this would cause the tooltip to
// be locked to the first "section" it touches.

@JCQuintas
Copy link
Member Author

  • useChartCartesianAxis

It mostly doesn't do much when zoom in enabled, unless you already panned to the zoom border. On regular charts it allows you to slide your finger over the tooltips

With the releasePointerCapture

Screen.Recording.2025-07-02.at.15.32.56.mov

Without

Screen.Recording.2025-07-02.at.15.33.26.mov

@alexfauquette
Copy link
Member

Ok, because the pointer get capputred, it can not enter/leave any of the item.

I got confused because I was doing tests on the zoom-and-pan docs page 🙈

This solution still have some hard edges. For example let's pick a bar chart with zoom.

  1. If you pointer down inside a rectangle and move. Thanks to instance.isElementInside(target) being true, the pointer capture get released, and item highlight can work.
  2. If you pointer down outside of a bar rectangle. The target is the SVG component. So instance.isElementInside(target) and item interaction is not available.

On the first example I still do not understand why zoom panning is still working outside of the chart even though the pointer capture got removed

Another example is If you pointer down on a chart, you can not interact with another chart before doing pointer up.

In my opinion the root cause is that we capture pointer event in case where it's not needed.

@JCQuintas
Copy link
Member Author

In my opinion the root cause is that we capture pointer event in case where it's not needed.

Modern browsers implicitly capture the element in pretty much every occasion as far as I remember, only the release has to be explicit if you want the pointer to be released before the next pointerup event.

@JCQuintas
Copy link
Member Author

I've removed the isElementInside and reverted to using the data-...-slider attribute so we can filter out specific interactions for now.

If there is a regression the tests should pick it up at least.

Copy link
Member

@alexfauquette alexfauquette left a comment

Choose a reason for hiding this comment

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

Seems to fix the issue without potentially opening future breaking change 👍

@JCQuintas JCQuintas merged commit c4abca2 into mui:master Jul 2, 2025
23 checks passed
@JCQuintas JCQuintas deleted the fix-area-chart branch July 2, 2025 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plan: Pro Impact at least one Pro user. scope: charts Changes related to the charts. type: bug It doesn't behave as expected.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants