-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[charts] Add control to the axis highlight #17900
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
Conversation
Deploy preview: https://deploy-preview-17900--material-ui-x.netlify.app/ Updated pages: Bundle size reportTotal Size Change: 🔺+49KB(+0.37%) - Total Gzip Change: 🔺+15.7KB(+0.39%) Show details for 100 more bundles (22 more not shown)@mui/x-charts-pro/ChartContainerPro parsed: 🔺+3.88KB(+2.20%) gzip: 🔺+1.2KB(+2.16%) |
CodSpeed Performance ReportMerging #17900 will degrade performances by 11.34%Comparing Summary
Benchmarks breakdown
|
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.
It seems to be a nice addition and works as expected.
I have some concerns however, regarding the API.
I think I would rather have a more generic api, like onAxisHighlightChange
and highlightedAxes
, where the identifier actually includes the axis dimension
as a part of it.
{
axisId: 123,
dataIndex: 0,
value: 'Pizza',
axis: 'z'
}
Since the X/Y dimension forces us to play by the cartesian charts rules 😆
The highlight can be controlled by using `xAxisHighlight`/`yAxisHighlight` and the `onXAxisInteraction`/`onYAxisInteraction`. | ||
|
||
The `xAxisHighlight`/`yAxisHighlight` are objects `{ axisId, dataIndex, value }`. | ||
If the `dataIndex` is provided, the axis `value` is ignored an computed from the `dataIndex` and `axis.data`. |
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 with
If the
dataIndex
is provided
provided by whom?
The `xAxisHighlight`/`yAxisHighlight` are objects `{ axisId, dataIndex, value }`. | ||
If the `dataIndex` is provided, the axis `value` is ignored an computed from the `dataIndex` and `axis.data`. | ||
|
||
The `onXAxisInteraction`/`onYAxisInteraction` handler are trigger each time pointer moves from one axis value to another. |
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.
The `onXAxisInteraction`/`onYAxisInteraction` handler are trigger each time pointer moves from one axis value to another. | |
The `onXAxisInteraction`/`onYAxisInteraction` handler are triggered each time the pointer moves from one axis value to another. |
The `xAxisHighlight`/`yAxisHighlight` are objects `{ axisId, dataIndex, value }`. | ||
If the `dataIndex` is provided, the axis `value` is ignored an computed from the `dataIndex` and `axis.data`. | ||
|
||
The `onXAxisInteraction`/`onYAxisInteraction` handler are trigger each time pointer moves from one axis value to another. |
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.
Maybe we want to rewrite most text instead
The `onXAxisInteraction`/`onYAxisInteraction` handler are trigger each time pointer moves from one axis value to another. | |
The `onXAxisInteraction`/`onYAxisInteraction` handler are triggered each time the pointer crosses the boundaries between two axes. |
The handler get an array of axis value identifier. | ||
Whereas the controlled value only accept on objects. |
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.
The handler get an array of axis value identifier. | |
Whereas the controlled value only accept on objects. | |
The handler accepts an array of axis value identifiers. | |
Whereas the controlled value only accepts one object. |
Whereas the controlled value only accept on objects. | ||
|
||
For now highlight components assume you use the first axis. | ||
Being able to highlight any axis will arrive in further development. |
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.
doesnt seem to add much from the phrase above it
Being able to highlight any axis will arrive in further development. |
We could do it, if we restrict ourself to axes with a |
packages/x-charts/src/models/axis.ts
Outdated
/** | ||
* The data index if the axis have a `data` property. | ||
*/ | ||
dataIndex: number | null; |
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 LineItemIdentifier
we use dataIndex?: number
. Should we decide on whether to use dataIndex?: number
or dataIndex: number | null
? It might be confusing otherwise.
if (controlledItem.dataIndex !== null) { | ||
return axis.axis[controlledItem.axisId].data?.[controlledItem.dataIndex]; | ||
} | ||
return controlledItem.value; |
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 guess this is what "get priority" means, right?
Why do we need to prioritize data index over value? Can't we just use one or the other?
* The function called when pointer moves from one axis item to another. | ||
* @param {AxisPointerIdentifier[]} newAxes The array of item per axis. | ||
*/ | ||
onXAxisInteraction?: (newAxes: AxisPointerIdentifier[] | null) => void; |
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 call the first argument pointerIds
instead of newAxes
? They aren't axes, they are pointer identifiers.
...store.value, | ||
interaction: { ...store.value.interaction!, pointer: svgPoint }, |
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.
Not sure if spreading the whole store will impact performance negatively, especially since it's being called on pointer move.
And potentially twice if onXAxisInteraction
and onYAxisInteraction
are both set.
What if we move the second argument of selectorChartsInteractionXAxisIndex
and selectorChartsInteractionXAxisValue
into standalone functions and then we provide them to the selector while also calling them here?
export function selectChartsInteractionXAxisValue(x: number | null, xAxes: ComputeResult<ChartsXAxisProps>, xIndex: number | null, id: AxisId | undefined) {
if (x === null || xIndex === null || xAxes.axisIds.length === 0) {
return null;
}
return valueGetter(x, xAxes, xIndex, id);
},
}
export const selectorChartsInteractionXAxisValue = createSelector(
[
selectorChartsInteractionPointerX,
selectorChartXAxis,
selectorChartsInteractionXAxisIndex,
optionalGetAxisId,
],
selectChartsInteractionXAxisValue
);
And then in this file, we would do:
const nextXAxisValue = selectChartsInteractionXAxisValue(svgPoint.x, xAxes, nextXAxisIndex, axisId);
The same would apply for the Y axis and for the X axis index.
What do you think?
|
||
const newStore = { | ||
...store.value, | ||
interaction: { ...store.value.interaction!, pointer: svgPoint }, |
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.
Do we need the exclamation mark? Spreading undefined
or null
into an object is fine:
const a = { ...undefined }
// a = { }
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, but in that case the value of interaction: { ...store.value.interaction, pointer: svgPoint },
could be interaction: { pointer: {x, y} }
(if the interaction is undefined) which is not a valid interaction
value. So TS complains in the selectors
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Based on feedback, I went with the following DX
The callback is get an array, because I plan to use the same one for the tooltip control. And the tooltip is able to support multiple axes so we will need to get an access to all of them |
packages/x-charts/src/models/axis.ts
Outdated
/** | ||
* The axis direction. | ||
*/ | ||
direction: CartesianDirection; | ||
/** | ||
* The axis id. | ||
*/ | ||
axisId: AxisId; |
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.
Do we need the direction? The direction can be derived from the list of axes and the axis ID, right? So isn't the direction a bit redundant here?
It would allow us to have just one AxisItemIdentifier
instead of having to split by cartesian/polar.
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.
With the current implementation, it would be hard, because
const axis = useXAxis(id) ?? useYAxis(id)
On the principle, I agree. The current axis management could be improved to remove this x/y distinction in the storage. But that would be a much larger PR.
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.
But that would be a much larger PR.
Wouldn't it be exporting a useCartesianAxes
and a useCartesianAxis
hooks whose implementation is basically return useXAxis(id) ?? useYAxis(id)
? Or am I missing something?
It would save us from exposing the direction here.
Is there ever a case for a chart with cartesian and polar axes simultaneously? If so, we might have to expose a useAxes
because a user doesn't know the axis type (cartesian or polar) to use the proper hook.
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 there ever a case for a chart with cartesian and polar axes simultaneously?
No there is no case like that.
We could export useCartesianAxis(id)
but we still need useXAxis()
and useYAxis()
because they default to first axis in each direction
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.
we still need useXAxis() and useYAxis() because they default to first axis in each direction
Sure, we can keep it, although the difference in behavior should only be theoretical. I believe we issue a warning when an x-axis has the same ID as a y-axis, so in practice the behavior should be the same as users aren't using the same IDs for different axes.
@@ -123,6 +196,7 @@ export const useChartCartesianAxis: ChartPlugin<UseChartCartesianAxisSignature<a | |||
instance, | |||
params.disableAxisListener, | |||
isInteractionEnabled, | |||
onAxisInteraction, |
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 wrap this in an useEventCallback
?
If onAxisInteraction
is a function that's created on every render, we might get into infinite loops because we run this useEffect
whenever onAxisInteraction
changes, which might call onAxisInteraction
with newAxisPointerInteraction
which is a new array every time. If onAxisInteraction
causes a re-render (e.g., by calling setState
), then we'll get an infinite loop.
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.
We could, but I'm not a big fan of memorizing user functions.
About the inifit loop, I don't think it's an issue since onAxisInteraction
is only called inside pointer events callback
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'm not a big fan of memorizing user functions
Why not?
I don't think it's an issue since onAxisInteraction is only called inside pointer events callback
You're right. It might just be inefficient to be removing and adding event listeners on every re-render. Not sure if we want to address that now.
@@ -47,6 +48,16 @@ export interface UseChartCartesianAxisParameters<S extends ScaleName = ScaleName | |||
* @param {null | ChartsAxisData} data The data about the clicked axis and items associated with it. | |||
*/ | |||
onAxisClick?: (event: MouseEvent, data: null | ChartsAxisData) => void; | |||
/** | |||
* The function called when pointer moves from one axis value to another. |
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 pointer moves from one axis value to another
Is this the only case? Can't it be called if the data changes as well?
I think something like "when the axis value changes" would work better.
Also, we use "axis value" here, but "axis item" below. Are they the same thing? Should we use the same name in both places?
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 the only case? Can't it be called if the data changes as well?
It's the case for performance reasons. Otherwise each pointer move will trigger it.
If user what to have value updates, they should get full control on their events with https://mui.com/x/react-charts/components/#scales
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.
Otherwise each pointer move will trigger it
Sorry, didn't understand why every pointer move would trigger the callback. Why do you say that's the case?
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 misunderstood your initial comment.
I was focusing on the fact I remove the value
to only keep the dataIndex
attribute.
Can't it be called if the data changes as well?
I would be great but not implemented in this PR. I will have a look if the new selector implementation makes it easier to do
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 misunderstood your initial comment.
Sure, no problem.
I would be great but not implemented in this PR. I will have a look if the new selector implementation makes it easier to do
I see. Should we still update the docs so that it's clear that it isn't only pointer move that will cause this event handler to be called?
Also, we use "axis value" here, but "axis item" below. Are they the same thing? Should we use the same name in both places?
I think we also missed the question above
@@ -47,6 +48,16 @@ export interface UseChartCartesianAxisParameters<S extends ScaleName = ScaleName | |||
* @param {null | ChartsAxisData} data The data about the clicked axis and items associated with it. | |||
*/ | |||
onAxisClick?: (event: MouseEvent, data: null | ChartsAxisData) => void; | |||
/** | |||
* The function called when pointer moves from one axis value to another. | |||
* @param {AxisItemIdentifier[]} newAxisInteraction The array of item per axis. |
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.
Does this contain all axis items or only the ones that changed? We should probably specify that
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.
Only those for axis with data
property.
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.
Yeah, but my point is: does newAxisInteraction
contain only values that changed or the new interaction state?
For example, if there's an x-axis and an y-axis and I move the pointer horizontally such that only the x-axis value changes, do I get an array with one or two values?
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.
It contains all the axes with a data
property.
If one axis have a new dataIndex we return all the axes data index with their current highlight index. Such that the array always have the same length/order
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.
Such that the array always have the same length/order
Thanks, that answers my question! Should it be part of the docs? Otherwise I suspect users might have the same doubts I had
## Controlled axis highlight | ||
|
||
The highlight can be controlled by using `highlightedAxis` prop. | ||
Its value can be `null` to remove axis highlight, or an object `{ direction: 'x' | 'y', axisId: string, dataIndex: number }`. |
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.
Its value can be `null` to remove axis highlight, or an object `{ direction: 'x' | 'y', axisId: string, dataIndex: number }`. | |
Its value can be `null` to remove axis highlight, or an object `{ axisId: string, dataIndex: number }`. |
The highlight can be controlled by using `highlightedAxis` prop. | ||
Its value can be `null` to remove axis highlight, or an object `{ direction: 'x' | 'y', axisId: string, dataIndex: number }`. | ||
|
||
The `onAxisInteraction` handler is trigger each time the pointer crosses the boundaries between two axis values. |
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.
The `onAxisInteraction` handler is trigger each time the pointer crosses the boundaries between two axis values. | |
The `onAxisInteraction` handler is triggered each time the pointer crosses the boundaries between two axis values. |
Its parameter is an array of objects `{ direction, axisId, dataIndex }`. | ||
One per axis. |
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.
So, if no axis highlight exists, the argument is an empty array, right?
One per axis.
How does this work? I'm trying the example below and there's an x and a y axes, but onAxisInteraction
only returns:
[
{
"axisId": "x-axis",
"dataIndex": 4
}
]
Which axes are supposed to be shown?
One per axis. | ||
Axes without data are ignored by the handler. | ||
|
||
The handler gets an array of objects. |
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.
Isn't this the same as the content on line 60?
...ts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.ts
Outdated
Show resolved
Hide resolved
...ts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.ts
Outdated
Show resolved
Hide resolved
...ts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.ts
Outdated
Show resolved
Hide resolved
* The controlled axis highlighted. | ||
* Indicates the direction, axis id, and data index to highlight. | ||
*/ | ||
highlightedAxis?: AxisItemIdentifier | null; |
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.
Isn't it a bit weird that the prop that sets the highlighted is called highlightedAxis
, but when it changes, the event handler is called onAxisInteraction
?
Should they be axisInteraction
/onAxisInteraction
or highlightedAxis
/onAxisHighlight
?
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.
My concern is the upcoming tooltip control that will need the same callback to know which axis needs to be displayed.
But we could effectively expose an onAxisHighlight
and trigger another function for the tooltip.
This just open a new question. Should onAxisHighlight
get an array of axis item or just the first one.
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.
My concern is the upcoming tooltip control that will need the same callback to know which axis needs to be displayed.
Sorry, what do you mean?
But we could effectively expose an
onAxisHighlight
and trigger another function for the tooltip.
Ah, do you mean onAxisHighlight
for axis highlights and onTooltipChange
when the tooltip should display different items?
Should onAxisHighlight get an array of axis item or just the first one.
I think that's what I'm trying to get at in my comment below. I think there might be more than one axis highlighted at a time, so we probably need an array.
/** | ||
* The controlled axis item highlighted. | ||
*/ | ||
controlledCartesianAxisHighlight?: AxisItemIdentifier | null; |
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.
Can't we have more than one highlighted axis item?
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 theory it's not that hard. In practice I don't see usecases.
I can think about the crosshair but when following the point, I feel doing store update would be a bad trade off in terms of performances. Bette to do all the computation in the crosshair component itself.
I can also think about someone who would like to highlight multiple values at once. But in that case, it would probably be better to create his own highlight component
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 theory it's not that hard. In practice I don't see usecases.
What about charts with multiple X axes? Can't there be more than one axis highlight at a time?
I can also think about someone who would like to highlight multiple values at once. But in that case, it would probably be better to create his own highlight component
Would that be something that's easy to do? Should we have a demo for it?
I suspect it's somewhat useful. For example, you might want to click a button and highlight some points.
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 about charts with multiple X axes? Can't there be more than one axis highlight at a time?
For me it looks like the worst case scenario with two axis highligh component rendered either on top of each other or fairly close
Would that be something that's easy to do? Should we have a demo for it?
That's a line or a rectangle to render. The logic should not be that different from what the demos in https://mui.com/x/react-charts/components/#scales
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 me it looks like the worst case scenario with two axis highligh component rendered either on top of each other or fairly close
Sure, but shouldn't we support it? I think it's a bit weird that in controlled state the handler provides an array of values and the state itself only accepts a single value. IMO, both should accept an array or a single value.
In this case, since there can be multiple axes, I think we should always use an array. What downsides do you see in this?
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
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.
Left a couple comments
...ts/src/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.ts
Outdated
Show resolved
Hide resolved
@@ -116,7 +116,7 @@ describe('ScatterChart - click event', () => { | |||
}); | |||
}); | |||
|
|||
describe('onItemClick - disabling voronoi', () => { | |||
describe.skipIf(isJSDOM)('onItemClick - disabling voronoi', () => { |
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.
Why do we now need to skip this test?
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.
Initially because I added a pointer down event that triggers the axis highlight. So I needed the SVG coordinates.
But now with JC library it's not needed anymore
@@ -170,7 +186,7 @@ function MarkPlot(props: MarkPlotProps) { | |||
((event) => | |||
onItemClick(event, { type: 'line', seriesId, dataIndex: index })) | |||
} | |||
isHighlighted={xAxisInteractionIndex === index || isSeriesHighlighted} | |||
isHighlighted={highlightedItems[xAxisId]?.has(index) || isSeriesHighlighted} | |||
isFaded={isSeriesFaded} |
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.
Shouldn't we also check highlightedItems[xAxisId]?.has(index)
to see if something is faded?
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.
It's a bit tricky.
We have two king of highlight
- The item interactions. That are handled by highlight scope. They highlight/fade series.
- The axis interactions. They only highlight value son a given axis value.
To fix this we should have a single highlight model. But that would require a breaking 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.
I see. Maybe this is something we should rethink for v9? It's indeed confusing how the different systems interact.
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.
👍 Added to #17955
* The argument contains the identifier for all axes with a `data` property. | ||
* @param {AxisItemIdentifier[]} axisItems The array of axes item identifiers. | ||
*/ | ||
onHighlightedAxisChange: PropTypes.func, |
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.
Why can't we set the highlighted axis for heatmaps?
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.
We pick the decision to do not have axis highlight on heatmap, because it felt like a weird behavior
and never got any request for it.
Woudl you be ok if I remove the onHighlightedAxisChange
from the heatmap?
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.
Would you be ok if I remove the onHighlightedAxisChange from the heatmap?
Yeah 👍 but the heatmap isn't the only chart where this happens. It also happens on ScatterChart
, but not ScatterChartPro
.
@@ -170,7 +186,7 @@ function MarkPlot(props: MarkPlotProps) { | |||
((event) => | |||
onItemClick(event, { type: 'line', seriesId, dataIndex: index })) | |||
} | |||
isHighlighted={xAxisInteractionIndex === index || isSeriesHighlighted} | |||
isHighlighted={highlightedItems[xAxisId]?.has(index) || isSeriesHighlighted} |
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.
Why do we need this special case for mark plot, but not for other plots?
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.
Because a line or an area are not related the the x-axis value.
The only charts with similar behavior would be the bar chart if we allow a highlight par band ;)
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, since the line and area span the whole series, there isn't a way to highlight just one specific x-axis value, is that right?
Does that mean that the scatter should have this feature also since it's a kind of a mark plot?
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, since the line and area span the whole series, there isn't a way to highlight just one specific x-axis value, is that right?
Yes
Does that mean that the scatter should have this feature also since it's a kind of a mark plot?
No because the scatter point all have different x/y values. The line/bar are the only chart that use axies values and so are impacted by axis highlight
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.
To go a bit further, the scatter chart could in theory benefit from it. Because the exact need for onAxisHighlightedChange
to be triggered is the presence of data
property in the axis.
But it's the kind of thing we should have a more precise example in mind to do an usfull implementation
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.
To go a bit further, the scatter chart could in theory benefit from it. Because the exact need for onAxisHighlightedChange to be triggered is the presence of data property in the axis.
Yeah, that was my suspicion. It might be useful for the crosshair functionality, though.
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.
It's easier to create a dedicated componnent that based on the highlighted item get the x/y values and render the crosshair
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.
Looking good! Minor comments
...ternals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianHighlight.selectors.ts
Outdated
Show resolved
Hide resolved
...ls/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.axisHighlight.test.tsx
Outdated
Show resolved
Hide resolved
{ axisId: 'x-axis', dataIndex: 0 }, | ||
{ axisId: 'y-axis', dataIndex: 0 }, |
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.
Why is this test calling onHighlightedAxisChange
with both the x and y axes, but the others are only calling that callback with the x-axis?
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.
Because the y-axis has a data attribute in this test
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.
Great job 💪
The idea is to add an
onXAxisInteraction
that provides meaning full information about where the pointer is compared to x-axes.Implementation
For now it's only trigger by
pointerMove
event. Which has a known limitation: It the developer has some logic that modifies the data theonXAxisInteraction
will not be trigger by the modification.For example, if the pointer moves over the last value of the x-axis. we call
onXAxisInteraction
with this data index.When the last item get removed by some async call, the item does not exist anymore, and we end up in a weird situation.
A potential solution is to apply the same logic when updating axes: compute interaction values before/after the axis modification and trigger the
onXAxisInteraction
if needed (to explore)DX
The
onXAxisInteraction
support multiple axes. That's not super useful for highlight that only accept one. But it will be usefull to control that axis tooltip.The axis interaction contains both
dataIndex
andvalue
. The usecases are the following:I'm not sure it's a good idea to have both. I'm thinking about only supporting the
dataIndex
, because axes withoutdata
are useless for the axis-tooltip. And the axis highlight is overcomplicated and wem behavior can be obtained by implementing the last demo of Custom components - Scales (Coordinate to value)