To update a Matplotlib plot in a loop, create the plot once, then inside the loop change the data of the existing artist (line.set_ydata() for lines, scatter.set_offsets() for scatter plots) and call plt.pause() so the window redraws. For smooth, repeatable animations use FuncAnimation. This guide shows each way to update a plot in a loop in Python, including scatter plots, real-time data, Jupyter notebooks and the mistakes that make a plot slow or frozen.
Tested with Python 3.12.5, Matplotlib 3.11.2 and NumPy 2.5.3. The screenshots are the real Matplotlib windows at the end of each loop, and the timings come from the Windows Command Prompt. See also the official Matplotlib animations guide and pyplot.pause().
Update a line plot in a loop with plt.pause()
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 4 * np.pi, 200)
plt.ion() # interactive mode: plt.pause() redraws without blocking
fig, ax = plt.subplots(figsize=(7, 4))
line, = ax.plot(x, np.sin(x)) # create the line once
ax.set_ylim(-1.2, 1.2)
for step in range(60):
line.set_ydata(np.sin(x + step / 5)) # then only change its data
ax.set_title(f"Frame {step + 1} of 60")
plt.pause(0.05) # draw and let the window process events
plt.ioff()
plt.show() # keep the window open at the end
plt.ion()turns on interactive mode, so drawing does not block the loop.line.set_ydata()(orset_data(x, y)) changes the data of the line that already exists.plt.pause(interval)redraws the figure and processes window events. Without it the window freezes until the loop ends.plt.ioff()+plt.show()keeps the window open after the last frame.
Why you should not call plot() again in every loop step
Calling ax.plot() inside the loop does not replace the old line; it adds a new one each time, so the plot gets cluttered and slower:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots()
for i in range(20):
ax.plot(x, np.sin(x + i / 3)) # a NEW line every time: nothing is replaced
print("lines on the axes:", len(ax.lines))
Output:
lines on the axes: 20
plot() calls leave twenty lines.Clearing the axes with ax.clear() (or plt.cla()) before plotting fixes the clutter, but it rebuilds the whole plot, including ticks and labels, every frame. Updating the existing line skips that work:
import time
import matplotlib
matplotlib.use("Agg") # draw off screen so only the drawing is timed
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 2000)
frames = 150
fig, ax = plt.subplots()
start = time.perf_counter()
for i in range(frames):
ax.clear() # 1. clear and plot again
ax.plot(x, np.sin(x + i / 10))
fig.canvas.draw()
clear_time = time.perf_counter() - start
fig, ax = plt.subplots()
line, = ax.plot(x, np.sin(x))
start = time.perf_counter()
for i in range(frames):
line.set_ydata(np.sin(x + i / 10)) # 2. update the existing line
fig.canvas.draw()
update_time = time.perf_counter() - start
print(f"ax.clear() + plot : {clear_time:.2f} s for {frames} frames")
print(f"line.set_ydata() : {update_time:.2f} s for {frames} frames")
print(f"set_ydata is {clear_time / update_time:.1f}x faster")
Output (one run; the screenshot below is a second run, so the numbers differ):
ax.clear() + plot : 7.13 s for 150 frames
line.set_ydata() : 5.21 s for 150 frames
set_ydata is 1.4x faster
Use ax.clear() only when the plot structure really changes (different plot type, different number of lines).
Update a scatter plot in a loop
Scatter plots are PathCollection objects. Move the points with set_offsets() (an N×2 array of x, y) and change their colours with set_array() or sizes with set_sizes():
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(3)
points = rng.uniform(0, 10, size=(40, 2))
speed = rng.normal(0, 0.25, size=(40, 2))
plt.ion()
fig, ax = plt.subplots(figsize=(6, 5))
sc = ax.scatter(points[:, 0], points[:, 1], c=np.zeros(40), cmap="plasma", vmin=0, vmax=10, s=60)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
fig.colorbar(sc, label="distance from the centre")
for step in range(80):
points = (points + speed) % 10 # move every point
sc.set_offsets(points) # new x, y positions
sc.set_array(np.hypot(*(points - 5).T) * 1.4) # new colours
ax.set_title(f"Step {step + 1}")
plt.pause(0.03)
plt.ioff()
plt.show()
set_offsets() and recoloured with set_array().Set fixed axis limits (or vmin/vmax for colours) before the loop; otherwise the axes and colour scale do not follow the new data.
Real-time plot with changing axis limits
For live data, keep only the last N values in a deque and let Matplotlib recompute the limits with relim() and autoscale_view():
import matplotlib.pyplot as plt
import numpy as np
from collections import deque
rng = np.random.default_rng(7)
readings = deque(maxlen=50) # keep only the last 50 values
plt.ion()
fig, ax = plt.subplots(figsize=(7, 4))
line, = ax.plot([], [], color="tab:red")
ax.set_xlabel("Sample")
ax.set_ylabel("Temperature (°C)")
value = 21.0
for i in range(150):
value += rng.normal(0, 0.3) # a new "sensor" reading
readings.append(value)
line.set_data(range(i - len(readings) + 1, i + 1), readings)
ax.relim() # recompute the data limits ...
ax.autoscale_view() # ... and rescale the axes to them
plt.pause(0.01)
plt.ioff()
plt.show()
In a real program, replace the random value with your sensor, API or file reading.
Animate with FuncAnimation
FuncAnimation calls your update function on a timer, so you do not write the loop yourself. It is the best choice for smooth animations and for saving them as GIF or MP4:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
x = np.linspace(0, 2 * np.pi, 200)
fig, ax = plt.subplots(figsize=(7, 4))
line, = ax.plot(x, np.sin(x), color="tab:green")
ax.set_ylim(-1.2, 1.2)
def update(frame):
line.set_ydata(np.sin(x + frame / 10))
return (line,)
ani = FuncAnimation(fig, update, frames=120, interval=30, blit=True) # keep a reference in a variable!
ani.save("sine_wave.gif", writer="pillow", fps=30) # optional: save as GIF
plt.show()
FuncAnimation of a moving sine wave; the script also saves it as sine_wave.gif.Always assign the animation to a variable (ani = FuncAnimation(...)). If you do not, Python deletes it and the plot stays still. blit=True redraws only the changed artists; the update function must then return them.
Update a plot in a loop in Jupyter Notebook
In the classic inline backend each figure is a static image, so plt.pause() does nothing useful. Two options work:
from IPython.display import clear_output, display
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 200)
for i in range(30):
fig, ax = plt.subplots()
ax.plot(x, np.sin(x + i / 5))
clear_output(wait=True) # remove the previous image only when the new one is ready
display(fig)
plt.close(fig)
Or install ipympl and run %matplotlib widget; then the set_ydata() + fig.canvas.draw_idle() approach works inside the notebook.
Troubleshooting
| Problem | Fix |
|---|---|
| The window is blank or frozen until the loop ends | Call plt.pause() in the loop (not time.sleep()) and use plt.ion() |
| The window closes at the end | Finish with plt.ioff() and plt.show() |
| New data goes outside the plot | Set limits first, or call ax.relim() and ax.autoscale_view() |
| The plot gets slower over time | You are adding new lines; update the existing ones |
| FuncAnimation does not move | Keep the animation in a variable and make sure a GUI backend is used |
More Matplotlib tutorials you may find useful:
- Plot multiple lines in Python
- Create a 3D scatter plot in Matplotlib
- Set the axis range in Matplotlib
- Fix overlapping labels with tight_layout()
Frequently asked questions
How do I update a Matplotlib plot in a loop?
Create the plot once, keep the line (line, = ax.plot(x, y)), and in the loop call line.set_ydata(new_y) followed by plt.pause(0.05). Turn on interactive mode with plt.ion() first.
How do I update a scatter plot in a loop?
Keep the object returned by ax.scatter() and call sc.set_offsets(xy) with an N×2 array, then plt.pause(). Use set_array() for colours and set_sizes() for sizes.
Why does my plot not update inside a loop?
Usually because the loop never gives Matplotlib time to draw. Use plt.pause() instead of time.sleep(), and turn on interactive mode with plt.ion().
What is the difference between plt.pause and FuncAnimation?
plt.pause() lets you write your own loop and is simple for live data. FuncAnimation runs the updates on a timer, supports blitting and can save the animation to a GIF or video.
How do I refresh a plot in Jupyter Notebook?
Use clear_output(wait=True) and display(fig) in the loop, or switch to the interactive %matplotlib widget backend (ipympl).
How do I update the axis limits when the data changes?
Call ax.relim() and then ax.autoscale_view() after updating the data, or set new limits with ax.set_xlim() and ax.set_ylim().
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn