Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

ENH: Scroll to zoom#30405

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
timhoffm merged 11 commits intomatplotlib:mainfromtimhoffm:scroll-to-zoom
Sep 10, 2025
Merged

ENH: Scroll to zoom#30405

timhoffm merged 11 commits intomatplotlib:mainfromtimhoffm:scroll-to-zoom
Sep 10, 2025

Conversation

@timhoffm
Copy link
Member

@timhoffmtimhoffm commentedAug 7, 2025
edited
Loading

Implements a minimal version of#20317, in particular#20317 (comment):

When any of the axes manipulation tools is active (pan or zoom tool), a mouse scroll results in a zoom towards the cursor, keeping aspect ratio.

I've decided to require an active manipulation tool, so that without any active tool the plot cannot be changed (accidentally) - as before. For convenience, scroll-to-zoom is allowed with both the zoom and pan tools. Limiting further feels unnecessarily restrictive.

Zooming is also limited to not having a modifier key pressed. This is because we might later want to add scroll+modifiers for other operations . It's better for now not to react to these at all to not introduce behaviors we later want to change.

Closes#28412.

Copy link
Member

@QuLogicQuLogic left a comment

Choose a reason for hiding this comment

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

This works pretty well as I would expect.

The only minor annoyance is that every scroll event is pushed to the view history, when it might be nicer to group them a bit, but I'm not sure we have the architecture for that, and could wait for a followup.

@ianhi
Copy link
Contributor

An optional feature I really like that I have in mpl-pan-zoom is to autocenter on the plot if you zoom out by a lot. This is what programs like gimp do, it makes it so you don't completely lose track of where the data extents are:https://github.com/mpl-extensions/mpl-pan-zoom/blob/158c68bc3ba5bab785b0a7cb4719774af0a632ac/mpl_pan_zoom/_zoom.py#L76

A second note is that for the web/notebook backends this basically has to capture scrolls if you want it to work properly. Would it be possible to add support for ipympl by adding something like this:https://github.com/mpl-extensions/mpl-pan-zoom/blob/158c68bc3ba5bab785b0a7cb4719774af0a632ac/mpl_pan_zoom/_zoom.py#L32-L33 otherwise this will result in behavior like this:matplotlib/ipympl#222

@ianhi
Copy link
Contributor

A final thought. I haven't tested this but it may be important to test this on non-euclidean projections. For example see this issue:mpl-extensions/mpl-pan-zoom#10

@timhoffm
Copy link
MemberAuthor

A final thought. I haven't tested this but it may be important to test this on non-euclidean projections. For example see this issue:mpl-extensions/mpl-pan-zoom#10

Indeed. In non-rectilinear cases it is not correct to just reduce the data limits. I've limited this functionality to rectilinear Axes for now. I suspect, every projection needs its own recipe. - For example polar plots should not change the theta limits at all.

@anntzer
Copy link
Contributor

I think this doesn't support scales other than linear for now (well, I guess it depends on the exact behavior one expects); I suspect the proper behavior would be to perform the zooming in pixel coordinates rather than in data coordinates?

re: view history; I guess a "relatively" simple fix would be to record whether the last history entry pushed into the stack was from a scroll-zoom, and if so and if the current entry being pushed is also a scroll-zoom (optionally: a scroll zoom from the same point), then pop the last entry before pushing the new one?

@timhoffm
Copy link
MemberAuthor

I have no time to work on this further in the near future.

I believe the current state is good enough for a start, given that a vast of majority of plots are rectilinear Axes with linear scales:

  • limiting to rectilinear Axes prevents really awkward behavior.
  • zooming on nonlinear scales may not be ideal, but is bearable.
  • batching zoom in the history would be nice but is not essential

I propose to merge the PR as. Improvements by others are welcome but could also be done in follow-up PRs.

@ianhi
Copy link
Contributor

ianhi commentedAug 17, 2025
edited
Loading

I propose to merge the PR as. Improvements by others are welcome but could also be done in follow-up PRs.

A second note is that for the web/notebook backends this basically has to capture scrolls if you want it to work properly. Would it be possible to add support for ipympl by adding something like this:

If this is merged I can commit to figuring what, if any, follow ups are needed here and/or in ipympl to make this feature work for webbackends

This PR adds a good and extremely useful thing, and feels to me to be standard behavior among plotting libraries now.

timhoffm reacted with heart emoji

@QuLogic
Copy link
Member

I wrote this to try and handle non-linear scales:

diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.pyindex 815c61a0ee..10634d68a1 100644--- a/lib/matplotlib/backend_bases.py+++ b/lib/matplotlib/backend_bases.py@@ -2593,10 +2593,11 @@ def scroll_handler(event, canvas=None, toolbar=None):          xmin, xmax = ax.get_xlim()         ymin, ymax = ax.get_ylim()+        xmin, ymin = ax.transScale.transform((xmin, ymin))+        xmax, ymax = ax.transScale.transform((xmax, ymax)) -        # mouse position in data coordinates-        x = event.xdata-        y = event.ydata+        # mouse position in scaled (e.g., log) data coordinates+        x, y = ax.transScale.transform((event.xdata, event.ydata))          scale_factor = 1.0 - 0.05 * event.step         new_xmin = x - (x - xmin) * scale_factor@@ -2604,6 +2605,10 @@ def scroll_handler(event, canvas=None, toolbar=None):         new_ymin = y - (y - ymin) * scale_factor         new_ymax = y + (ymax - y) * scale_factor +        inv_scale = ax.transScale.inverted()+        new_xmin, new_ymin = inv_scale.transform((new_xmin, new_ymin))+        new_xmax, new_ymax = inv_scale.transform((new_xmax, new_ymax))+         ax.set_xlim(new_xmin, new_xmax)         ax.set_ylim(new_ymin, new_ymax)

If that makes sense, should I push that here?

@timhoffm
Copy link
MemberAuthor

timhoffm commentedAug 19, 2025
edited
Loading

Looks good, but I can’t test right now. If somebody has verified that this is the „canonically expected“ behavior e.g. on a log scale, you are welcome to push to this branch.

@scottshambaugh
Copy link
Contributor

3D axes are going to get pretty distorted with xy scaling like this, could you check for a zaxis and either extend the zooming to that or block it altogether?

@QuLogic
Copy link
Member

Only rectilinear Axes are affected at this point.

Copy link
Member

@QuLogicQuLogic left a comment

Choose a reason for hiding this comment

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

This seems to work pretty well; I can push#30405 (comment) as a separate PR if necessary.

@timhoffm
Copy link
MemberAuthor

@QuLogic I‘m fine either way. You can take over this PR or add the transformation as a separate PR afterwards.

@dstansby
Copy link
Member

I had a quick play, and on linear scales this is super neat!

On a double log scale (or a single too), it's very easy to hit these warnings when zooming:

/Users/dstansby/software/mpl/matplotlib/test.py:9:UserWarning:Attempttosetnon-positivexlimonalog-scaledaxiswillbeignored.plt.show()/Users/dstansby/software/mpl/matplotlib/test.py:9:UserWarning:Attempttosetnon-positiveylimonalog-scaledaxiswillbeignored.plt.show()

which probably shouldn't be happening. With#30405 (comment) applied though, this isn't an issue, and the zooming looks more natural to me because the data points stay rigid instead of stretching about. So I think it makes sense to apply#30405 (comment) before this is merged.

@timhoffm
Copy link
MemberAuthor

I've included push#30405 (comment). That's indeed much better for nonlinear scales. Thanks@QuLogic! 🚀

I've additionally increased the zoom step from 5% to 10%. With 5% it felt I had to scroll a lot to zoom into any part of the plot. With 10% there are still 7 scroll steps to get a factor of ~2. That's definitively enough granularity. Note, I also checked outbokeh and they have a ~20% step, meaning 3 steps for a factor ~2.

Question: During testing I was somewhat annoyed that I always had to select a tool before the scroll wheel had any effect - or rather I scrolled and was annoyed that nothing happened. Maybe that's a biased use case, but I'm wondering whether we could generally activate scroll-to-zoom independent of the tool state. - The argument not to do this was to ensure the plot was not modified unintentionally. But OTOH how often do you scroll unintentionally? Also, we have "undo" and "reset to original view" so that one could recover if that really happens.

@tacaswell
Copy link
Member

I propose tying it to the tool for 3.11 and making it always-on in 3.12 just in case there is some pathological stress-cases we are not thinking of yet.

Having it opt-in for a while will be enough time for people to find and report those cases.

@tacaswell
Copy link
Member

The other reason not to make it alwasy on by default (at least not without warning) is that users may have hooked their own things up to scroll and we do not want to conflict with them on day 0.

timhoffm reacted with thumbs up emoji

timhoffmand others added6 commitsSeptember 5, 2025 00:30
Implements a minimal version ofmatplotlib#20317, in particular https://github.com/matplotlib/pull/20317#issuecomment-2233156558:When any of the axes manipulation tools is active (pan or zoom tool), amouse scroll results in a zoom towards the cursor, keeping aspect ratio.I've decided to require an active manipulation tool, so that without anyactive tool the plot cannot be changed (accidentally) - as before.For convenience, scroll-to-zoom is allowed with both the zoom and pantools. Limiting further feels unnecessarily restrictive.Zooming is also limited to not having a modifier key pressed. This isbecause we might later want to add scroll+modifiers for other operations. It's better for now not to react to these at all to not introducebehaviors we later want to change.
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
@timhoffm
Copy link
MemberAuthor

Hopefully fixed docs through a rebase.

@anntzer
Copy link
Contributor

I would really not want to have scroll by default for two reasons: 1) I indeed have scroll_event hooked up to other stuff quite often, and 2) on a mac magicmouse it is indeed quite easy to scroll accidentally.

xmin,xmax=ax.get_xlim()
ymin,ymax=ax.get_ylim()
xmin,ymin=ax.transScale.transform((xmin,ymin))
xmax,ymax=ax.transScale.transform((xmax,ymax))
Copy link
Contributor

Choose a reason for hiding this comment

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

You can save a transform call with(xmin, ymin), (xmax, ymax) = ax.transScale.transform([(xmin, ymin), (xmax, ymax)]); ditto below.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Done. But out of curiosity: Are transform calls that expensive? I would have though that they are rather cheap, in particular compared todraw_idle(). The single- transform call is a bit less easy to read.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not really, so I don't really mind either way, but I actually find transforming all at once easier to read ("I do a global transformation of coordinate systems, do some math, then again globally transform back").

@jklymak
Copy link
Member

The other reason not to make it alwasy on by default (at least not without warning) is that users may have hooked their own things up to scroll and we do not want to conflict with them on day 0.

I'll note that on my Mac, none of the graphics packages I use automatically have scroll attached to zoom, though some of them use a modifier key: Adobe products option-scroll to zoom in or out and Inkscape uses Ctrl-scroll. Scroll pans in most packages I use. However I use a Magic Mouse, and I don't have access to a scroll wheel, so maybe my understanding of what is proposed here is incorrect. Are there graphics packages that use scroll-to-zoom by default?

xmin,ymin=ax.transScale.transform((xmin,ymin))
xmax,ymax=ax.transScale.transform((xmax,ymax))
(xmin,ymin), (xmax,ymax)=ax.transScale.transform(
[ax.get_xlim(),ax.get_ylim()])
Copy link
Contributor

Choose a reason for hiding this comment

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

You would need a transpose here (it's (xmin, ymin), (xmax, ymax); not (xmin, xmax), (xmin, ymax)).

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Whoops. Good catch. Then let's keep this in two steps. - Force pushed.

@timhoffm
Copy link
MemberAuthor

Finding the right operation for zoom is not trivial.

  • Most graphics tools use wheel for vertical scrolling and Ctrl+Wheel for zoom. Not sure why they do it; either because they find it correct, it's what others do or because of a technical reason - note that they all operate on finite canvases and have widgets with scroll bars attached - it could be that with scoll bars wheel=vertical scroll is the easy or right thing.
  • bokeh does nothing by default (i.e. the wheel scrolls the embedding web page). They have a dedicated "Wheel zoom" button. If activated, they zoom on wheel only. This button makes sense since bokeh is typically embedded in webpages and they don't want to steal the wheel event from the page unless explicitly desired by the user. But if they take over, they don't use any further modifiers.
  • As a full-window application, Google Maps does not use modifiers either.

What can we learn from them?

In our context, the canvas has no scollbars and there is no outer context which needs to be accounted for, so there is no particular other use for a wheel-only event (I would refrain from assigning it to vertical scroll). So wheel-only could either be zoom or a noop.
Conversely, i believe there are to valid approaches which action should zoom:

  • Use wheel-only on the basis that nothing else needs it and it's easier than using an additional modifier (this is the Google Maps argument)
  • Use Ctrl+Wheel on the basis that most other tools use Ctrl+Wheel (for whatever reason)
srkunze reacted with thumbs up emoji

@timhoffm
Copy link
MemberAuthor

After thinking a bit more, I suggest to go with this approach: Use "Ctrl+Wheel" but keep it always on, i.e. independent of any tool status.

Rationale:

  • Avoid ambiguity: Ctrl+Wheel is overwhelmingly associated with zooming. - If somebody does Ctrl+Wheel they most likely want to zoom.
  • Be defensive: We could always later make Wheel-only also zoom if we come to the conclusion that makes sense.
  • Usability: Ctrl+Wheel is harder to trigger accientally. Therefore we can afford to make it always-on.

I will change to this behavior later. Please speak up if you have any concerns with this approach.

dstansby, tacaswell, and jklymak reacted with thumbs up emoji

- use Ctrl+Wheel to zoom- make this always on - independent of tool state
@timhoffm
Copy link
MemberAuthor

I've also fine-tuned the zoom factor to 0.85, which means approximately 5 zoom steps for a factor 2. I felt this is a good trade of between speed of zooming and detailed control. The mouse wheel is not intended for precision zoomin (better use the zoom box instead). Therefore we can tune a bit more towards speed.

@tacaswell
Copy link
Member

Re-ran the two failures, expect one of them to pass (failure to install system deps from brew) and one to fail again (gtk has started warning on import).

@timhoffm
Copy link
MemberAuthor

Merging based on the two formal approvals and the thumbs-ups to the changed behavior#30405 (comment)

@timhoffmtimhoffm merged commitf2a7682 intomatplotlib:mainSep 10, 2025
44 of 50 checks passed
@timhoffmtimhoffm deleted the scroll-to-zoom branchSeptember 10, 2025 08:01
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@anntzeranntzeranntzer left review comments

@tacaswelltacaswelltacaswell approved these changes

@QuLogicQuLogicQuLogic approved these changes

Assignees

No one assigned

Projects

None yet

Milestone

v3.11.0

Development

Successfully merging this pull request may close these issues.

[ENH]: Zoom in/out on rolling the mouse wheel

8 participants

@timhoffm@ianhi@anntzer@QuLogic@scottshambaugh@dstansby@tacaswell@jklymak

[8]ページ先頭

©2009-2025 Movatter.jp