-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
correctly treat pan/zoom events of overlapping axes #22347
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
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.
Thank you for opening your first PR into Matplotlib!
If you have not heard from us in a while, please feel free to ping @matplotlib/developers
or anyone who has commented on the PR. Most of our reviewers are volunteers and sometimes things fall through the cracks.
You can also join us on gitter for real-time discussion.
For details on testing, writing docs, and our review process, please see the developer guide
We strive to be a welcoming and open project. Please follow our Code of Conduct.
This is a relatively large behaviour change. Perhaps for the better, and I'd definitely support this as a default. However, is there a way for users to get the old behaviour and still keep the axes transparencies as they were? It seems to me that we need a manual toggle for all of this? Perhaps a contrived example, but it's possible I make a plot and know that a useful portion of the main axes is covered by the inset and hence zoom over the inset expecting the main axes to re-center. It is also quite likely that an inset is showing a fixed portion of the main axes and I don't want to allow a zoom on it, in which case the zoom may as well pass through. I'm not particulalry opposed to this solution, but want to make sure it is though through. |
@jklymak of course... I tried to give this as much thought as i could π So the old behaviour would mean that irrespective of background and zorder, you always trigger all axes... not sure anyone really wants to go back to that.
|
I guess I'm in favour of a reasonable default as proposed here, but also an explicit state on the Axes that can be queried and set. Probably pan needs to be included? Certainly I don't think a zoom in one axes need be tied to anything on the other axes. We just encouraged that functionality in an external package for now. |
@jklymak you mean a state-variable for disabling zoom&pan on an axes right? I could add it, but I'd need someone to point me to the right location for that. (also I don't know what your naming-conventions for such a variable would be) Maybe it's better to open an issue and do this in another pull-request? |
My personal opinion is we should figure out the api for allowing the user to control the pass through before we change the behaviour in a way the user can't get back to the previous behaviour. We would only do that if we were sure the old behaviour was definitely wrong. |
So I think the API would be:
Then we just need a This would live on |
@jklymak
... here's what I mean as a gif: π code to reproduceimport matplotlib.pyplot as plt
import numpy as np
data = np.mgrid[0:30, 0:30]
f, ax = plt.subplots()
ax2 = f.add_axes((.25,.25,.5,.5))
ax.scatter(*data, c="C0")
ax2.scatter(*data, c="C1") |
Thats understood, but replacing an explicit (though perhaps undesirable) behaviour with an implicit behaviour depending on murky heuristics is how we get into trouble unless we allow the user an escape hatch to specify things explicitly. Given that no one has complained about the existing behaviour until now (that I know of) makes me a bit skeptical that we can just discount the old behaviour as a bug. |
Just a quick drive-by comment: there's already |
@anntzer thx, don't know how I've missed that... (I've indicated the function in the @jklymak OK, I get your concerns here... I've now implemented the API as suggested! Note however that there's not yet a way to get back to the current behavior globally... so at the moment, if you really need the old behavior, you'd have to set the navigation-capture mode for each axes individually... Should |
I wouldn't unless there is an outcry. To be clear, I support the proposed default behaviour, and think it is what most people would prefer, so I think its OK to ask the few folks who may object to set the state manually on each axes. |
@jklymak OK, sounds good to me! ...then I think from my side I did what I can to push this forward... |
Yes, if you rebase, the docs should build again. Setuptools broke us. |
BTW: this definitely needs an API behaviour change note. |
@jklymak I added a note to |
hey, before this gets lost in time... |
Try getting rid of leading spaces on lines 9 and 10. That should solve the doc problem. In the release note, that is. (For the PR itself, I cannot really say.) |
I believe the doc failure is that you need to add your new functions to |
hey all, |
|
Hey all, before this gets lost in time, I'll give it another try...
From my side, the tests are now quite general and all works as expected. |
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.
Some quick feedback to consider. I didn't read the entire message chain, so I may have missed some context on why certain values/words were chosen already. But, my overarching comments that I put more detail inline about are:
- the word "forward" is confusing/ambiguous to me
- we usually use
None
as default instead of "auto" - See if you can re-use your axes-calculating logic between the pan and zoom functions by splitting it into a private helper which would also keep those functions manageable size to see at a glance what is going on.
lib/matplotlib/axes/_base.py
Outdated
@@ -581,6 +581,7 @@ def __init__(self, fig, | |||
xscale=None, | |||
yscale=None, | |||
box_aspect=None, | |||
capture_navigation_events="auto", |
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.
MPL typically uses None
to indicate the default value. Can we use that here? (I haven't followed the rest of the discussion if someone else mentioned to put this string in before)
lib/matplotlib/axes/_base.py
Outdated
|
||
Parameters | ||
---------- | ||
capture : bool or 'auto' |
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.
capture : bool or 'auto' | |
capture : bool or None |
If you take the above comment
lib/matplotlib/backend_bases.py
Outdated
if a.in_axes(event) and a.get_navigate() and a.can_pan(): | ||
# By default, axes with an invisible patch forward events | ||
# while axes with a visible patch capture events. | ||
if a.get_capture_navigation_events() == "auto": |
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 a.get_capture_navigation_events() == "auto": | |
if a.get_capture_navigation_events() is None: |
Sorry this is dragging @raphaelquast - your perseverance is very much appreciated. I think you still have some actionable comments from Greg above. He is usually great about re-reviews, so feel free to ping! |
Hey @jklymak, I got a bit carried away by other tasks recently, but I'll do my best to find some time to finalize this as soon as possible. To summarize, the remaining steps are:Someone has to decide this once and for all:
In the end I think this is a very minor problem since almost nobody will tinker with this option anyways... TODOs:
|
I would lean to |
Sorry for only drive-by reviewing this, but per #22347 (comment) is this distinct enough from |
@jklymak At the moment, It would be possible (and maybe actually preferable?) to supercharge
|
Would that give you all the flexibility you need, solve the simplest case, and be relatively easy to explain to users? If so, maybe that is a good way to go forward? Edit: just to be clear, not blocking on this, just wanted to make sure it had been considered. |
OK, I invested some time to make the axes-trigger logic easier to follow and I simplified the example.
Remaining open points for discussion:
Finally, I realized the more general reason why some of the tests were failing at one stage:
At the moment I've implemented explicit triggers of any relevant twinned-axes to handle this, However, I'd prefer not to tackle this in this PR to avoid any more confusion. |
Two comments from today's call:
|
Hey, thanks for the follow-up on this! I can try to implement this in the next few days, but I'll be afk for all of August,
|
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.
A few more comments where I was confused at some of the logic happening in the code. I think we are getting close.
One major issue is that right-click zoom (i.e. zoom out) does not work on the lower two axes of your example. I get: ValueError: Axis limits cannot be NaN or Inf
(I also only get this in the "y-direction" when holding "x" everything seems to work as expected, but holding "y" throws the ValueError
First, thanks @greglucas for the thorough review! I've implemented all minor comments, but the final major one actually went down a rabbit-hole and I'm slowly starting to understand the initial comment of @timhoffm when I first posted the issue more than a year ago π
OK, now to the actual problem:
It turns out the origin of this is hidden a bit deeper in the code... To clarify:
I have implemented explicit triggers for all twin-axes (see here), which revealed that there's a bit more to fix to get this working properly... My guess is that things go wrong because the event-position As I see it there are 2 ways to solve this:
The question also remains if this should be part of this PR or if we let twin-axes of shared axes remain ignored for now and postpone it until someone actually complains about it...
|
I haven't given this a lot of thought, but this seems like a good approach. If I have located an axes somewhere else and want to "share" limits, then I wouldn't expect a whole new pan/zoom trigger, because the event may not be contained in that axes, so sending a set_xlim() etc... seems to make sense to me. One thing to think about here is if it would make sense to do something along the lines of |
7b21e75
to
ccaa573
Compare
Did you mean to close this? |
@ksunden NO definitely not.. I actually wanted to finally finish it by implementing the last minor changes and merging all to 1 commit. However, in the process I somehow managed to mixup histories (still not really sure how that happened) and now GitHub claims that the branch-histories are unrelated and automatically closed the PR. |
@ksunden Apparently, I ran into an GitHub issue (isaacs/github#361) which caused this PR to be closed and also prevents re-opening this PR. The events that led to this are the following
Really sorry about this... I actually just wanted to finalize this in a clean way and messed up quite a bit π Not sure how we want to proceed with the duplicated PRs now... aside of that, this PR would be ready for a final review! |
PR Summary
This is intended to fix the treatment of pan/zoom events for overlapping axes (e.g. #22324 )
π code to generate the plot of the gif
changes on pan/zoom events
ax.patch.get_visible = True
triggers pan/zoom eventsax.patch.get_visible = False
pass events to axes below (e.g. at lower or equal zorder)other changes
zorder
property is set to thezorder
of the parent axes.in
ax._make_twin_axes()
PR Checklist
Tests and Styling
pytest
passes).flake8-docstrings
and runflake8 --docstring-convention=all
).Documentation
doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).