-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Cache the last mousemove event #1304
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
Instead of totally rethinking this, the other option is to just be more careful about scoping, caching and reusing the last hover data. The more I look at it and think about it, the more I think this might actually be a decent option. As long as it's robust, it seems much simpler than refactoring things to really just accomplish the same thing. |
Yeah, multiple subplots and different trace types were my main concern here. Wanted to get feedback on if this is even reasonable or if there's a magic shortcut before going too far with it. This will probably require some refinement to ensure it's fully robust and only affects what it should affect. |
@alexcjohnson can you continue to review this one for us? |
thanks @bpostlethwaite @rreusser I like the concept of caching the event - but I think rather than re-evaluating it on click, I think it needs to happen after any change to the plot. That way if animation actually changes which point you're over, this will be captured by the displayed hover info (and therefore by the click too). Note that this issue has existed forever (though never bubbled up into an issue...) in the case that something else that wasn't directly initiated by the user changes the plot while the mouse is over it - such as streaming, or restyle/relayout/whatever calls coming from some larger javascript app that the plot is embedded in. Would be great if whatever we do here can solve those cases too.
|
Thanks for the context, @alexcjohnson. I think the path forward then is:
|
I've very slightly modified this to cache the last hover handler call instead. Whenever it runs calcdata, it now executes the last hover callback. I still need to figure out whether hoverdata is being updated at the correct time or not. The gif below shows that it updates the hover to reflect new data even when the mouse hasn't moved. It's not shown here, but if you click repeatedly, the correct (that is, current) curve and point are returned in the click handler. Live example here: https://rreusser.github.io/demos/plotly-unsupported/outdated-hover.html |
In order to finish this, I the main thing I need is hints regarding the cases in which it's still likely to fail. Here's what I can come up with:
|
It seems a little funny to me to have something as presentational as hover take place during the calc stage. And there may be some changes that affect the hover label but don't recalc - like changing trace color? I think Another thing I worry about is whether Anyway the general behavior your demo shows (hover label jumps while animation is smooth) seems totally reasonable. |
Agreed. Pardon the burden, but that's exactly the feedback I was hoping for while it was still only a five-line patch. It would be possible to unpack values from the event so everything is definitely current when used later, but I think some of the references (maindrag?) were only accessible in the closure, which is where the out-of-date reference issue becomes tricky to resolve. I'll iterate on this and see if I can make it a bit more satisfying. Thanks! |
Conflicts: src/plot_api/plot_api.js src/plots/plots.js
@alexcjohnson I've taken another shot at refining this by explicitly appending it to the async queue where needed. I've also added matching of the subplot and unsetting the name of the hovered subplot when the mouse leaves maindrag. This prevents some corner cases like when it would reapply the hover inappropriately after the mouse has fully left the plot. I think it's doing a bit of unnecessary work by applying the hover more often than necessary. It's possible this could be optimized by more carefully relying on flags like |
src/plots/plots.js
Outdated
var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, executeTransitions]; | ||
function rehover() { | ||
plots.rehover(gd); | ||
} |
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 wrapper isn't necessary, as all the items get gd
passed as an arg.
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.
Ahh, thanks. Wondered about that but assumed the opposite.
// When the mouse leaves this maindrag, unset the hovered subplot. | ||
// This may cause problems if it leaves the subplot directly *onto* | ||
// another subplot, but that's a tiny corner case at the moment. | ||
gd._fullLayout._hoversubplot = 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.
Inset plots seem like a not-so-tiny corner case. But does this actually cause problems? Presumably mouseout
happens before mouseover
in that 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.
Yes, which happens first was the question. If mouseover and then mouseout, it would end up unset, but the only time that could happen is if you move exactly one pixel outside of the inset region, which is where it started to seem like a small corner case. The second pixel moved will immediately fix things.
gd._fullLayout._hoversubplot = plotinfo.id; | ||
|
||
gd._fullLayout._rehover(); | ||
|
||
fx.hover(gd, evt, subplot); | ||
fullLayout._lasthover = maindrag; | ||
fullLayout._hoversubplot = subplot; |
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.
Are this fx.hover
and fullLayout._hoversubplot = subplot
still necessary then?
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.
Oops! Good catch. I change this back and forth and accidentally left both. Fixed in the commit below.
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.
Oops. Yeah, you're right. There's still an extra item or two there. Fixing (and looking at the full diff when doing so this time)
@alexcjohnson Thanks for the quick review. I made the changes you pointed out. I can add a test that catches the straightforward cases if you think the proposed fix is starting to look reasonable. It now looks like this, which removes unneeded lines and is a bit more precise with maindrag.onmousemove = function(evt) {
// This is on `gd._fullLayout`, *not* fullLayout because the reference
// changes by the time this is called again.
gd._fullLayout._rehover = function() {
if(gd._fullLayout._hoversubplot === subplot) {
fx.hover(gd, evt, subplot);
}
};
fx.hover(gd, evt, subplot);
// Note that we have *not* used the cached fullLayout variable here
// since that may be outdated when this is called as a callback later on
gd._fullLayout._lasthover = maindrag;
gd._fullLayout._hoversubplot = subplot;
}; |
Yes, this looks great! 🔒 it down with some tests and we should be ready to roll.
Agreed 100% |
Added test to verify it's updated when data changes (and nothing else, i.e. no replot, no mouse event). Used animation to accomplish this. That doesn't exhaustively test every case, but it at least verifies the fix is occurring. |
Could you add a test where an update causes the hover label to disappear, then another update causes one to appear again? Maybe a |
I've expanded the test so that it now:
|
Awesome, thanks! 💃 |
This PR attempts to solve #1274 by caching the mousemove event globally and then using it to re-set the hoverdata when the plot is clicked.
I don't think this is really satisfactory, but I didn't manage to come up with a more pleasant solution. The problem is that animations may change the data, but the hoverdata is only set when the mouse moves, so that if the data changes and you click before moving the mouse, hoverdata won't be correct. The next best solution seems to be to use the click event data to compute the hover data, except
clientX
andclientY
aren't available on the click event; they're only available on the mousemove event. So the solution here is to simply use the most recent mouse move event, if available. This could work, but I'm concerned about what it means to only consider a single mousemove event. Does clientX mean the same thing against all subplots that might call this?I don't think it's a good idea to merge this, but submitting it for feedback so we can come up with a better solution.
cc @etpinard @alexcjohnson @bpostlethwaite