Animation in JupyterLab produces a moving and a static graphic. Why?

I am an absolute novice in Python and Jupyter, and also new to this forum! So it’s likely that I am missing something obvious.

I have produced an animation within JupyterLab which works fine. It produces an animation along with control buttons (play/stop etc.) as expected. At the same time, it also produces a second, static graphic which is useless to me. I don’t know where second graphic is coming from and how to get rid of it. I would appreciate a remedy, and also comments for imporving the code in general.

Here is my code:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from IPython.display import HTML

fig, ax = plt.subplots()

x = np.arange(0, 2*np.pi, 0.1)
line, = ax.plot(x, np.sin(x))
z = x.size

def animate(i):
    line.set_ydata(np.sin(x - 2*np.pi*i / z)) 
    return line,

ani = animation.FuncAnimation(
    fig, animate,
    frames = z,
    blit=True)

HTML(ani.to_jshtml())

This is done in Firefox in Linux. Here is the output of “jupyter --verion”:

IPython : 8.31.0
ipykernel : 6.29.5
ipywidgets : not installed
jupyter_client : 8.6.3
jupyter_core : 5.7.2
jupyter_server : 2.15.0
jupyterlab : 4.3.4
nbclient : 0.10.2
nbconvert : 7.16.5
nbformat : 5.10.4
notebook : not installed
qtconsole : not installed
traitlets : 5.14.3

UPDATE: here now addresses it, even adapting your code


I’ve seen this before. It stems, I think from the way modern Jupyter tries to evaluate a cell for an active plot and display it automatically. I cannot recall if I found a way way around out. I may be able to investigate more on Monday over at my animated_matplotlib-binder where I like to keep good examples. I also had come across a similar thing with a more complicated use of ipywidgets and wrapping it an an output widget helped. I don’t have time to explore if that may help here to absolutely control.

For now…
I can offer for now this work around. Your code will keep producing the issue. My code will at least only produce it once. When you run the code the second time, it just produces the plot with the controller widget.

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from IPython.display import HTML
plt.rcParams["animation.html"] = "jshtml"
plt.ioff() #needed so the second time you run it you get only single plot

fig, ax = plt.subplots()

x = np.arange(0, 2*np.pi, 0.1)
line, = ax.plot(x, np.sin(x))
z = x.size

def animate(i):
    line.set_ydata(np.sin(x - 2*np.pi*i / z)) 
    return line,

ani = animation.FuncAnimation(
    fig, animate,
    frames = z,
    blit=True)
ani

Thank you, Wayne, for the workaround. Please let us know if you find a more permanent solution.

By the way, as a newcomer to Jupyter, I found your Guide to distinguishing Jupyter interfaces early 2024.md of great help. Thanks!

1 Like

Sorry, I didn’t have time the other day to explore this better…

That static graphic corresponds to the last frame generated.

The more permanent solution is to close the current active plot view so that modern Jupyter doesn’t detect that active plot object and display it.
In practical terms for your example code, that means adding plt.close() in the plot generating code cell just before you call the display of the playback widget that you made for stepping through the calculated frames.
Here is your code adapted to do that by adding that one line:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from IPython.display import HTML

fig, ax = plt.subplots()

x = np.arange(0, 2*np.pi, 0.1)
line, = ax.plot(x, np.sin(x))
z = x.size

def animate(i):
    line.set_ydata(np.sin(x - 2*np.pi*i / z)) 
    return line,

ani = animation.FuncAnimation(
    fig, animate,
    frames = z,
    blit=True)

plt.close()
HTML(ani.to_jshtml())

Related examples:

  • here
  • here
  • top example here
  • Plus, note that this is related in a way to the fact you no longer need plt.show() in modern Jupyter, even though most examples still include it.

Glad you asked because it prompted me to update my animated_matplotlib_binder for where I show the examples generating the playback controller widget that lets you step through the animation frames.

Tried out your solution. Works perfectly! Thanks for looking through the issue.

1 Like