Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 772a315

Browse files
authored
Merge pull request #16220 from anntzer/3dp
Fix interaction with unpickled 3d plots.
2 parents 7db82ea + 08236e7 commit 772a315

File tree

5 files changed

+57
-15
lines changed

5 files changed

+57
-15
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Canvas's callback registry now stored on Figure
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The canonical location of the `~.cbook.CallbackRegistry` used to
5+
handle Figure/Canvas events has been moved from the Canvas to the
6+
Figure. This change should be transparent to almost all users,
7+
however if you are swapping switching the Figure out from on top of a
8+
Canvas or visa versa you may see a change in behavior.

lib/matplotlib/backend_bases.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,8 +1713,6 @@ def __init__(self, figure):
17131713
figure.set_canvas(self)
17141714
self.figure = figure
17151715
self.manager = None
1716-
# a dictionary from event name to a dictionary that maps cid->func
1717-
self.callbacks = cbook.CallbackRegistry()
17181716
self.widgetlock = widgets.LockDraw()
17191717
self._button = None # the button pressed
17201718
self._key = None # the key pressed
@@ -1725,6 +1723,10 @@ def __init__(self, figure):
17251723
self.toolbar = None # NavigationToolbar2 will set me
17261724
self._is_idle_drawing = False
17271725

1726+
@property
1727+
def callbacks(self):
1728+
return self.figure._canvas_callbacks
1729+
17281730
@classmethod
17291731
@functools.lru_cache()
17301732
def _fix_ipython_backend2gui(cls):

lib/matplotlib/cbook/__init__.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ def __hash__(self):
104104
return hash(self._obj)
105105

106106

107+
def _weak_or_strong_ref(func, callback):
108+
"""
109+
Return a `WeakMethod` wrapping *func* if possible, else a `_StrongRef`.
110+
"""
111+
try:
112+
return weakref.WeakMethod(func, callback)
113+
except TypeError:
114+
return _StrongRef(func)
115+
116+
107117
class CallbackRegistry:
108118
"""
109119
Handle registering and disconnecting for a set of signals and callbacks:
@@ -157,21 +167,37 @@ def __init__(self, exception_handler=_exception_printer):
157167
self.callbacks = {}
158168
self._cid_gen = itertools.count()
159169
self._func_cid_map = {}
170+
# A hidden variable that marks cids that need to be pickled.
171+
self._pickled_cids = set()
160172

161173
def __getstate__(self):
162-
# In general, callbacks may not be pickled, so we just drop them.
163-
return {**vars(self), "callbacks": {}, "_func_cid_map": {}}
174+
return {
175+
**vars(self),
176+
# In general, callbacks may not be pickled, so we just drop them,
177+
# unless directed otherwise by self._pickled_cids.
178+
"callbacks": {s: {cid: proxy() for cid, proxy in d.items()
179+
if cid in self._pickled_cids}
180+
for s, d in self.callbacks.items()},
181+
# It is simpler to reconstruct this from callbacks in __setstate__.
182+
"_func_cid_map": None,
183+
}
184+
185+
def __setstate__(self, state):
186+
vars(self).update(state)
187+
self.callbacks = {
188+
s: {cid: _weak_or_strong_ref(func, self._remove_proxy)
189+
for cid, func in d.items()}
190+
for s, d in self.callbacks.items()}
191+
self._func_cid_map = {
192+
s: {proxy: cid for cid, proxy in d.items()}
193+
for s, d in self.callbacks.items()}
164194

165195
def connect(self, s, func):
166196
"""Register *func* to be called when signal *s* is generated."""
167197
self._func_cid_map.setdefault(s, {})
168-
try:
169-
proxy = weakref.WeakMethod(func, self._remove_proxy)
170-
except TypeError:
171-
proxy = _StrongRef(func)
198+
proxy = _weak_or_strong_ref(func, self._remove_proxy)
172199
if proxy in self._func_cid_map[s]:
173200
return self._func_cid_map[s][proxy]
174-
175201
cid = next(self._cid_gen)
176202
self._func_cid_map[s][proxy] = cid
177203
self.callbacks.setdefault(s, {})

lib/matplotlib/figure.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2322,6 +2322,10 @@ def __init__(self,
23222322
super().__init__()
23232323

23242324
self.callbacks = cbook.CallbackRegistry()
2325+
# Callbacks traditionally associated with the canvas (and exposed with
2326+
# a proxy property), but that actually need to be on the figure for
2327+
# pickling.
2328+
self._canvas_callbacks = cbook.CallbackRegistry()
23252329

23262330
if figsize is None:
23272331
figsize = mpl.rcParams['figure.figsize']

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,14 @@ def __init__(
118118
self._zcid = None
119119

120120
self.mouse_init()
121-
self.figure.canvas.mpl_connect(
122-
'motion_notify_event', self._on_move),
123-
self.figure.canvas.mpl_connect(
124-
'button_press_event', self._button_press),
125-
self.figure.canvas.mpl_connect(
126-
'button_release_event', self._button_release),
121+
self.figure.canvas.callbacks._pickled_cids.update({
122+
self.figure.canvas.mpl_connect(
123+
'motion_notify_event', self._on_move),
124+
self.figure.canvas.mpl_connect(
125+
'button_press_event', self._button_press),
126+
self.figure.canvas.mpl_connect(
127+
'button_release_event', self._button_release),
128+
})
127129
self.set_top_view()
128130

129131
self.patch.set_linewidth(0)

0 commit comments

Comments
 (0)