Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[charts] Add onClick support #11411

Merged
merged 16 commits into from
Feb 1, 2024
Merged

Conversation

alexfauquette
Copy link
Member

@alexfauquette alexfauquette commented Dec 14, 2023

Fixes #10549

This PR is have commits per component impacted. I recommend to have first a look a documentation for the DX aspect, and a per commit review for code quality

Decision

I went with the onAxisClick/onItemClick such that easy usecase are handled with easy solution. The more complex ones are:

  • scatter plot for which I extend the the voronoid handler
  • line and bar chart that are the one using the onAxisClick

For docs, I'm moving to a docs per component to explain edge cases only in the docs of the charts for which they are relevant. It's easier to explain why onClick is more complex than usual in the scatter charts than in a dedicated page


It's a attempt to take best of the different proposed PRs

Current solution

I'm adding an <ChartsHandleOnClick onclick={onClick} /> that can be added anywhere.

This simply contain a useEffect that adds an event listener to the svgRef which will call the onClick callback with the event, plus some data if an item or an axis is active according to our internal state.

Why such a solution

The onClick is not passed to each element, but listen at the <svg/> level for multiple reasons:

  • For scatter charts, points are too small, so it's more interesting to benefit from the voronoid cells. And that can not be done when listening to event on components rendered by the scatter plot.
  • For stacked charts, or charts with band/point axis, it's not clear if you want the data of the item you clicked, or all the items at a given axis position. See the recording of PR [charts] On line chart click #10575 to get a good example about where axis data is more interesting than item ones.

Issues

The main drawback I can see is the mouse pointer.

  1. The event being at the <svg/> level to modify the mouse pointer when there is something to click I would need to modify style by looking at the context to see if there is an interactive item or axis selected.
  2. Since the onClick returns multiple values, I can not know if devs will use only item data, or axis data. So I can't put a meaning full style.

Ideas

I've some ideas of improvement

Idea 1

The first one is to distinguish item and axis click with distinct onItemClick and onAxisClick

This should allow to setup good default styling:

  • If onAxisClick is defined the drawing area get cursor: pointer
  • If onItemClick is defined the drawing area get cursor: pointer when the active item is not null.

Idea 2

A second one is to add onClick to element slots with additional data. Such that for simple use cases, Devs could do

<BarChart
	slotProps={{ 
		bar: { 
			onClick: (event, {dataIndex, seriesId, value}) => {...} 
		}
	}
/>

Note

For now I did not update the Pie chart interaction, but will have to be coherent with other charts API

Changelog

Big thanks to @giladappsforce and @yaredtsy for their contribution on exploring this feature

Breaking change

Pie charts onClick get renamed onItemClick for consistency with other charts click callback.

@alexfauquette alexfauquette added the component: charts This is the name of the generic UI component, not the React module! label Dec 14, 2023
docs/data/charts/tooltip/tooltip.md Outdated Show resolved Hide resolved
docs/data/charts/tooltip/tooltip.md Outdated Show resolved Hide resolved
docs/data/charts/tooltip/tooltip.md Outdated Show resolved Hide resolved
docs/data/charts/tooltip/tooltip.md Outdated Show resolved Hide resolved
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 10, 2024
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

1 similar comment
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 17, 2024
@alexfauquette alexfauquette marked this pull request as ready for review January 19, 2024 15:29
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 24, 2024
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 25, 2024
Signed-off-by: Alexandre Fauquette <[email protected]>
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 29, 2024
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Copy link
Member

@LukasTy LukasTy left a comment

Choose a reason for hiding this comment

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

Superb work! 👍 👏 😍

Leaving some general nitpicks and raising a discussion point below.

Have you considered going with one event handler with a type prop to identify the type on the consumer side? 🤔
I've checked and it seems that both Nivo and Highcharts have only a single click handler. 🤷
At a glance it seems that we are adding quite a bit of complexity and it's a bit unclear if it is warranted. 🤔

docs/data/charts/bars/BarClickNoSnap.js Outdated Show resolved Hide resolved
docs/data/charts/lines/LineClickNoSnap.js Outdated Show resolved Hide resolved
</Box>

<Stack direction="column" sx={{ width: { xs: '100%', md: '40%' } }}>
<Typography>click data</Typography>
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick, but it feels like this section could use a bit extra "eye candy" to be a bit more clear and UX-friendly. 🤔
It also feels like these demos could use a reset button to clear the clicked data. 🤷
cc @noraleonte

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<Typography>click data</Typography>
<Box sx={{display:'flex', justifyContent:'space-between'}}>
<Typography>Click on the chart!</Typography>
<IconButton aria-label="reset"> // reset button
<UndoOutlinedIcon/>
</IconButton>
</Box>

I agree, the reset button would make the experience a lot better 👌 We can do something simple, like this. And some slight improvement of the text

docs/data/charts/pie/pie.md Outdated Show resolved Hide resolved
docs/data/charts/pie/pie.md Outdated Show resolved Hide resolved
packages/x-charts/src/LineChart/MarkPlot.tsx Outdated Show resolved Hide resolved
packages/x-charts/src/ScatterChart/Scatter.tsx Outdated Show resolved Hide resolved
packages/x-charts/src/ScatterChart/Scatter.tsx Outdated Show resolved Hide resolved
packages/x-charts/src/ScatterChart/ScatterChart.tsx Outdated Show resolved Hide resolved
packages/x-charts/src/ScatterChart/ScatterChart.tsx Outdated Show resolved Hide resolved
@alexfauquette
Copy link
Member Author

Have you considered going with one event handler with a type prop to identify the type on the consumer side? 🤔

I made test for line chart:

  • For Recharts: they only add it to the container and behave a similar way to the onAxisClick I'm adding
  • For Nivo: The onClick works with voronoid but not with the vertical slices (you can only click on one point at the time)
  • For highchart, I did not found the docs about how the manage click 🙈

The complexity added by having multiple way of listening click event allows to adapt the chart behavior. For example modifying the pointer on hover if something is listening to the click.

I think the complexity is the largest for those components. Tree view, gauge, and other don't have those axes triggering issues, so they will only get itemClick

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 30, 2024
@LukasTy
Copy link
Member

LukasTy commented Jan 30, 2024

Gotcha. Seems fair. 👌

I think the complexity is the largest for those components. Tree view, gauge, and other don't have those axes triggering issues, so they will only get itemClick

Do you feel that it still makes sense to go for consistent naming (onItemClick vs onClick) even though we know that there will only be one kind of click handler? 🤔

Copy link
Contributor

@noraleonte noraleonte left a comment

Choose a reason for hiding this comment

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

Amazing work! 🍰 🎉

Leaving a small suggestion to follow up on Lukas' comment 👍

</Box>

<Stack direction="column" sx={{ width: { xs: '100%', md: '40%' } }}>
<Typography>click data</Typography>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<Typography>click data</Typography>
<Box sx={{display:'flex', justifyContent:'space-between'}}>
<Typography>Click on the chart!</Typography>
<IconButton aria-label="reset"> // reset button
<UndoOutlinedIcon/>
</IconButton>
</Box>

I agree, the reset button would make the experience a lot better 👌 We can do something simple, like this. And some slight improvement of the text

Comment on lines 69 to 71
code={`// Data from item click
${itemData ? JSON.stringify(itemData, null, 2) : '// click on the chart'}
`}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
code={`// Data from item click
${itemData ? JSON.stringify(itemData, null, 2) : '// click on the chart'}
`}
code={
itemData ? `// Data from item click:
${JSON.stringify(itemData, null, 2)}` : '// The data will appear here'
}

Maybe rephrasing these comments a bit could make the expected outcome a bit clearer 🤔

@alexfauquette
Copy link
Member Author

Do you feel that it still makes sense to go for consistent naming (onItemClick vs onClick) even though we know that there will only be one kind of click handler? 🤔

It feel safer because we could have <PieChart /> getting onLegendClick, end other event handler

@alexfauquette alexfauquette merged commit 9d35151 into mui:next Feb 1, 2024
17 checks passed
@oliviertassinari oliviertassinari added the new feature New feature or request label Feb 3, 2024
@wascou
Copy link
Contributor

wascou commented Feb 4, 2024

Hey thanks for the work, but one important thing is missing :)
Composability!
May you extend this to a nested ScatterPlot in a ResponsiveChartContainer?

@LukasTy
Copy link
Member

LukasTy commented Feb 5, 2024

@wascou have you checked this documentation section?

@alexfauquette
Copy link
Member Author

Thanks @wascou for the heads up, I forgot to document how to use composition with scatter charts. The best documentation for now might be the code itself :)

<ChartsVoronoiHandler
voronoiMaxRadius={voronoiMaxRadius}
onItemClick={onItemClick as ChartsVoronoiHandlerProps['onItemClick']}
/>

<ScatterPlot
slots={slots}
slotProps={slotProps}
onItemClick={disableVoronoi ? (onItemClick as ScatterPlotProps['onItemClick']) : undefined}
/>

The <ResponsiveChartContainer /> shoudl have no impact on the click. And scatter plot has the same behavior as other. But I would recommend to use the click listener of the <ChartsVoronoiHandler />

Both are available only in v7

@wascou
Copy link
Contributor

wascou commented Feb 5, 2024

ah ok, no BC for this one ? I have to wait for a stable v7 then

@wascou
Copy link
Contributor

wascou commented Feb 21, 2024

Ok, works perfectly on my side. Voronoi i interresting but not very accurate when you have a lot of points in your scatter plot. I also put the onItemClick on ScatterPlot. Well done @alexfauquette and @LukasTy !

thomasmoon pushed a commit to thomasmoon/mui-x that referenced this pull request Sep 9, 2024
Signed-off-by: Alexandre Fauquette <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: charts This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[charts] Pass onClick to chart item
7 participants