-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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] Introduce the plugin system #15513
Conversation
Deploy preview: https://deploy-preview-15513--material-ui-x.netlify.app/ |
6566e4b
to
0ffe570
Compare
CodSpeed Performance ReportMerging #15513 will improve performances by 35.98%Comparing Summary
Benchmarks breakdown
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
packages/x-charts-pro/src/context/ChartDataProviderPro/ChartDataProviderPro.tsx
Show resolved
Hide resolved
function ChartProvider(props: ChartProviderProps) { | ||
const { children } = props; | ||
|
||
const { contextValue } = useCharts([], {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we will have to propagate the plugins and to props to generate the internal state and add the different methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example the cartesian chart shoudl enable axis processing with something like
<ChartProvider
plugins={[
useCartesianAxes
]}
>
This plugin would add the x/y axes to the store when doing
const { contextValue } = useCharts([useCartesianAxes], { xAxis, yAxis; ... });
import { UseChartIdSignature } from './useChartId.types'; | ||
import { createChartDefaultId } from './useChartId.utils'; | ||
|
||
export const useChartId: ChartPlugin<UseChartIdSignature> = ({ params, store }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is taken from the tree-view.
It allows the user to pass a custom id that override the default one. We don't use that feature we don't have an id prop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably would be good to add it in the future though
|
||
if (inputApiRef) { | ||
if (inputApiRef.current == null) { | ||
// eslint-disable-next-line react-compiler/react-compiler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's because eslint does not see we are using a ref
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should work once we have facebook/react#29916
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have enough product context, but the plugin itself seems to be correctly migrated
|
||
if (inputApiRef) { | ||
if (inputApiRef.current == null) { | ||
// eslint-disable-next-line react-compiler/react-compiler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should work once we have facebook/react#29916
if (element === null) { | ||
if (svgRef.current === null) { | ||
return undefined; | ||
} | ||
const element = svgRef.current; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong. Technically the current
can change from filled
to null
after the if
, but before before we assign it to element
. I think eslint used to complain about this 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How could it be? Between the if and the assignment there is no thing, So I don't see how the main JS thread could be interupted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I said "technically" 😅
The ref is a mutable ref, so we can technically change it at any time, if there are async processes that set/remove it, then it could change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if there are async processes that set/remove it, then it could change.
Not fully sure, but for me JS is mono-thread, which means the current function is executed up to its end, or an await
. Only after finishing this execution, the event loop will take other tasks to work on. That's why we sometime use the setTimeout(..., 0)
By running a code in async way, we are sure to be executed after the end of React main script
import { useStore } from '../internals/store/useStore'; | ||
import { useSelector } from '../internals/store/useSelector'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we barrel these two together through a internals/store/index.ts
?
packages/x-charts/src/internals/plugins/corePlugins/corePlugins.ts
Outdated
Show resolved
Hide resolved
function ChartProvider(props: ChartProviderProps) { | ||
const { children } = props; | ||
|
||
const { contextValue } = useCharts([], {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean?
import { UseChartIdSignature } from './useChartId.types'; | ||
import { createChartDefaultId } from './useChartId.utils'; | ||
|
||
export const useChartId: ChartPlugin<UseChartIdSignature> = ({ params, store }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably would be good to add it in the future though
useChartId.params = { | ||
id: true, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where are the these params used? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the tree view, they are used to split the props that are forwarded to the DOM and the props that are owned by the plugins and should not be forwarded to the DOM
globalId += 1; | ||
|
||
const initialState = { | ||
// TODO remove when the interaction moves to plugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this TODO still relevant? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because the interaction pluggin is created but not used.
To move interaction behavior I would need to migrate the useAxisListener
, which implies to have access to the drawingArea
.
The confusion comes form the fact I started to implement it, but it's not fully operational
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it
|
||
export function useStore(skipError?: boolean): ChartStore { | ||
const charts = React.useContext(ChartsContext); | ||
// This hook should be removed because user and us should not interact with the store directly, but with public/private APIs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When should we remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the interaction fully move to the plugins, which would allow to remove the hook for axis listener, and voronoi handler which are using the store to update interaction state.
…s.ts Co-authored-by: Jose C Quintas Jr <juniorquintas@gmail.com> Signed-off-by: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com>
Object.assign(initialState, plugin.getInitialState({})); | ||
} | ||
}); | ||
storeRef.current = new ChartStore(initialState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this line the state is directly set because we are in the condition storeRef.current == null
so we directly compute the initial state.
The discussion we had is about storing data from props into the store.
Since it's a ref we will need to use a useEffect
to update the store. For example, the cartesian pluggin could contain to following code to make the xAxis available to selectors
useEffect(() => {
store.update(updateXAxis(xAxis))
}, [xAxis])
Which would cause 2 renders. One when user update their prop. A second one due to the store update.
So we trade potential double render with selectors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small note from the meeting: Unless the chart never uses any selector that contain this data AND the child component (the series? the dot? I don't know what consumes that 😆 ) doesn't have props update or context update when this data changes from the parent.
In which case, we have a first render on the chart and then a second render on the child, but both only render once (except for the mount).
Signed-off-by: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Co-authored-by: Jose C Quintas Jr <juniorquintas@gmail.com>
This PR brings all the typing and pipeline to support plugins in the ChartProvider.
For now the hooks only replace 2 features:
svgRef
chartId
Chart state
The main idea is to have in a provider multiple ref you can ask to interact/render the chart
instance
which contains all the methods to get/update dataplublicApi
that contains the methods we will let people usesvgRef
that already existstore
introduced in the previous PR that manage state accessible by selectors.Typing
The plugin types work as follows:
UseChartXxxSignature
which defines the state, the interface, ...ChartState<[UseChartInteractionSignature, ...]>
and it provide a type that merge the instance, and the store of the plugin signatures you listed