Skip to content

Commit da66560

Browse files
authored
Merge pull request #18847 from anntzer/pz
Cleanup interactive pan/zoom.
2 parents 506b108 + e16dc7e commit da66560

File tree

1 file changed

+58
-70
lines changed

1 file changed

+58
-70
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 58 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
The base class for the Toolbar class of each interactive backend.
2626
"""
2727

28+
from collections import namedtuple
2829
from contextlib import contextmanager, suppress
2930
from enum import Enum, IntEnum
3031
import functools
@@ -2863,7 +2864,6 @@ def __init__(self, canvas):
28632864
self.canvas = canvas
28642865
canvas.toolbar = self
28652866
self._nav_stack = cbook.Stack()
2866-
self._xypress = None # location and axis info at the time of the press
28672867
# This cursor will be set after the initial draw.
28682868
self._lastCursor = cursors.POINTER
28692869

@@ -2881,10 +2881,9 @@ def __init__(self, canvas):
28812881
'button_release_event', self._zoom_pan_handler)
28822882
self._id_drag = self.canvas.mpl_connect(
28832883
'motion_notify_event', self.mouse_move)
2884+
self._pan_info = None
28842885
self._zoom_info = None
28852886

2886-
self._button_pressed = None # determined by button pressed at start
2887-
28882887
self.mode = _Mode.NONE # a mode string for the status bar
28892888
self.set_history_buttons()
28902889

@@ -3066,26 +3065,25 @@ def pan(self, *args):
30663065
a.set_navigate_mode(self.mode._navigate_mode)
30673066
self.set_message(self.mode)
30683067

3068+
_PanInfo = namedtuple("_PanInfo", "button axes cid")
3069+
30693070
def press_pan(self, event):
30703071
"""Callback for mouse button press in pan/zoom mode."""
3071-
if event.button in [1, 3]:
3072-
self._button_pressed = event.button
3073-
else:
3074-
self._button_pressed = None
3072+
if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT]
3073+
or event.x is None or event.y is None):
3074+
return
3075+
axes = [a for a in self.canvas.figure.get_axes()
3076+
if a.in_axes(event) and a.get_navigate() and a.can_pan()]
3077+
if not axes:
30753078
return
30763079
if self._nav_stack() is None:
3077-
# set the home button to this view
3078-
self.push_current()
3079-
x, y = event.x, event.y
3080-
self._xypress = []
3081-
for i, a in enumerate(self.canvas.figure.get_axes()):
3082-
if (x is not None and y is not None and a.in_axes(event) and
3083-
a.get_navigate() and a.can_pan()):
3084-
a.start_pan(x, y, event.button)
3085-
self._xypress.append((a, i))
3086-
self.canvas.mpl_disconnect(self._id_drag)
3087-
self._id_drag = self.canvas.mpl_connect(
3088-
'motion_notify_event', self.drag_pan)
3080+
self.push_current() # set the home button to this view
3081+
for ax in axes:
3082+
ax.start_pan(event.x, event.y, event.button)
3083+
self.canvas.mpl_disconnect(self._id_drag)
3084+
id_drag = self.canvas.mpl_connect("motion_notify_event", self.drag_pan)
3085+
self._pan_info = self._PanInfo(
3086+
button=event.button, axes=axes, cid=id_drag)
30893087
press = cbook._deprecate_method_override(
30903088
__class__.press, self, since="3.3", message="Calling an "
30913089
"overridden press() at pan start is deprecated since %(since)s "
@@ -3095,34 +3093,30 @@ def press_pan(self, event):
30953093

30963094
def drag_pan(self, event):
30973095
"""Callback for dragging in pan/zoom mode."""
3098-
for a, ind in self._xypress:
3099-
#safer to use the recorded button at the press than current button:
3100-
#multiple button can get pressed during motion...
3101-
a.drag_pan(self._button_pressed, event.key, event.x, event.y)
3096+
for ax in self._pan_info.axes:
3097+
# Using the recorded button at the press is safer than the current
3098+
# button, as multiple buttons can get pressed during motion.
3099+
ax.drag_pan(self._pan_info.button, event.key, event.x, event.y)
31023100
self.canvas.draw_idle()
31033101

31043102
def release_pan(self, event):
31053103
"""Callback for mouse button release in pan/zoom mode."""
3106-
3107-
if self._button_pressed is None:
3104+
if self._pan_info is None:
31083105
return
3109-
self.canvas.mpl_disconnect(self._id_drag)
3106+
self.canvas.mpl_disconnect(self._pan_info.cid)
31103107
self._id_drag = self.canvas.mpl_connect(
31113108
'motion_notify_event', self.mouse_move)
3112-
for a, ind in self._xypress:
3113-
a.end_pan()
3114-
if not self._xypress:
3115-
return
3116-
self._xypress = []
3117-
self._button_pressed = None
3118-
self.push_current()
3109+
for ax in self._pan_info.axes:
3110+
ax.end_pan()
31193111
release = cbook._deprecate_method_override(
31203112
__class__.press, self, since="3.3", message="Calling an "
31213113
"overridden release() at pan stop is deprecated since %(since)s "
31223114
"and will be removed %(removal)s; override release_pan() instead.")
31233115
if release is not None:
31243116
release(event)
31253117
self._draw()
3118+
self._pan_info = None
3119+
self.push_current()
31263120

31273121
def zoom(self, *args):
31283122
"""Toggle zoom to rect mode."""
@@ -3136,11 +3130,12 @@ def zoom(self, *args):
31363130
a.set_navigate_mode(self.mode._navigate_mode)
31373131
self.set_message(self.mode)
31383132

3133+
_ZoomInfo = namedtuple("_ZoomInfo", "direction start_xy axes cid")
3134+
31393135
def press_zoom(self, event):
31403136
"""Callback for mouse button press in zoom to rect mode."""
3141-
if event.button not in [1, 3]:
3142-
return
3143-
if event.x is None or event.y is None:
3137+
if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT]
3138+
or event.x is None or event.y is None):
31443139
return
31453140
axes = [a for a in self.canvas.figure.get_axes()
31463141
if a.in_axes(event) and a.get_navigate() and a.can_zoom()]
@@ -3150,12 +3145,9 @@ def press_zoom(self, event):
31503145
self.push_current() # set the home button to this view
31513146
id_zoom = self.canvas.mpl_connect(
31523147
"motion_notify_event", self.drag_zoom)
3153-
self._zoom_info = {
3154-
"direction": "in" if event.button == 1 else "out",
3155-
"start_xy": (event.x, event.y),
3156-
"axes": axes,
3157-
"cid": id_zoom,
3158-
}
3148+
self._zoom_info = self._ZoomInfo(
3149+
direction="in" if event.button == 1 else "out",
3150+
start_xy=(event.x, event.y), axes=axes, cid=id_zoom)
31593151
press = cbook._deprecate_method_override(
31603152
__class__.press, self, since="3.3", message="Calling an "
31613153
"overridden press() at zoom start is deprecated since %(since)s "
@@ -3165,8 +3157,8 @@ def press_zoom(self, event):
31653157

31663158
def drag_zoom(self, event):
31673159
"""Callback for dragging in zoom mode."""
3168-
start_xy = self._zoom_info["start_xy"]
3169-
ax = self._zoom_info["axes"][0]
3160+
start_xy = self._zoom_info.start_xy
3161+
ax = self._zoom_info.axes[0]
31703162
(x1, y1), (x2, y2) = np.clip(
31713163
[start_xy, [event.x, event.y]], ax.bbox.min, ax.bbox.max)
31723164
if event.key == "x":
@@ -3182,44 +3174,40 @@ def release_zoom(self, event):
31823174

31833175
# We don't check the event button here, so that zooms can be cancelled
31843176
# by (pressing and) releasing another mouse button.
3185-
self.canvas.mpl_disconnect(self._zoom_info["cid"])
3177+
self.canvas.mpl_disconnect(self._zoom_info.cid)
31863178
self.remove_rubberband()
31873179

3188-
start_x, start_y = self._zoom_info["start_xy"]
3189-
3190-
for i, ax in enumerate(self._zoom_info["axes"]):
3191-
x, y = event.x, event.y
3192-
# ignore singular clicks - 5 pixels is a threshold
3193-
# allows the user to "cancel" a zoom action
3194-
# by zooming by less than 5 pixels
3195-
if ((abs(x - start_x) < 5 and event.key != "y") or
3196-
(abs(y - start_y) < 5 and event.key != "x")):
3197-
self._xypress = None
3198-
release = cbook._deprecate_method_override(
3199-
__class__.press, self, since="3.3", message="Calling an "
3200-
"overridden release() at zoom stop is deprecated since "
3201-
"%(since)s and will be removed %(removal)s; override "
3202-
"release_zoom() instead.")
3203-
if release is not None:
3204-
release(event)
3205-
self._draw()
3206-
return
3180+
start_x, start_y = self._zoom_info.start_xy
3181+
# Ignore single clicks: 5 pixels is a threshold that allows the user to
3182+
# "cancel" a zoom action by zooming by less than 5 pixels.
3183+
if ((abs(event.x - start_x) < 5 and event.key != "y")
3184+
or (abs(event.y - start_y) < 5 and event.key != "x")):
3185+
self._draw()
3186+
self._zoom_info = None
3187+
release = cbook._deprecate_method_override(
3188+
__class__.press, self, since="3.3", message="Calling an "
3189+
"overridden release() at zoom stop is deprecated since "
3190+
"%(since)s and will be removed %(removal)s; override "
3191+
"release_zoom() instead.")
3192+
if release is not None:
3193+
release(event)
3194+
return
32073195

3196+
for i, ax in enumerate(self._zoom_info.axes):
32083197
# Detect whether this axes is twinned with an earlier axes in the
32093198
# list of zoomed axes, to avoid double zooming.
32103199
twinx = any(ax.get_shared_x_axes().joined(ax, prev)
3211-
for prev in self._zoom_info["axes"][:i])
3200+
for prev in self._zoom_info.axes[:i])
32123201
twiny = any(ax.get_shared_y_axes().joined(ax, prev)
3213-
for prev in self._zoom_info["axes"][:i])
3214-
3202+
for prev in self._zoom_info.axes[:i])
32153203
ax._set_view_from_bbox(
3216-
(start_x, start_y, x, y), self._zoom_info["direction"],
3217-
event.key, twinx, twiny)
3204+
(start_x, start_y, event.x, event.y),
3205+
self._zoom_info.direction, event.key, twinx, twiny)
32183206

32193207
self._draw()
32203208
self._zoom_info = None
3221-
32223209
self.push_current()
3210+
32233211
release = cbook._deprecate_method_override(
32243212
__class__.release, self, since="3.3", message="Calling an "
32253213
"overridden release() at zoom stop is deprecated since %(since)s "

0 commit comments

Comments
 (0)