HOWTO · Matplotlib
如何使用 Matplotlib 实时绘制数据
使用 FuncAnimation()、手动重绘画布或不断更新的散点图 artist,在 Matplotlib 窗口中绘制变化的数据。
本页内容
要在 Matplotlib 窗口中绘制不断变化的数据,请更新图中 artist 保存的数据,并让支持 GUI 的后端刷新显示。数据按帧到达时可使用 FuncAnimation();需要在循环中直接控制更新时,可使用 canvas.draw() 和 canvas.flush_events();每次更新都添加一个点时,可使用散点 artist。这些示例在本地生成数据;改编时,可将更新逻辑替换为自己的数据来源。它们不会连接传感器或网络数据流。
使用 FuncAnimation() 为图形添加动画
matplotlib.animation.FuncAnimation 会针对每一帧调用更新函数,并安排重绘。回调函数会收到当前帧的值,更新现有曲线,并返回发生变化的 artist。frames=10 会提供从 0 到 9 的值;interval=50 则请求每帧之间间隔 50 毫秒。实际时间间隔取决于 GUI 事件循环。
只要窗口仍然打开,就要保留对动画对象的引用。示例会以交互方式显示动画。若要保存动画,请在调用 plt.show() 之前执行 ani.save("animation.gif", writer="pillow", fps=10),并安装 Pillow 这类兼容的动画写入器。保存是单独的渲染步骤,不会创建实时数据流。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
figure, ax = plt.subplots()
line, = ax.plot([], [])
ax.set(xlim=(0, 2 * np.pi), ylim=(-1, 1), xlabel="x", ylabel="sin(x)")
def update(frame):
x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x - frame / 5)
line.set_data(x, y)
return (line,)
ani = FuncAnimation(figure, update, frames=40, interval=50, blit=True)
plt.show()
如果动画需要单独的初始状态,请使用 init_func;如果要向回调函数传递额外的固定参数,请使用 fargs。如果窗口打开后动画没有更新,请检查所选 Matplotlib 后端是否支持 GUI 事件循环,并在窗口显示期间保留 ani 的引用。
使用 canvas.draw() 和 canvas.flush_events() 更新曲线
如果要由循环控制每次更新,可先创建一条曲线,再使用 set_ydata() 修改其数据。随后,canvas.draw() 会绘制更新后的图形,而 canvas.flush_events() 会让 GUI 处理待处理的事件。这些是 Matplotlib canvas 的方法,不会清除图形。plt.ion() 会启用交互模式,短暂暂停则能让 GUI 有时间响应。
import time
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 200)
figure, ax = plt.subplots()
line, = ax.plot(x, np.sin(x))
ax.set(xlim=(0, 2 * np.pi), ylim=(-1, 1), xlabel="x", ylabel="sin(x)")
plt.ion()
for frame in range(40):
line.set_ydata(np.sin(x - frame / 5))
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(0.05)
plt.ioff()
plt.show()
如果循环还需要协调其他工作,这种方式会很有用。每次更新都应尽量简短,以便事件循环处理输入和重绘请求。在非交互式后端中,这些调用可以绘制图形,但无法提供响应及时的窗口;若要进行交互式更新,请选择支持 GUI 的后端。
创建实时散点图
对于连续到来的点,可以先创建一个散点 artist,再随着新坐标到达替换它的偏移量。重复使用同一个 artist,可以避免每次循环都累积一个新的绘图对象。plt.pause() 会在帧之间更新 GUI 事件循环,下面设置的坐标轴范围则能让所有生成的点保持可见。
import numpy as np
import matplotlib.pyplot as plt
figure, ax = plt.subplots()
points = ax.scatter([], [])
ax.set(xlim=(0, 4), ylim=(-1.1, 1.1), xlabel="x", ylabel="sin(x)")
x_values = []
y_values = []
for frame in range(100):
x = frame * 0.04
x_values.append(x)
y_values.append(np.sin(x))
points.set_offsets(np.column_stack((x_values, y_values)))
plt.pause(0.05)
plt.show()
如果只关心最新的点,只需保留一组坐标,无需保存完整历史。如果要让所有历史点持续可见,请随着数据流增长扩大坐标轴范围。对于高频数据,可以降低更新频率,或只保留最近一段数据,以免绘图成为性能瓶颈。