
本文讲解如何修正 matplotlib 实时绘图中“每次新数据都弹出新窗口”的常见错误,核心是将 `plt.figure()` 和绘图对象初始化移出循环,并结合 `funcanimation` 正确复用同一图表,实现高效、流畅的压力数据动态可视化。
在使用 Matplotlib 进行实时数据可视化(如真空腔压力监测)时,一个典型误区是:在数据采集循环内反复调用 plt.figure() 或 pyplot.plot()——这会导致每轮迭代都新建一个 Figure 窗口,最终堆积大量独立图表,不仅卡顿,还完全违背“动态更新”初衷。
你的原始代码中,关键问题正出现在 while 循环内部:
while (cmd != "shutdown"):
# ... 数据接收与解析 ...
x_data, y_data = [], [] # ❌ 每次清空数据 → 图表无法累积
figure = pyplot.figure() # ❌ 每次新建 Figure → 弹出新窗口!
line, = pyplot.plot_date(x_data, y_data, '-')
def update(frame): # ❌ 定义在循环内,且未正确绑定数据生命周期
x_data.append(datetime.now())
y_data.append(response4)
# ...✅ 正确做法是:只初始化一次 Figure 和 Line 对象,在循环外完成;所有更新逻辑交由 FuncAnimation 统一驱动。以下是优化后的完整可运行示例(已精简冗余导入,修复逻辑漏洞):
import socket
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from datetime import datetime
# === 配置区 ===
target_host = "10.1.2.121"
target_port = 50
cmd = "?VP\r" # 注意:命令末尾已含 \r,无需额外拼接
# === TCP 连接 ===
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((target_host, target_port))
print("✅ Socket connected to", target_host)
except socket.error as e:
print("❌ Connection failed:", e)
exit(1)
# === Matplotlib 初始化(关键!必须在循环外)===
plt.yscale('symlog')
plt.grid(True)
plt.title("Real-time Vacuum Pressure")
plt.xlabel("Time")
plt.ylabel("Pressure (Torr)")
# 创建 figure 和 line 对象(仅一次)
fig, ax = plt.subplots()
line, = ax.plot_date([], [], '-', label="Pressure")
ax.legend()
ax.ticklabel_format(axis='y', style='sci', scilimits=(0,0))
# 数据容器(在闭包或全局作用域中维护)
x_data, y_data = [], []
# === 动画更新函数 ===
def update(frame):
try:
# 发送命令并解析响应
client.send(cmd.encode('ascii'))
response = client.recv(1024).decode('ascii').strip()
# 示例响应: "VP:3.58E-7" → 提取数值部分
if response.startswith("VP:"):
val_str = response[3:].replace("E", "e")
pressure = float(val_str)
# 更新数据
x_data.append(datetime.now())
y_data.append(pressure)
# 限制显示点数(可选,防内存溢出)
if len(x_data) > 200:
x_data.pop(0)
y_data.pop(0)
# 刷新绘图
line.set_data(x_data, y_data)
ax.relim() # 重设坐标轴范围
ax.autoscale_view() # 自动缩放
return line,
except Exception as e:
print("⚠️ Data read error:", e)
return line,
# === 启动动画(interval 单位:毫秒)===
ani = FuncAnimation(fig, update, interval=500, blit=False, cache_frame_data=False)
# 显示图形(阻塞式,保持窗口活跃)
plt.show()
# === 清理连接(退出时执行)===
client.close()
print("? Connection closed.")? 关键改进说明:
- ✅ Figure & Line 初始化移出循环:fig, ax = plt.subplots() 和 line, = ax.plot_date(...) 仅执行一次,确保所有更新复用同一画布;
- ✅ 数据容器全局化:x_data 和 y_data 在 update() 外定义,避免每次清空,支持时间序列累积;
- ✅ FuncAnimation 替代手动循环绘图:由 Matplotlib 内部定时器驱动更新,线程安全且性能更优;
- ✅ 异常防护增强:网络异常不中断主流程,仅打印警告;
- ✅ 内存友好设计:通过 pop(0) 限制历史数据长度,防止长时间运行后内存暴涨;
- ✅ 坐标轴自动适配:relim() + autoscale_view() 确保新数据始终可见。
⚠️ 注意事项:
- 若需更高频率更新(如
- TCP 命令需严格遵循设备协议(注意 \r/\n、超时设置、缓冲区清空),建议添加 client.settimeout(2);
- 生产环境应使用 threading 或 asyncio 解耦通信与绘图,避免 recv() 阻塞导致动画卡顿。
通过以上重构,你的真空压力曲线将稳定运行在一个窗口中,平滑滚动、实时响应——这才是工业级数据监控应有的表现。










