matplotlib / ipympl

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

Mock tornado for jupyterlite #381

Closed martinRenou closed 2 years ago

martinRenou commented 2 years ago

See #334

github-actions[bot] commented 2 years ago

Binder :point_left: Launch a binder notebook on branch _martinRenou/ipympl/supportjupyterlite

lgtm-com[bot] commented 2 years ago

This pull request introduces 1 alert when merging e5b2cf919ded805262838e9a482f28191a92c58c into 76111ce56165e07dd87fe246613bd7c5bd66fa64 - view on LGTM.com

new alerts:

lgtm-com[bot] commented 2 years ago

This pull request introduces 1 alert when merging 011ee0fbf2858a9624ec2ef86f54b30d1d589ccd into 558cc471f949892438ac3fd9c37eb7c45592c6f9 - view on LGTM.com

new alerts:

ianhi commented 2 years ago

This broke the save_animation functionality of mpl-interactions as that does some tricky things with animations. As in the review comment I think that the check for if we're in a pyodide context is not set up correctly.

%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np

import mpl_interactions.ipyplot as iplt

x = np.linspace(0, 2 * np.pi, 200)

def f(x, amp, freq):
    return amp * np.sin(x * freq)

# Create the plot as normal
fig, ax = plt.subplots()
controls = iplt.plot(x, f, freq=(0.05, 10, 250), amp=(1, 10))
_ = iplt.title("the Frequency is: {freq:.2f}", controls=controls["freq"])

# save as a gif
anim = controls.save_animation("freq-plot-1.gif", fig, "freq", interval=35)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-6978a96bf4a5> in <module>
     18 
     19 # save as a gif
---> 20 anim = controls.save_animation("freq-plot-1.gif", fig, "freq", interval=35)

~/mambaforge/envs/micro/lib/python3.8/site-packages/mpl_interactions/controller.py in save_animation(self, filename, fig, param, interval, func_anim_kwargs, N_frames, **kwargs)
    318         # draw then stop necessary to prevent an extra loop after finished saving
    319         # see https://discourse.matplotlib.org/t/how-to-prevent-funcanimation-looping-a-single-time-after-save/21680/2
--> 320         fig.canvas.draw()
    321         anim.event_source.stop()
    322         anim.save(filename, **kwargs)

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/backends/backend_webagg_core.py in draw(self)
    151         self._png_is_old = True
    152         try:
--> 153             super().draw()
    154         finally:
    155             self.manager.refresh_all()  # Swap the frames.

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py in draw(self)
    404              (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    405               else nullcontext()):
--> 406             self.figure.draw(self.renderer)
    407             # A GUI class may be need to update a window using this draw, so
    408             # don't forget to call the superclass.

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     72     @wraps(draw)
     73     def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74         result = draw(artist, renderer, *args, **kwargs)
     75         if renderer._rasterizing:
     76             renderer.stop_rasterizing()

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     49                 renderer.start_filter()
     50 
---> 51             return draw(artist, renderer, *args, **kwargs)
     52         finally:
     53             if artist.get_agg_filter() is not None:

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/figure.py in draw(self, renderer)
   2788             self.stale = False
   2789 
-> 2790         self.canvas.draw_event(renderer)
   2791 
   2792     def draw_artist(self, a):

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/backend_bases.py in draw_event(self, renderer)
   1790         s = 'draw_event'
   1791         event = DrawEvent(s, self, renderer)
-> 1792         self.callbacks.process(s, event)
   1793 
   1794     def resize_event(self):

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/cbook/__init__.py in process(self, s, *args, **kwargs)
    273                 except Exception as exc:
    274                     if self.exception_handler is not None:
--> 275                         self.exception_handler(exc)
    276                     else:
    277                         raise

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/cbook/__init__.py in _exception_printer(exc)
     87 def _exception_printer(exc):
     88     if _get_running_interactive_framework() in ["headless", None]:
---> 89         raise exc
     90     else:
     91         traceback.print_exc()

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/cbook/__init__.py in process(self, s, *args, **kwargs)
    268             if func is not None:
    269                 try:
--> 270                     func(*args, **kwargs)
    271                 # this does not capture KeyboardInterrupt, SystemExit,
    272                 # and GeneratorExit

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/animation.py in _start(self, *args)
    994         # actually start the event_source.
    995         self.event_source.add_callback(self._step)
--> 996         self.event_source.start()
    997 
    998     def _stop(self, *args):

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/backend_bases.py in start(self, interval)
   1132         if interval is not None:
   1133             self.interval = interval
-> 1134         self._timer_start()
   1135 
   1136     def stop(self):

~/mambaforge/envs/micro/lib/python3.8/site-packages/matplotlib/backends/backend_webagg_core.py in _timer_start(self)
     93                 self._on_timer)
     94         else:
---> 95             self._timer = tornado.ioloop.PeriodicCallback(
     96                 self._on_timer,
     97                 max(self.interval, 1e-6))

AttributeError: module 'tornadofake' has no attribute 'ioloop'
ianhi commented 2 years ago

simpler matplotlib only reproduction:

%matplotlib ipympl

from matplotlib import pyplot as plt
from matplotlib import animation

fig = plt.figure()

def animate(i):
    pass

anim = animation.FuncAnimation(fig, animate)
fig.canvas.draw()