diff --git a/doc/api/next_api_changes/behavior/16220-AL.rst b/doc/api/next_api_changes/behavior/16220-AL.rst new file mode 100644 index 000000000000..e2ecca545d06 --- /dev/null +++ b/doc/api/next_api_changes/behavior/16220-AL.rst @@ -0,0 +1,8 @@ +Canvas's callback registry now stored on Figure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The canonical location of the `~.cbook.CallbackRegistry` used to +handle Figure/Canvas events has been moved from the Canvas to the +Figure. This change should be transparent to almost all users, +however if you are swapping switching the Figure out from on top of a +Canvas or visa versa you may see a change in behavior. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 7f2ab50a56b2..9b1ef446cfa6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1718,8 +1718,6 @@ def __init__(self, figure): figure.set_canvas(self) self.figure = figure self.manager = None - # a dictionary from event name to a dictionary that maps cid->func - self.callbacks = cbook.CallbackRegistry() self.widgetlock = widgets.LockDraw() self._button = None # the button pressed self._key = None # the key pressed @@ -1730,6 +1728,10 @@ def __init__(self, figure): self.toolbar = None # NavigationToolbar2 will set me self._is_idle_drawing = False + @property + def callbacks(self): + return self.figure._canvas_callbacks + @classmethod @functools.lru_cache() def _fix_ipython_backend2gui(cls): diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index cbfe9db794f1..728a663a02cd 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -104,6 +104,16 @@ def __hash__(self): return hash(self._obj) +def _weak_or_strong_ref(func, callback): + """ + Return a `WeakMethod` wrapping *func* if possible, else a `_StrongRef`. + """ + try: + return weakref.WeakMethod(func, callback) + except TypeError: + return _StrongRef(func) + + class CallbackRegistry: """ Handle registering and disconnecting for a set of signals and callbacks: @@ -163,21 +173,37 @@ def __init__(self, exception_handler=_exception_printer): self.callbacks = {} self._cid_gen = itertools.count() self._func_cid_map = {} + # A hidden variable that marks cids that need to be pickled. + self._pickled_cids = set() def __getstate__(self): - # In general, callbacks may not be pickled, so we just drop them. - return {**vars(self), "callbacks": {}, "_func_cid_map": {}} + return { + **vars(self), + # In general, callbacks may not be pickled, so we just drop them, + # unless directed otherwise by self._pickled_cids. + "callbacks": {s: {cid: proxy() for cid, proxy in d.items() + if cid in self._pickled_cids} + for s, d in self.callbacks.items()}, + # It is simpler to reconstruct this from callbacks in __setstate__. + "_func_cid_map": None, + } + + def __setstate__(self, state): + vars(self).update(state) + self.callbacks = { + s: {cid: _weak_or_strong_ref(func, self._remove_proxy) + for cid, func in d.items()} + for s, d in self.callbacks.items()} + self._func_cid_map = { + s: {proxy: cid for cid, proxy in d.items()} + for s, d in self.callbacks.items()} def connect(self, s, func): """Register *func* to be called when signal *s* is generated.""" self._func_cid_map.setdefault(s, {}) - try: - proxy = weakref.WeakMethod(func, self._remove_proxy) - except TypeError: - proxy = _StrongRef(func) + proxy = _weak_or_strong_ref(func, self._remove_proxy) if proxy in self._func_cid_map[s]: return self._func_cid_map[s][proxy] - cid = next(self._cid_gen) self._func_cid_map[s][proxy] = cid self.callbacks.setdefault(s, {}) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b1ae93bd2b37..d1b0b9cc7638 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2280,6 +2280,10 @@ def __init__(self, super().__init__() self.callbacks = cbook.CallbackRegistry() + # Callbacks traditionally associated with the canvas (and exposed with + # a proxy property), but that actually need to be on the figure for + # pickling. + self._canvas_callbacks = cbook.CallbackRegistry() if figsize is None: figsize = mpl.rcParams['figure.figsize'] diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d732631a260e..80cf7fa473f8 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -118,12 +118,14 @@ def __init__( self._zcid = None self.mouse_init() - self.figure.canvas.mpl_connect( - 'motion_notify_event', self._on_move), - self.figure.canvas.mpl_connect( - 'button_press_event', self._button_press), - self.figure.canvas.mpl_connect( - 'button_release_event', self._button_release), + self.figure.canvas.callbacks._pickled_cids.update({ + self.figure.canvas.mpl_connect( + 'motion_notify_event', self._on_move), + self.figure.canvas.mpl_connect( + 'button_press_event', self._button_press), + self.figure.canvas.mpl_connect( + 'button_release_event', self._button_release), + }) self.set_top_view() self.patch.set_linewidth(0) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy