matplotlib / ipympl

Matplotlib Jupyter Integration
https://matplotlib.org/ipympl/
BSD 3-Clause "New" or "Revised" License
1.58k stars 227 forks source link

Plotting error with ipympl 0.5 series #204

Closed thomasaarholt closed 4 years ago

thomasaarholt commented 4 years ago

Trying to do interactive plotting with the hyperspy library, users have recently (the last two weeks) started experiencing the following bug when we try to plot after upgrading ipympl. The following fails with %matplotlib widget, but works with %matplotlib notebook. (This is not testable with lab, since the notebook backend doesn't work there.)

Since it only started happening recently (and we didn't update hyperspy), I believe something has changed in the recent versions of ipympl that broke it for us. If the implemented change is for the better, we would be happy to change our code, but I'm not sure what has changed.

# The following works if we replace widget with notebook
%matplotlib widget
import hyperspy.api as hs

s = hs.signals.Signal1D([1,2,3])
s.plot()

This is the expected plot:

The error is the (long) following:

``` --------------------------------------------------------------------------- IndexError Traceback (most recent call last) in ----> 1 s.plot() c:\users\thomasaar\documents\github\hyperspy\hyperspy\signal.py in plot(self, navigator, axes_manager, plot_markers, **kwargs) 2158 " \"slider\", None, a Signal instance") 2159 -> 2160 self._plot.plot(**kwargs) 2161 self.events.data_changed.connect(self.update_plot, []) 2162 if self._plot.signal_plot: c:\users\thomasaar\documents\github\hyperspy\hyperspy\drawing\mpl_he.py in plot(self, **kwargs) 174 self.pointer.connect_navigate() 175 self.plot_navigator(**kwargs.pop('navigator_kwds', {})) --> 176 self.plot_signal(**kwargs) 177 178 def assign_pointer(self): c:\users\thomasaar\documents\github\hyperspy\hyperspy\drawing\mpl_hse.py in plot_signal(self, **kwargs) 124 125 self.signal_plot = sf --> 126 sf.plot(**kwargs) 127 if sf.figure is not None: 128 if self.axes_manager.navigation_axes: c:\users\thomasaar\documents\github\hyperspy\hyperspy\drawing\signal1d.py in plot(self, data_function_kwargs, **kwargs) 135 # complains 136 pass --> 137 self.figure.canvas.draw() 138 139 def _on_close(self): ~\.conda\envs\py5\lib\site-packages\matplotlib\backends\backend_webagg_core.py in draw(self) 149 self._png_is_old = True 150 try: --> 151 super().draw() 152 finally: 153 self.manager.refresh_all() # Swap the frames. ~\.conda\envs\py5\lib\site-packages\matplotlib\backends\backend_agg.py in draw(self) 388 self.renderer = self.get_renderer(cleared=True) 389 # Acquire a lock on the shared font cache. --> 390 with RendererAgg.lock, \ 391 (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar 392 else nullcontext()): ~\.conda\envs\py5\lib\contextlib.py in __enter__(self) 111 del self.args, self.kwds, self.func 112 try: --> 113 return next(self.gen) 114 except StopIteration: 115 raise RuntimeError("generator didn't yield") from None ~\.conda\envs\py5\lib\site-packages\matplotlib\backend_bases.py in _wait_cursor_for_draw_cm(self) 2771 if self._draw_time - last_draw_time > 1: 2772 try: -> 2773 self.set_cursor(cursors.WAIT) 2774 yield 2775 finally: ~\.conda\envs\py5\lib\site-packages\matplotlib\backends\backend_webagg_core.py in set_cursor(self, cursor) 379 def set_cursor(self, cursor): 380 if cursor != self.cursor: --> 381 self.canvas.send_event("cursor", cursor=cursor) 382 self.cursor = cursor 383 ~\.conda\envs\py5\lib\site-packages\matplotlib\backends\backend_webagg_core.py in send_event(self, event_type, **kwargs) 344 345 def send_event(self, event_type, **kwargs): --> 346 self.manager._send_event(event_type, **kwargs) 347 348 ~\.conda\envs\py5\lib\site-packages\matplotlib\backends\backend_webagg_core.py in _send_event(self, event_type, **kwargs) 487 payload = {'type': event_type, **kwargs} 488 for s in self.web_sockets: --> 489 s.send_json(payload) 490 491 ~\.conda\envs\py5\lib\site-packages\ipympl\backend_nbagg.py in send_json(self, content) 207 if content['type'] == 'cursor': 208 cursors = ['pointer', 'default', 'crosshair', 'move'] --> 209 self._cursor = cursors[content['cursor']] 210 211 elif content['type'] == 'message': IndexError: list index out of range ```

thomasaarholt commented 4 years ago

Actually, I'm now thinking this is due to matplotlib 3.2.0 instead. I just tested by upgrading ipympl from 0.5.2 to 0.5.3 in an older environment I had, and it worked fine there. In the older library, mpl is version 3.1.3. Upgrading (which did upgrade a lot of libraries) introduces the error in the post.

ericpre commented 4 years ago

I can reproduce with matplotlib 3.2.0 (3.1.3 is working fine) with the following minimal example:

%matplotlib widget
import matplotlib.pyplot as plt

fig = plt.figure()
plt.plot([1, 2, 3])

fig.canvas.draw()

The fig.canvas.draw call triggers the issue described above.

thomasaarholt commented 4 years ago

Ah, I didn't even think to try a standard matplotlib example. Thanks @ericpre.

martinRenou commented 4 years ago

Thanks. I will fix this.

martinRenou commented 4 years ago

It might be that hyperspy supports a new kind of cursor.

What happens if you put a print(content) line 208 of file ~\.conda\envs\py5\lib\site-packages\ipympl\backend_nbagg.py

thomasaarholt commented 4 years ago

The following is the result of the print. The print is the same with my hyperspy example as with @ericpre's MWE above (non-hyperspy).

{'type': 'cursor', 'cursor': <Cursors.WAIT: 4>}
{'type': 'cursor', 'cursor': <Cursors.POINTER: 1>}

To be clear, @ericpre's example above shows that this issue is independent of hyperspy, and hyperspy hasn't changed its cursor (to the best of my knowledge) in many years. (that said, I don't really know what kind of cursor we're talking about here)

martinRenou commented 4 years ago

It might be a bug that has been introduced recently in ipympl then.

Let me fix this. The fix should be straightforward.

martinRenou commented 4 years ago

I just released the Python package, 0.5.4 should be available via pip now. Coming soon via conda.

thomasaarholt commented 4 years ago

Excellent work! Thanks @martinRenou!