
本文详解如何修正 matplotlib 实时绘图中“每次新数据都弹出新窗口”的常见错误,核心是将 `plt.figure()` 和绘图对象初始化移出循环,并结合 `funcanimation` 正确复用同一图表进行动态更新。
在使用 Matplotlib 实现传感器数据(如真空压力)的实时可视化时,一个典型误区是:在数据采集循环内反复调用 plt.figure() 或 pyplot.plot_date()。这会导致每轮迭代都新建一个 Figure 窗口,不仅资源浪费,更使动画失效——因为 FuncAnimation 无法接管多个独立 Figure。
你的原始代码中,关键问题出现在 while 循环内部:
while (cmd != "shutdown"):
# ... 数据接收与解析 ...
x_data, y_data = [], [] # ❌ 每次清空数据 → 历史丢失
figure = pyplot.figure() # ❌ 每次新建 Figure → 多窗口
line, = pyplot.plot_date(x_data, y_data, '-') # ❌ 每次新建 Line 对象
def update(frame): # ❌ 定义在循环内,作用域混乱且低效
x_data.append(...)
y_data.append(...)
line.set_data(...) # ✅ 正确更新,但前提必须复用同一 line 对象
# ...
anim = animation.FuncAnimation(figure, update, ...) # ❌ 每次重启动画✅ 正确做法:一次性初始化图形、坐标轴和绘图元素,仅在 update 函数中更新数据。以下是优化后的完整结构(已适配你的 TCP 压力读取逻辑):
import socket
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from datetime import datetime
# --- 1. 初始化通信 ---
target_host, target_port = "10.1.2.121", 50
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((target_host, target_port))
print(f"Connected to {target_host}:{target_port}")
# --- 2. 初始化绘图(仅执行一次!)---
plt.yscale('symlog')
fig, ax = plt.subplots(figsize=(10, 6))
line, = ax.plot_date([], [], '-', label='Vacuum Pressure', linewidth=2)
ax.grid(True)
ax.legend()
ax.set_xlabel('Time')
ax.set_ylabel('Pressure (Torr)')
plt.tight_layout()
# --- 3. 全局数据容器(避免闭包/作用域问题)---
x_data, y_data = [], []
response4 = 0.0 # 初始值,后续由循环更新
# --- 4. 定义动画更新函数 ---
def update(frame):
global response4, x_data, y_data
try:
# 发送命令并解析响应
client.send(b"?VP\r\0")
response = client.recv(1024).decode('ascii').strip()
# 示例响应: "VP:3.58E-7" → 提取数值部分
if response.startswith("VP:"):
val_str = response[3:].replace("E", "e")
response4 = float(val_str)
# 更新数据
x_data.append(datetime.now())
y_data.append(response4)
# 限制显示点数(可选,防内存溢出)
if len(x_data) > 200:
x_data = x_data[-200:]
y_data = y_data[-200:]
# 更新绘图
line.set_data(x_data, y_data)
ax.relim() # 重算坐标轴范围
ax.autoscale_view() # 自动缩放视图
ax.ticklabel_format(axis='y', style='sci', scilimits=(0,0))
except Exception as e:
print(f"Data error: {e}")
return line,
# --- 5. 启动动画(仅一次)---
anim = FuncAnimation(
fig, update,
interval=200, # 每200ms刷新一次
cache_frame_data=False,
blit=False # 因使用 autoscale_view,设为 False 更稳妥
)
plt.show()
# --- 清理(退出时关闭 socket)---
client.close()⚠️ 关键注意事项:
- plt.figure() / plt.subplots() 必须在循环外,否则每次迭代都会创建新窗口;
- 数据列表 x_data, y_data 也需定义在循环外,否则历史数据被清空;
- FuncAnimation 的 update 函数应无副作用地更新已有对象(如 line.set_data()),而非重建;
- 使用 ax.relim() + ax.autoscale_view() 确保坐标轴随新数据自动适应;
- 生产环境建议添加异常处理与连接超时机制,并考虑用 threading 分离通信与绘图线程,避免阻塞。
通过以上重构,你的真空压力曲线将稳定在一个窗口中连续滚动更新,真正实现专业级实时监控效果。










