ipympl: Saving png figures matplotlib 3.3.0 results in "AttributeError: __delete__"

Describe the issue

When trying to save png figures with ipympl, interactive widgets, and the latest matplotlib (3.3.0), the saving fails with the following error message:

import matplotlib
matplotlib.use('module://ipympl.backend_nbagg')

from matplotlib import pyplot as plt
fig = plt.figure()
fig.savefig('test.png')

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-c4b3ac84131c> in <module>
      6 from matplotlib import pyplot as plt
      7 fig = plt.figure()
----> 8 fig.savefig('test.png')

/usr/local/lib/python3.8/dist-packages/matplotlib/figure.py in savefig(self, fname, transparent, **kwargs)
   2309                 patch.set_edgecolor('none')
   2310 
-> 2311         self.canvas.print_figure(fname, **kwargs)
   2312 
   2313         if transparent:

/usr/local/lib/python3.8/dist-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2223                 self.figure.set_edgecolor(origedgecolor)
   2224                 self.figure.set_canvas(self)
-> 2225             return result
   2226 
   2227     @classmethod

/usr/lib/python3.8/contextlib.py in __exit__(self, type, value, traceback)
    118         if type is None:
    119             try:
--> 120                 next(self.gen)
    121             except StopIteration:
    122                 return False

/usr/local/lib/python3.8/dist-packages/matplotlib/cbook/__init__.py in _setattr_cm(obj, **kwargs)
   2064         for attr, orig in origs.items():
   2065             if orig is sentinel:
-> 2066                 delattr(obj, attr)
   2067             else:
   2068                 setattr(obj, attr, orig)

AttributeError: __delete__

Saving figures as pdf or pngfigures with an older Matplotlib version (3.2.0) works well.

Versions

Matplotlib version: 3.3.0

python -c "import sys; print('\n',sys.version); import ipympl; print('ipympl version:', ipympl.__version__)" && jupyter --version && jupyter nbextension list && jupyter labextension list

–>

3.8.5 (default, Jul 20 2020, 18:32:44)
[GCC 9.3.0]
ipympl version: 0.5.7
jupyter core     : 4.6.3
jupyter-notebook : 6.0.3
qtconsole        : 4.7.4
ipython          : 7.16.1
ipykernel        : 5.3.4
jupyter client   : 6.1.6
jupyter lab      : 2.2.2
nbconvert        : 5.6.1
ipywidgets       : 7.5.1
nbformat         : 5.0.7
traitlets        : 4.3.3
Known nbextensions:
  config dir: /usr/etc/jupyter/nbconfig
    notebook section
      jupyter-js-widgets/extension disabled
  config dir: /usr/local/etc/jupyter/nbconfig
    notebook section
      bqplot/extension  enabled
      - Validating: OK
      itkwidgets/extension  enabled
      - Validating: OK
      jupyter-datawidgets/extension  enabled
      - Validating: OK
      jupyter-leaflet/extension  enabled
      - Validating: OK
      jupyter-matplotlib/extension  enabled
      - Validating: OK
      jupyter-js-widgets/extension  enabled
      - Validating: OK
  config dir: /etc/jupyter/nbconfig
    notebook section
      jupyter-js-widgets/extension  enabled
      - Validating: OK
JupyterLab v2.2.2
Known labextensions:
   app dir: /usr/local/share/jupyter/lab
        @jupyter-widgets/jupyterlab-manager v2.0.0  enabled  OK
        @jupyterlab/hub-extension v2.0.2  enabled  OK
        jupyter-matplotlib v0.7.3  enabled  OK
        jupyterlab-datawidgets v6.3.0  enabled  OK

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 3
  • Comments: 21 (9 by maintainers)

Most upvoted comments

For me it was fixed with the new release 0.5.8 after the fix from @ianhi . But I have to admit I moved on to the next matplotlib release already. Are you using ipympl 0.5.8?

Edit: I just saw on the readme page that ipympl 0.5.8 requires matplotlib 3.3.1. Couldn’t you move to this one? Or is 3.3.0 necessary?

and if this fixes the problem, then I would say this is the optimal solution as it does not involve core Matplotlib picking up any more complexity and reduces the exposed public surface on the ipympl side.

@tacaswell sorry I keep diverting away from the final solution of implementing better handling in matplotlib, unfortunately I don’t think I can help there. But! I do have a workaround that can be implemented in ipympl that seems to give all the correct behaviors and I don’t think will messing with the context manager:

It turns out that _is_saving only shows up in one place in the codebase: https://github.com/matplotlib/ipympl/search?q=_is_saving&unscoped_q=_is_saving Most importantly, it is not used at all on the javascript side so I don’t think it actually needs to be defined using traitlets. So if you delete the line defining it using traitlets then everything works as expected because it inherits _is_saving from its super class:

diff --git a/ipympl/backend_nbagg.py b/ipympl/backend_nbagg.py
index de37bab..8b88609 100644
--- a/ipympl/backend_nbagg.py
+++ b/ipympl/backend_nbagg.py
@@ -199,7 +199,6 @@ class Canvas(DOMWidget, FigureCanvasWebAggCore):
     _current_image_mode = Unicode()
     _dpi_ratio = Float(1.0)
     _is_idle_drawing = Bool()
-    _is_saving = Bool()
     _button = Any()
     _key = Any()
     _lastx = Any()

Which allows

%matplotlib widget
import matplotlib
from matplotlib import pyplot as plt
with plt.ioff():
    fig = plt.figure()
plt.plot([0,1],[0,1])
fig.savefig('test.png')
display(fig)
print(f'Matplotlib version: {matplotlib.__version__}')
print(fig.canvas._is_saving) # no attribute error!!

to give the expected output: image

I know this isn’t the optimal solution but how would you feel about using to fix the immediate problem?

this is also a problem if you display(fig).

I think is an important bug. That error is a bit opaque to me though. @tacaswell @martinRenou @thomasaarholt do any of you have thoughts on where to start here?