Skip to content

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/users/next_whats_new/scroll_to_zoom.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Scroll-to-zoom in GUIs
~~~~~~~~~~~~~~~~~~~~~~

When a plot manipulation tool (pan or zoom tool) in plot windows is enabled,
a mouse scroll operation results in a zoom focussing on the mouse pointer, keeping the
aspect ratio of the axes.

There is no effect if no manipulation tool is selected. This is intentional to
keep a state in which accidental manipulation of the plot is avoided.

Zooming is currently only supported on rectilinear Axes.
39 changes: 39 additions & 0 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2574,6 +2574,42 @@ def button_press_handler(event, canvas=None, toolbar=None):
toolbar.forward()


def scroll_handler(event, canvas=None, toolbar=None):
ax = event.inaxes
if ax is None:
return
if ax.name != "rectilinear":
# zooming is currently only supported on rectilinear axes
return

if toolbar is None:
toolbar = (canvas or event.canvas).toolbar

if toolbar is None or toolbar.mode == _Mode.NONE:
return

if event.key is None: # zoom towards the mouse position
toolbar.push_current()

xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()

# mouse position in data coordinates
x = event.xdata
y = event.ydata

scale_factor = 1.0 - 0.05 * event.step
new_xmin = x - (x - xmin) * scale_factor
new_xmax = x + (xmax - x) * scale_factor
new_ymin = y - (y - ymin) * scale_factor
new_ymax = y + (ymax - y) * scale_factor

ax.set_xlim(new_xmin, new_xmax)
ax.set_ylim(new_ymin, new_ymax)

ax.figure.canvas.draw_idle()


class NonGuiException(Exception):
"""Raised when trying show a figure in a non-GUI backend."""
pass
Expand Down Expand Up @@ -2653,11 +2689,14 @@ def __init__(self, canvas, num):

self.key_press_handler_id = None
self.button_press_handler_id = None
self.scroll_handler_id = None
if rcParams['toolbar'] != 'toolmanager':
self.key_press_handler_id = self.canvas.mpl_connect(
'key_press_event', key_press_handler)
self.button_press_handler_id = self.canvas.mpl_connect(
'button_press_event', button_press_handler)
self.scroll_handler_id = self.canvas.mpl_connect(
'scroll_event', scroll_handler)

self.toolmanager = (ToolManager(canvas.figure)
if mpl.rcParams['toolbar'] == 'toolmanager'
Expand Down
6 changes: 6 additions & 0 deletions lib/matplotlib/backend_bases.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,11 @@ def button_press_handler(
canvas: FigureCanvasBase | None = ...,
toolbar: NavigationToolbar2 | None = ...,
) -> None: ...
def scroll_handler(
event: MouseEvent,
canvas: FigureCanvasBase | None = ...,
toolbar: NavigationToolbar2 | None = ...,
) -> None: ...

class NonGuiException(Exception): ...

Expand All @@ -415,6 +420,7 @@ class FigureManagerBase:
num: int | str
key_press_handler_id: int | None
button_press_handler_id: int | None
scroll_handler_id: int | None
toolmanager: ToolManager | None
toolbar: NavigationToolbar2 | ToolContainerBase | None
def __init__(self, canvas: FigureCanvasBase, num: int | str) -> None: ...
Expand Down
Loading