Skip to content

Commit e16dc7e

Browse files
committed
Cleanup interactive pan/zoom.
Store all the relevant state in a single namedtuple (`_pan_info`), similar to the one used for zoom (`_zoom_info`), instead of scattering it in two attributes (`_button_pressed`/`_xypress`). Slightly reorder logic in `press_pan` to match the order in `press_zoom`; likewise sync `release_pan` with `release_zoom`. We don't need to store the axes index (as was done in `_xypress`). In `release_zoom`, move the less-than-5px check out of the loop, as it only needs to be done once.
1 parent 8244201 commit e16dc7e

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
@@ -2871,7 +2872,6 @@ def __init__(self, canvas):
28712872
self.canvas = canvas
28722873
canvas.toolbar = self
28732874
self._nav_stack = cbook.Stack()
2874-
self._xypress = None # location and axis info at the time of the press
28752875
# This cursor will be set after the initial draw.
28762876
self._lastCursor = cursors.POINTER
28772877

@@ -2889,10 +2889,9 @@ def __init__(self, canvas):
28892889
'button_release_event', self._zoom_pan_handler)
28902890
self._id_drag = self.canvas.mpl_connect(
28912891
'motion_notify_event', self.mouse_move)
2892+
self._pan_info = None
28922893
self._zoom_info = None
28932894

2894-
self._button_pressed = None # determined by button pressed at start
2895-
28962895
self.mode = _Mode.NONE # a mode string for the status bar
28972896
self.set_history_buttons()
28982897

@@ -3074,26 +3073,25 @@ def pan(self, *args):
30743073
a.set_navigate_mode(self.mode._navigate_mode)
30753074
self.set_message(self.mode)
30763075

3076+
_PanInfo = namedtuple("_PanInfo", "button axes cid")
3077+
30773078
def press_pan(self, event):
30783079
"""Callback for mouse button press in pan/zoom mode."""
3079-
if event.button in [1, 3]:
3080-
self._button_pressed = event.button
3081-
else:
3082-
self._button_pressed = None
3080+
if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT]
3081+
or event.x is None or event.y is None):
3082+
return
3083+
axes = [a for a in self.canvas.figure.get_axes()
3084+
if a.in_axes(event) and a.get_navigate() and a.can_pan()]
3085+
if not axes:
30833086
return
30843087
if self._nav_stack() is None:
3085-
# set the home button to this view
3086-
self.push_current()
3087-
x, y = event.x, event.y
3088-
self._xypress = []
3089-
for i, a in enumerate(self.canvas.figure.get_axes()):
3090-
if (x is not None and y is not None and a.in_axes(event) and
3091-
a.get_navigate() and a.can_pan()):
3092-
a.start_pan(x, y, event.button)
3093-
self._xypress.append((a, i))
3094-
self.canvas.mpl_disconnect(self._id_drag)
3095-
self._id_drag = self.canvas.mpl_connect(
3096-
'motion_notify_event', self.drag_pan)
3088+
self.push_current() # set the home button to this view
3089+
for ax in axes:
3090+
ax.start_pan(event.x, event.y, event.button)
3091+
self.canvas.mpl_disconnect(self._id_drag)
3092+
id_drag = self.canvas.mpl_connect("motion_notify_event", self.drag_pan)
3093+
self._pan_info = self._PanInfo(
3094+
button=event.button, axes=axes, cid=id_drag)
30973095
press = cbook._deprecate_method_override(
30983096
__class__.press, self, since="3.3", message="Calling an "
30993097
"overridden press() at pan start is deprecated since %(since)s "
@@ -3103,34 +3101,30 @@ def press_pan(self, event):
31033101

31043102
def drag_pan(self, event):
31053103
"""Callback for dragging in pan/zoom mode."""
3106-
for a, ind in self._xypress:
3107-
#safer to use the recorded button at the press than current button:
3108-
#multiple button can get pressed during motion...
3109-
a.drag_pan(self._button_pressed, event.key, event.x, event.y)
3104+
for ax in self._pan_info.axes:
3105+
# Using the recorded button at the press is safer than the current
3106+
# button, as multiple buttons can get pressed during motion.
3107+
ax.drag_pan(self._pan_info.button, event.key, event.x, event.y)
31103108
self.canvas.draw_idle()
31113109

31123110
def release_pan(self, event):
31133111
"""Callback for mouse button release in pan/zoom mode."""
3114-
3115-
if self._button_pressed is None:
3112+
if self._pan_info is None:
31163113
return
3117-
self.canvas.mpl_disconnect(self._id_drag)
3114+
self.canvas.mpl_disconnect(self._pan_info.cid)
31183115
self._id_drag = self.canvas.mpl_connect(
31193116
'motion_notify_event', self.mouse_move)
3120-
for a, ind in self._xypress:
3121-
a.end_pan()
3122-
if not self._xypress:
3123-
return
3124-
self._xypress = []
3125-
self._button_pressed = None
3126-
self.push_current()
3117+
for ax in self._pan_info.axes:
3118+
ax.end_pan()
31273119
release = cbook._deprecate_method_override(
31283120
__class__.press, self, since="3.3", message="Calling an "
31293121
"overridden release() at pan stop is deprecated since %(since)s "
31303122
"and will be removed %(removal)s; override release_pan() instead.")
31313123
if release is not None:
31323124
release(event)
31333125
self._draw()
3126+
self._pan_info = None
3127+
self.push_current()
31343128

31353129
def zoom(self, *args):
31363130
"""Toggle zoom to rect mode."""
@@ -3144,11 +3138,12 @@ def zoom(self, *args):
31443138
a.set_navigate_mode(self.mode._navigate_mode)
31453139
self.set_message(self.mode)
31463140

3141+
_ZoomInfo = namedtuple("_ZoomInfo", "direction start_xy axes cid")
3142+
31473143
def press_zoom(self, event):
31483144
"""Callback for mouse button press in zoom to rect mode."""
3149-
if event.button not in [1, 3]:
3150-
return
3151-
if event.x is None or event.y is None:
3145+
if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT]
3146+
or event.x is None or event.y is None):
31523147
return
31533148
axes = [a for a in self.canvas.figure.get_axes()
31543149
if a.in_axes(event) and a.get_navigate() and a.can_zoom()]
@@ -3158,12 +3153,9 @@ def press_zoom(self, event):
31583153
self.push_current() # set the home button to this view
31593154
id_zoom = self.canvas.mpl_connect(
31603155
"motion_notify_event", self.drag_zoom)
3161-
self._zoom_info = {
3162-
"direction": "in" if event.button == 1 else "out",
3163-
"start_xy": (event.x, event.y),
3164-
"axes": axes,
3165-
"cid": id_zoom,
3166-
}
3156+
self._zoom_info = self._ZoomInfo(
3157+
direction="in" if event.button == 1 else "out",
3158+
start_xy=(event.x, event.y), axes=axes, cid=id_zoom)
31673159
press = cbook._deprecate_method_override(
31683160
__class__.press, self, since="3.3", message="Calling an "
31693161
"overridden press() at zoom start is deprecated since %(since)s "
@@ -3173,8 +3165,8 @@ def press_zoom(self, event):
31733165

31743166
def drag_zoom(self, event):
31753167
"""Callback for dragging in zoom mode."""
3176-
start_xy = self._zoom_info["start_xy"]
3177-
ax = self._zoom_info["axes"][0]
3168+
start_xy = self._zoom_info.start_xy
3169+
ax = self._zoom_info.axes[0]
31783170
(x1, y1), (x2, y2) = np.clip(
31793171
[start_xy, [event.x, event.y]], ax.bbox.min, ax.bbox.max)
31803172
if event.key == "x":
@@ -3190,44 +3182,40 @@ def release_zoom(self, event):
31903182

31913183
# We don't check the event button here, so that zooms can be cancelled
31923184
# by (pressing and) releasing another mouse button.
3193-
self.canvas.mpl_disconnect(self._zoom_info["cid"])
3185+
self.canvas.mpl_disconnect(self._zoom_info.cid)
31943186
self.remove_rubberband()
31953187

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

3204+
for i, ax in enumerate(self._zoom_info.axes):
32163205
# Detect whether this axes is twinned with an earlier axes in the
32173206
# list of zoomed axes, to avoid double zooming.
32183207
twinx = any(ax.get_shared_x_axes().joined(ax, prev)
3219-
for prev in self._zoom_info["axes"][:i])
3208+
for prev in self._zoom_info.axes[:i])
32203209
twiny = any(ax.get_shared_y_axes().joined(ax, prev)
3221-
for prev in self._zoom_info["axes"][:i])
3222-
3210+
for prev in self._zoom_info.axes[:i])
32233211
ax._set_view_from_bbox(
3224-
(start_x, start_y, x, y), self._zoom_info["direction"],
3225-
event.key, twinx, twiny)
3212+
(start_x, start_y, event.x, event.y),
3213+
self._zoom_info.direction, event.key, twinx, twiny)
32263214

32273215
self._draw()
32283216
self._zoom_info = None
3229-
32303217
self.push_current()
3218+
32313219
release = cbook._deprecate_method_override(
32323220
__class__.release, self, since="3.3", message="Calling an "
32333221
"overridden release() at zoom stop is deprecated since %(since)s "

0 commit comments

Comments
 (0)