diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index d99323d0608e..f4c311ac649d 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -112,7 +112,7 @@ def __init__(self): except AttributeError: # Handle self.axes as a read-only property, as in Figure. pass - self._remove_method = None + self._on_remove = [] self._url = None self._gid = None self._snap = None @@ -142,31 +142,33 @@ def remove(self): Note: there is no support for removing the artist's legend entry. """ - # There is no method to set the callback. Instead the parent should - # set the _remove_method attribute directly. This would be a - # protected attribute if Python supported that sort of thing. The - # callback has one parameter, which is the child to be removed. - if self._remove_method is not None: - self._remove_method(self) - # clear stale callback - self.stale_callback = None - _ax_flag = False - if hasattr(self, 'axes') and self.axes: - # remove from the mouse hit list - self.axes.mouseover_set.discard(self) - # mark the axes as stale - self.axes.stale = True - # decouple the artist from the axes - self.axes = None - _ax_flag = True - - if self.figure: - self.figure = None - if not _ax_flag: - self.figure = True - - else: + # There is no method to set the callbacks. Instead the parent should + # populate the _on_remove list directly. This would be a protected + # attribute if Python supported that sort of thing. The callbacks have + # one parameter, which is the child to be removed. + if not self._on_remove: raise NotImplementedError('cannot remove artist') + + for cb in self._on_remove: + cb(self) + self._on_remove = [] + # clear stale callback + self.stale_callback = None + _ax_flag = False + if hasattr(self, 'axes') and self.axes: + # remove from the mouse hit list + self.axes.mouseover_set.discard(self) + # mark the axes as stale + self.axes.stale = True + # decouple the artist from the axes + self.axes = None + _ax_flag = True + + if self.figure: + self.figure = None + if not _ax_flag: + self.figure = True + # TODO: the fix for the collections relim problem is to move the # limits calculation into the artist itself, including the property of # whether or not the artist should affect the limits. Then there will diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a223c4e563bf..5b6f020a9611 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -378,7 +378,7 @@ def legend(self, *args, **kwargs): if len(extra_args): raise TypeError('legend only accepts two non-keyword arguments') self.legend_ = mlegend.Legend(self, handles, labels, **kwargs) - self.legend_._remove_method = self._remove_legend + self.legend_._on_remove = [self._remove_legend] return self.legend_ def _remove_legend(self, legend): diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9979bc543036..d4ba48613b8e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1860,7 +1860,7 @@ def add_artist(self, a): """ a.axes = self self.artists.append(a) - a._remove_method = self.artists.remove + a._on_remove = [self.artists.remove] self._set_artist_props(a) a.set_clip_path(self.patch) self.stale = True @@ -1877,7 +1877,7 @@ def add_collection(self, collection, autolim=True): if not label: collection.set_label('_collection%d' % len(self.collections)) self.collections.append(collection) - collection._remove_method = self.collections.remove + collection._on_remove = [self.collections.remove] self._set_artist_props(collection) if collection.get_clip_path() is None: @@ -1899,7 +1899,7 @@ def add_image(self, image): if not image.get_label(): image.set_label('_image%d' % len(self.images)) self.images.append(image) - image._remove_method = self.images.remove + image._on_remove = [self.images.remove] self.stale = True return image @@ -1922,7 +1922,7 @@ def add_line(self, line): if not line.get_label(): line.set_label('_line%d' % len(self.lines)) self.lines.append(line) - line._remove_method = self.lines.remove + line._on_remove = [self.lines.remove] self.stale = True return line @@ -1932,7 +1932,7 @@ def _add_text(self, txt): """ self._set_artist_props(txt) self.texts.append(txt) - txt._remove_method = self.texts.remove + txt._on_remove = [self.texts.remove] self.stale = True return txt @@ -1995,7 +1995,7 @@ def add_patch(self, p): p.set_clip_path(self.patch) self._update_patch_limits(p) self.patches.append(p) - p._remove_method = self.patches.remove + p._on_remove = [self.patches.remove] return p def _update_patch_limits(self, patch): @@ -2041,7 +2041,7 @@ def add_table(self, tab): self._set_artist_props(tab) self.tables.append(tab) tab.set_clip_path(self.patch) - tab._remove_method = self.tables.remove + tab._on_remove = [self.tables.remove] return tab def add_container(self, container): @@ -2055,7 +2055,7 @@ def add_container(self, container): if not label: container.set_label('_container%d' % len(self.containers)) self.containers.append(container) - container._remove_method = self.containers.remove + container._on_remove = [self.containers.remove] return container def _on_units_changed(self, scalex=False, scaley=False): diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 73ce21355fa8..a32bf211fc27 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -22,23 +22,21 @@ def __init__(self, kl, label=None): self.eventson = False # fire events only if eventson self._oid = 0 # an observer id self._propobservers = {} # a dict from oids to funcs - - self._remove_method = None + self._on_remove = [] self.set_label(label) @cbook.deprecated("3.0") def set_remove_method(self, f): - self._remove_method = f + self._on_remove = [f] def remove(self): for c in cbook.flatten( self, scalarp=lambda x: isinstance(x, martist.Artist)): if c is not None: c.remove() - - if self._remove_method: - self._remove_method(self) + for cb in self._on_remove: + cb(self) def get_label(self): """ diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index e82858a835b5..333862c1e4b2 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -885,7 +885,7 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, if norm is None: im.set_clim(vmin, vmax) self.images.append(im) - im._remove_method = self.images.remove + im._on_remove = [self.images.remove] self.stale = True return im @@ -1157,7 +1157,7 @@ def add_axes(self, *args, **kwargs): self._axstack.add(key, a) self.sca(a) - a._remove_method = self._remove_ax + a._on_remove = [self._remove_ax] self.stale = True a.stale_callback = _stale_figure_callback return a @@ -1264,7 +1264,7 @@ def add_subplot(self, *args, **kwargs): a = subplot_class_factory(projection_class)(self, *args, **kwargs) self._axstack.add(key, a) self.sca(a) - a._remove_method = self._remove_ax + a._on_remove = [self._remove_ax] self.stale = True a.stale_callback = _stale_figure_callback return a @@ -1598,7 +1598,7 @@ def legend(self, *args, **kwargs): pass l = mlegend.Legend(self, handles, labels, *extra_args, **kwargs) self.legends.append(l) - l._remove_method = self.legends.remove + l._on_remove = [self.legends.remove] self.stale = True return l @@ -1626,7 +1626,7 @@ def text(self, x, y, s, *args, **kwargs): t.update(override) self._set_artist_props(t) self.texts.append(t) - t._remove_method = self.texts.remove + t._on_remove = [self.texts.remove] self.stale = True return t diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 9094dc5a1bf2..e06063212c40 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1107,24 +1107,26 @@ def draggable(self, state=None, use_blit=False, update="loc"): when dragged. If update is "loc", the *loc* parameter of the legend is changed. If "bbox", the *bbox_to_anchor* parameter is changed. """ - is_draggable = self._draggable is not None - # if state is None we'll toggle if state is None: - state = not is_draggable + state = self._draggable is None if state: if self._draggable is None: - self._draggable = DraggableLegend(self, - use_blit, - update=update) + self._draggable = DraggableLegend( + self, use_blit, update=update) + self._on_remove.append(self._set_undraggable) else: - if self._draggable is not None: - self._draggable.disconnect() - self._draggable = None + self._set_undraggable() return self._draggable + def _set_undraggable(self, _=None): + if self._draggable is not None: + self._on_remove.remove(self._set_undraggable) + self._draggable.disconnect() + self._draggable = None + # Helper functions to parse legend arguments for both `figure.legend` and # `axes.legend`: diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 77cf2313427b..70882807c83d 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1837,22 +1837,26 @@ def draggable(self, state=None, use_blit=False): draggable is on. """ from matplotlib.offsetbox import DraggableAnnotation - is_draggable = self._draggable is not None # if state is None we'll toggle if state is None: - state = not is_draggable + state = self._draggable is None if state: if self._draggable is None: self._draggable = DraggableAnnotation(self, use_blit) + self._on_remove.append(self._set_undraggable) else: - if self._draggable is not None: - self._draggable.disconnect() - self._draggable = None + self._set_undraggable() return self._draggable + def _set_undraggable(self, _=None): + if self._draggable is not None: + self._on_remove.remove(self._set_undraggable) + self._draggable.disconnect() + self._draggable = None + class Annotation(Text, _AnnotationBase): def __str__(self): diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index a543a012c915..f7cc2334ae8c 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -242,7 +242,7 @@ def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=None): # note that ax2.transData == tr + ax1.transData # Anthing you draw in ax2 will match the ticks and grids of ax1. self.parasites.append(ax2) - ax2._remove_method = self.parasites.remove + ax2._on_remove = [self.parasites.remove] return ax2 def _get_legend_handles(self, legend_handler_map=None): @@ -303,7 +303,7 @@ def twinx(self, axes_class=None): ax2 = parasite_axes_class(self, sharex=self, frameon=False) self.parasites.append(ax2) - ax2._remove_method = self._remove_twinx + ax2._on_remove = [self._remove_twinx] self.axis["right"].set_visible(False) @@ -332,7 +332,7 @@ def twiny(self, axes_class=None): ax2 = parasite_axes_class(self, sharey=self, frameon=False) self.parasites.append(ax2) - ax2._remove_method = self._remove_twiny + ax2._on_remove = [self._remove_twiny] self.axis["top"].set_visible(False) @@ -367,7 +367,6 @@ def twin(self, aux_trans=None, axes_class=None): ax2 = parasite_axes_auxtrans_class( self, aux_trans, viewlim_mode="transform") self.parasites.append(ax2) - ax2._remove_method = self.parasites.remove self.axis["top", "right"].set_visible(False) @@ -378,7 +377,7 @@ def _remove_method(h): self.parasites.remove(h) self.axis["top", "right"].set_visible(True) self.axis["top", "right"].toggle(ticklabels=False, label=False) - ax2._remove_method = _remove_method + ax2._on_remove = [_remove_method] return ax2