Skip to content

Conversation

@lucasgruwez
Copy link
Contributor

PR summary

Tangent to #30541
Allows for zooming in a single axis using the zoom rectangle by dragging only in one direction.

Screen.Recording.2025-09-14.at.01.37.29.mov

PR checklist

@rcomer
Copy link
Member

rcomer commented Sep 13, 2025

I note that the existing x-/y- modifiers do not really work when using my laptop's touchpad1, but this proposal does work nicely 👍

Footnotes

  1. If I hold x and the left button, nothing happens when I drag. I can get it to work by making the box and then pressing x before I let go, but I don't get the visual of the box turning into two parallel lines. If I use an external mouse, everything works as it should so it definitely seems to be an issue with the touchpad.

@timhoffm
Copy link
Member

This is an interesting concept, though I was a bit surprised at first. Is there any precedence that other programs use this approach?

Also, would it be a good idea to give some sort of visual aide, with the goals (i) to make it clear that something special is going on and (ii) give an indication of the area the mouse has to be in so that the special handling applies? e.g. something like marking the region:
grafik

@lucasgruwez
Copy link
Contributor Author

Matlab has similar behavior. Could implement something similar where the lines at the edge indicate the region the mouse needs to be in?

Screen.Recording.2025-09-14.at.19.18.48.mov

@timhoffm
Copy link
Member

timhoffm commented Sep 14, 2025

The range marker is a good visualization to indicate the difference. Though, I would like to additionally keep dashed lines as an extension of the whiskers, because that helps to ensure relevant data points are included.

That said, all the visual changes would need refactoring and backend-dependent implementation as currently all visualization is done via RubberBandBase and that does only cover the rectangle without the ability for further modification.

class RubberbandBase(ToolBase):

@lucasgruwez
Copy link
Contributor Author

Example of whiskers and dashed rectangle using QtCairo and QtAgg backends. The width of the whiskers represents the region the mouse needs to be in for single-axis zoom.

Excuse the lack of mouse pointer in the screenshot.

Screenshot 2025-09-14 at 23 10 09

@lucasgruwez
Copy link
Contributor Author

lucasgruwez commented Sep 14, 2025

Backends so far

  • tk
  • gtk (unable to test on my machine, but code is written)
  • Qt
  • wx
  • WebAgg
  • MacOS

lucasgruwez and others added 3 commits September 15, 2025 13:55
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@lucasgruwez
Copy link
Contributor Author

All backends have been updated. As previously mentioned, I was unable to test gtk on my machine, but I have tested all other backends. Several other fixes were also included:

  • Ensure that the zoom rubber band is dashed in all backends (it was not in WebAgg).
  • Fix the Tk rubber band: the implicit black line colour was not working, so it was set explicitly instead.

@timhoffm
Copy link
Member

All backends have been updated. As previously mentioned, I was unable to test gtk on my machine, but I have tested all other backends. Several other fixes were also included:

  • Ensure that the zoom rubber band is dashed in all backends (it was not in WebAgg).
  • Fix the Tk rubber band: the implicit black line colour was not working, so it was set explicitly instead.

Could you move them out into a separate PR? It makes reviewing this PR simpler if we don't have multiple topics.

This PR is a good prove-of-concept and from a first glance feature complete - thanks for implementing all the backends!

We still need to think about the architecture and how to bring it into the main library. As is, the draw_whisker method is an API change that will break third-party backends. We'll have to decide

  • API design: whether that method is the right approach (other alternatives could be integrate it into the rubberband, or start a more general approach for drawing some primitives in the backend in the anticipation that we will have more interactivity (e.g. PoC: GUI-native crosshair cursor #30516).
  • API compatibility: As is this is breaking 3rd party backends, which is a no-go. We could either work around this through special-casing (test if the method is available in the backend) or go for a more structured approach of API versioning [ENH]: Backend versioning #30559.

@lucasgruwez
Copy link
Contributor Author

The bugfixes have been separated into #30560

Regarding third-party backends, I don't fully understand how these would break. The empty draw_whiskers method in the base class NavigationToolbar2 appears to address this concern. During development, even before integrating the whiskers into a new backend, the backend would continue to function, albeit without displaying the whiskers.

lib/matplotlib/backend_bases.py:2938

Attempting to use the ipympl backend in Jupyter leads to the following behaviour:
no whiskers are displayed, only a zoom box. There appear to be console messages indicating that there is no handler for the whiskers event, as it is a subclass of the WebAgg backend, which calls this event.

Screenshot 2025-09-15 at 18 05 51

@lucasgruwez
Copy link
Contributor Author

Tested with mplcairo third party backend and got the following results

  • mplcairo.macosx: Whiskers inherited from mpl macosx class
  • mplcairo.qt: No whiskers drawn, box only. No errors thrown
  • mplcairo.wx: Scaling is off on my machine, whiskers still work though
  • mplcairo.tk: Whiskers inherited from mpl tk class
  • mplcairo.gtk: Whiskers inherited from mpl gtk class

@lucasgruwez
Copy link
Contributor Author

Since I got gtk working on my machine, here's a screenshot of the feature with each builtin backend. Have also tested the cairo backends with the same results.

image

@lucasgruwez
Copy link
Contributor Author

lucasgruwez commented Sep 15, 2025

Having a more thorough read of the issues you've linked, I think we can consider the following implementation:

A draw_line() method be implemented for each backend, taking in color/stroke/dash parameters. draw_rubberband() and draw_whiskers() could then make use of this method in a backend-agnostic way.
Additional interactions (i.e. the crosshair) could then make use of the draw_line() function and not require backend refactoring.

I see that this could have some ramifications on third party backends, as it could break draw_rubberband() as they won't have a draw_line() method (unless subclassed from a mpl backend). Somewhat uncertain about the versioning system, is this for third party backends only? or is there concern that mpl native backends could misalign with the package itself?

@timhoffm
Copy link
Member

Somewhat uncertain about the versioning system, is this for third party backends only? or is there concern that mpl native backends could misalign with the package itself?

native backends could partially misalign, i.e. an intenal backend doesn't have to be on the newest version of the API, but of course it must have a version that is supported, i.e. if at some point matplotlib supports backend versions v1 and v2, then every internal backend must be one of these versions. For example it would be permissible if we don't implement a new API for all backends right away.

@timhoffm
Copy link
Member

see that this could have some ramifications on third party backends, as it could break draw_rubberband() as they won't have a draw_line() method (unless subclassed from a mpl backend).

For the time being, I would leave draw_rubberband() it's working as is. We can refactor that later when/if backends have gained more general drawing capability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants