Skip to content

Commit e339521

Browse files
committed
ENH: Scroll to zoom
Implements a minimal version of #20317, in particular https://github .com//pull/20317#issuecomment-2233156558: 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.
1 parent a5e1f60 commit e339521

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Scroll-to-zoom in GUIs
2+
~~~~~~~~~~~~~~~~~~~~~~
3+
4+
When a plot manipulation tool (pan or zoom tool) in plot windows is selected,
5+
a mouse scroll operation results in a zoom towards the mouse pointer, keeping the
6+
aspect ratio of the axes.
7+
8+
There is no effect if no manipulation tool is selected. This is intentional to
9+
keep a state in which accidental manipulation of the plot is excluded.

lib/matplotlib/backend_bases.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,6 +2574,41 @@ def button_press_handler(event, canvas=None, toolbar=None):
25742574
toolbar.forward()
25752575

25762576

2577+
def scroll_handler(event, canvas=None, toolbar=None):
2578+
ax = event.inaxes
2579+
if ax is None:
2580+
return
2581+
2582+
if toolbar is None:
2583+
if canvas is None:
2584+
canvas = event.canvas
2585+
toolbar = canvas.toolbar
2586+
2587+
if toolbar is None or toolbar.mode == _Mode.NONE:
2588+
return
2589+
2590+
if event.key is None: # zoom towards the mouse position
2591+
toolbar.push_current()
2592+
2593+
xmin, xmax = ax.get_xlim()
2594+
ymin, ymax = ax.get_ylim()
2595+
2596+
# mouse position in data coordinates
2597+
x = event.xdata
2598+
y = event.ydata
2599+
2600+
scale_factor = 1.0 - 0.05 * event.step
2601+
new_xmin = x - (x - xmin) * scale_factor
2602+
new_xmax = x + (xmax - x) * scale_factor
2603+
new_ymin = y - (y - ymin) * scale_factor
2604+
new_ymax = y + (ymax - y) * scale_factor
2605+
2606+
ax.set_xlim(new_xmin, new_xmax)
2607+
ax.set_ylim(new_ymin, new_ymax)
2608+
2609+
ax.figure.canvas.draw_idle()
2610+
2611+
25772612
class NonGuiException(Exception):
25782613
"""Raised when trying show a figure in a non-GUI backend."""
25792614
pass
@@ -2653,11 +2688,14 @@ def __init__(self, canvas, num):
26532688

26542689
self.key_press_handler_id = None
26552690
self.button_press_handler_id = None
2691+
self.scroll_handler_id = None
26562692
if rcParams['toolbar'] != 'toolmanager':
26572693
self.key_press_handler_id = self.canvas.mpl_connect(
26582694
'key_press_event', key_press_handler)
26592695
self.button_press_handler_id = self.canvas.mpl_connect(
26602696
'button_press_event', button_press_handler)
2697+
self.scroll_handler_id = self.canvas.mpl_connect(
2698+
'scroll_event', scroll_handler)
26612699

26622700
self.toolmanager = (ToolManager(canvas.figure)
26632701
if mpl.rcParams['toolbar'] == 'toolmanager'

lib/matplotlib/backend_bases.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ def button_press_handler(
407407
canvas: FigureCanvasBase | None = ...,
408408
toolbar: NavigationToolbar2 | None = ...,
409409
) -> None: ...
410+
def scroll_handler(
411+
event: MouseEvent,
412+
canvas: FigureCanvasBase | None = ...,
413+
toolbar: NavigationToolbar2 | None = ...,
414+
) -> None: ...
410415

411416
class NonGuiException(Exception): ...
412417

@@ -415,6 +420,7 @@ class FigureManagerBase:
415420
num: int | str
416421
key_press_handler_id: int | None
417422
button_press_handler_id: int | None
423+
scroll_handler_id: int | None
418424
toolmanager: ToolManager | None
419425
toolbar: NavigationToolbar2 | ToolContainerBase | None
420426
def __init__(self, canvas: FigureCanvasBase, num: int | str) -> None: ...

0 commit comments

Comments
 (0)