matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
20.27k stars 7.65k forks source link

[Bug]: Artist.remove() isn't fully removing it from figure #25572

Open raamana opened 1 year ago

raamana commented 1 year ago

Bug summary

I am trying to remove some artists (RadioButtons and CheckButtons) while keeping some others (axes with images), before exporting a figure to disk, and am running into this error: AttributeError: 'NoneType' object has no attribute 'canvas'

as MPL is trying to check if canvas for RadioButton has changed, although that RadioButton should not part of the equation at all as it was removed prior to trying to .savefig()

Code for reproduction

import matplotlib.pyplot as plt

from matplotlib.widgets import RadioButtons

fig, ax = plt.subplots(1, 1)

radio = RadioButtons(ax, ['1', '2'], 0)

radio.ax.remove()

fig.savefig('~/Downloads/saved_after_artist_remove.png')

Actual outcome

Traceback (most recent call last):
  File "/Users/Reddy/dev/visualqc/visualqc/tests/artist_remove.py", line 12, in <module>
    fig.savefig('~/Downloads/saved_after_artist_remove.png')
  File "/usr/local/lib/python3.11/site-packages/matplotlib/figure.py", line 3343, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 2366, in print_figure
    result = print_method(
             ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 2232, in <lambda>
    print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
                                                                 ^^^^^
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backends/backend_agg.py", line 509, in print_png
    self._print_pil(filename_or_obj, "png", pil_kwargs, metadata)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backends/backend_agg.py", line 457, in _print_pil
    FigureCanvasAgg.draw(self)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backends/backend_agg.py", line 400, in draw
    self.figure.draw(self.renderer)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/artist.py", line 95, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/matplotlib/artist.py", line 72, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/matplotlib/figure.py", line 3150, in draw
    DrawEvent("draw_event", self.canvas, renderer)._process()
  File "/usr/local/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 1263, in _process
    self.canvas.callbacks.process(self.name, self)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/cbook/__init__.py", line 309, in process
    self.exception_handler(exc)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/cbook/__init__.py", line 96, in _exception_printer
    raise exc
  File "/usr/local/lib/python3.11/site-packages/matplotlib/cbook/__init__.py", line 304, in process
    func(*args, **kwargs)
  File "/usr/local/lib/python3.11/site-packages/matplotlib/widgets.py", line 1719, in _clear
    if self.ignore(event) or self._changed_canvas():
                             ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/matplotlib/widgets.py", line 107, in _changed_canvas
    return self.canvas is not self.ax.figure.canvas
                              ^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'canvas'

Expected outcome

smooth export with no error

Additional information

No response

Operating system

macOS 13

Matplotlib Version

3.7.1

Matplotlib Backend

TkAgg

Python version

3.11

Jupyter version

No response

Installation

pip

raamana commented 1 year ago

likely related to this 2015 comment from @tacaswell : Apparently the remove is not doing enough clean up: https://github.com/matplotlib/matplotlib/issues/5663/#issuecomment-164228174

QuLogic commented 1 year ago

At the moment, I think you can workaround this by calling radio.disconnect_events() before removing the Axes.

Higgs32584 commented 1 year ago

This is likely related to #25274, although this is likely different enough to warrant its own issue. The legend appears to be unstable right now.

tacaswell commented 1 year ago

I do not think this is related to #25274 as in that case the Container is still in the list of containers however in this case the issue is that there is a dangling callback that was not cleared.

raamana commented 1 year ago

At the moment, I think you can workaround this by calling radio.disconnect_events() before removing the Axes.

that helped. thanks everyone!