Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7dfe6b8

Browse filesBrowse files
committed
Fix interaction with unpickled 3d plots.
In order to reset the mouse interaction callbacks on unpickled 3d plots, the callback registry really needs to be on the Figure object rather than the Canvas, because the canvas doesn't exist yet when the 3d axes is being unpickled (it is only set on the figure at the very end of unpickling). So move the callback registry to the figure (with a proxy property on the canvas). Then, add a private mechanism to pickle select callbacks, and combine everything together. Test with e.g. ``` import matplotlib.pyplot as plt import pickle fig = plt.figure() fig.add_subplot(111, projection='3d') p = pickle.dumps(fig) plt.close("all") pickle.loads(p) plt.show() ```
1 parent 2938c6f commit 7dfe6b8
Copy full SHA for 7dfe6b8

File tree

Expand file treeCollapse file tree

4 files changed

+49
-15
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+49
-15
lines changed

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,8 +1572,6 @@ def __init__(self, figure):
15721572
figure.set_canvas(self)
15731573
self.figure = figure
15741574
self.manager = None
1575-
# a dictionary from event name to a dictionary that maps cid->func
1576-
self.callbacks = cbook.CallbackRegistry()
15771575
self.widgetlock = widgets.LockDraw()
15781576
self._button = None # the button pressed
15791577
self._key = None # the key pressed
@@ -1584,6 +1582,10 @@ def __init__(self, figure):
15841582
self.toolbar = None # NavigationToolbar2 will set me
15851583
self._is_idle_drawing = False
15861584

1585+
@property
1586+
def callbacks(self):
1587+
return self.figure._canvas_callbacks
1588+
15871589
@classmethod
15881590
@functools.lru_cache()
15891591
def _fix_ipython_backend2gui(cls):

‎lib/matplotlib/cbook/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/cbook/__init__.py
+33-7Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ def __hash__(self):
100100
return hash(self._obj)
101101

102102

103+
def _weak_or_strong_ref(func, callback):
104+
"""
105+
Return a `WeakMethod` wrapping *func* if possible, else a `_StrongRef`.
106+
"""
107+
try:
108+
return WeakMethod(func, callback)
109+
except TypeError:
110+
return _StrongRef(func)
111+
112+
103113
class CallbackRegistry:
104114
"""Handle registering and disconnecting for a set of signals and callbacks:
105115
@@ -158,22 +168,38 @@ def __init__(self, exception_handler=_exception_printer):
158168
self.callbacks = {}
159169
self._cid_gen = itertools.count()
160170
self._func_cid_map = {}
171+
# A hidden variable that marks cids that need to be pickled.
172+
self._pickled_cids = set()
161173

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

166196
def connect(self, s, func):
167197
"""Register *func* to be called when signal *s* is generated.
168198
"""
169199
self._func_cid_map.setdefault(s, {})
170-
try:
171-
proxy = weakref.WeakMethod(func, self._remove_proxy)
172-
except TypeError:
173-
proxy = _StrongRef(func)
200+
proxy = _weak_or_strong_ref(func, self._remove_proxy)
174201
if proxy in self._func_cid_map[s]:
175202
return self._func_cid_map[s][proxy]
176-
177203
cid = next(self._cid_gen)
178204
self._func_cid_map[s][proxy] = cid
179205
self.callbacks.setdefault(s, {})

‎lib/matplotlib/figure.py

Copy file name to clipboardExpand all lines: lib/matplotlib/figure.py
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ def __init__(self,
306306
# which are over-ridden in this class
307307
del self._axes
308308
self.callbacks = cbook.CallbackRegistry()
309+
# Callbacks traditionally associated with the canvas (and exposed with
310+
# a proxy property), but that actually need to be on the figure for
311+
# pickling.
312+
self._canvas_callbacks = cbook.CallbackRegistry()
309313

310314
if figsize is None:
311315
figsize = rcParams['figure.figsize']

‎lib/mpl_toolkits/mplot3d/axes3d.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/mplot3d/axes3d.py
+8-6Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ def __init__(
114114
self._zcid = None
115115

116116
self.mouse_init()
117-
self.figure.canvas.mpl_connect(
118-
'motion_notify_event', self._on_move),
119-
self.figure.canvas.mpl_connect(
120-
'button_press_event', self._button_press),
121-
self.figure.canvas.mpl_connect(
122-
'button_release_event', self._button_release),
117+
self.figure.canvas.callbacks._pickled_cids.update({
118+
self.figure.canvas.mpl_connect(
119+
'motion_notify_event', self._on_move),
120+
self.figure.canvas.mpl_connect(
121+
'button_press_event', self._button_press),
122+
self.figure.canvas.mpl_connect(
123+
'button_release_event', self._button_release),
124+
})
123125
self.set_top_view()
124126

125127
self.patch.set_linewidth(0)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.