0

In Jupyter Lab on Windows, the matplotlib animation API documentation breaks when a no-op edit is added in the update loop.

%matplotlib ipympl
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = ax.plot([], [], 'ro')

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    #if False:
    #    xdata = xdata[1:]
    #    ydata = ydata[1:]
    ln.set_data(xdata, ydata)
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True)
plt.show()

The above is verbatim the code provided in the documentation, plus three comments in update. With the 3 lines commented, the code works; a sine wave is displayed from start to finish over small intervals.

However, when the three lines are uncommented, the animation never starts. My goal was to show a fixed-size sliding window of the function at a time. I started with the condition if len(xdata) > 10:. I learned that the condition never has to be met for the animation to break. In fact, the condition can be impossible.

The conditional statement

if len(xdata) > 10:
    pass

does NOT cause the animation to fail, indicating that it's the block within the condition - not the condition itself - that is the breaking change.

I have also tried setting blit=False and removing the return statements, but the same behavior happens - the animation never starts.

Versions:

ipykernel==6.29.4
ipympl==0.9.6
ipython==8.25.0
ipywidgets==8.1.5
jupyterlab==4.3.5
matplotlib==3.10.0
numpy==1.26.4
3
  • TIP: please use %matplotlib ipympl to be explicit that ipympl is involved, see the documentation here. That widget version is outdated now that JupyterLab and Jupyter Notebook use the same underlying componentes and it actually uses ipympl under the hood. It is only allowed for 'legacy' support at this point. Commented Feb 21 at 20:56
  • Thanks! Unfortunately, the animation is still broken. Commented Feb 23 at 4:12
  • Yes, sorry. It was just a clarification that it would be better to make things explicit and match modern stuff. Since it uses the same thing under the hood, it would not be expected to help your case. As to your issue, I think it is a Python/matplotlib thing. I noticed if you ran it first without restarting the kernel animation worked. Which suggested to me it is a state or namespace thing. I'd have to study it way more but it feels you are making an XY problem out of it by straying from your stated goal, which was, "My goal was to show a fixed-size sliding window of the function at a time" Commented Feb 24 at 13:38

1 Answer 1

1

The problem is how Python scoping works and the if False: attempt was further a red herring misleading you away from the real issue assignment vs. in-place modification of your variables.

Consider back to the conditional if len(xdata) > 10:, when you do xdata = xdata[1:] in update(), you're not modifying the global xdata list. Instead, you're creating a new local variable called xdata that only exists in the scope of the local update() function. The original global xdata remains unchanged. So nothing was happening because you call the update(frame) function and then when the condition triggers it creates the local variables that 'shadow' the globals. When the function encounters the return, those local variables are discarded and then the next time it is still working with the unmodified global xdata and ydata.

You could fix your original code where you wanted to use len(xdata) > 10: by adding global, like so:

%matplotlib ipympl
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
# Change to a line plot instead of just points
ln, = ax.plot([], [], 'r-')  # Use 'r-' for a red line

# Maximum number of points to display
max_points = 25

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    global xdata, ydata
    xdata.append(frame)
    ydata.append(np.sin(frame))
    
    # Only keep the most recent max_points
    if len(xdata) > max_points:
        xdata.pop(0)  # Remove the oldest point
        ydata.pop(0)
        
    ln.set_data(xdata, ydata)
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True, interval=50)
plt.show()

Optionally you could use list methods that modify in place and don't assign. Like xdata.pop(0).

So what was going on when you tried if False:? I'm not quite sure but you weren't getting closer to what you wanted anyway and so I feel you were making an XY problem there.

Another option for implementing your goal

Alternative to do what you stated as your goal to making a sliding window would be the following, I think:

%matplotlib ipympl
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# Create figure and axis
fig, ax = plt.subplots(figsize=(10, 6))

# Set up the data
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)  # Using sine function for demonstration

# Initialize an empty line
line, = ax.plot([], [], 'b-', lw=2)

# Set axis limits
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
ax.set_xlabel('x')
ax.set_ylabel('sin(x)')
ax.set_title('Animated Sine Function (25 points at a time)')
ax.grid(True)

# Window size (number of points to show at once)
window_size = 25

def init():
    """Initialize the animation"""
    line.set_data([], [])
    return line,

def animate(i):
    """Animation function - i is the frame number"""
    # Calculate start and end indices for the current window
    start_idx = i % (len(x) - window_size + 1)
    end_idx = start_idx + window_size
    
    # Update the line data
    line.set_data(x[start_idx:end_idx], y[start_idx:end_idx])
    return line,

# Create animation
ani = FuncAnimation(fig, animate, frames=len(x)-window_size+1,
                    init_func=init, blit=True, interval=100)


plt.tight_layout()
plt.show()

This handles the starting over/looping a little better than your approach.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.