如何实现多选项实时数据绘图?含秒级实时图与分钟均值图切换
解决方案
以下是修改后的代码,实现了实时秒级图和分钟均值图的切换功能,同时修复了数据累积和绘图更新的问题:
# 导入模块 import serial import pandas as pd from datetime import datetime, timedelta import tkinter as Tk import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg plt.style.use('ggplot') # 初始化串口连接 ser = serial.Serial( port='COM5', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=0.5, xonxoff=False, rtscts=False, dsrdtr=False ) # 全局数据存储:秒级数据和分钟均值数据 SData = pd.DataFrame(columns=['Time', 'hh', 'mm', 'ss', 'A', 'B', 'C']) MData = pd.DataFrame(columns=['Time', 'hh', 'mm', 'A', 'B', 'C']) # 绘图模式标记:'second' 为实时秒级图,'minute' 为分钟均值图 current_plot = 'second' # 记录上一次计算均值的分钟 last_minute = datetime.now().strftime('%H:%M') def animate(i): global SData, MData, last_minute, current_plot try: # 读取串口数据 dat1 = ser.readline() if not dat1: return # 无数据时直接返回 dat = dat1.decode('UTF-8').strip() # 校验数据长度(假设有效数据至少22字符) if len(dat) < 22: return # 获取当前时间(去除微秒) now = datetime.now().replace(microsecond=0) current_minute_str = now.strftime('%H:%M') # 解析数据 a_val = round(float(dat[1:8]), 1) b_val = round(float(dat[8:15]), 1) c_val = round(float(dat[15:22]), 1) # 将新数据添加到秒级数据框 new_second_row = pd.DataFrame({ 'Time': [now], 'hh': [now.strftime('%H')], 'mm': [now.strftime('%M')], 'ss': [now.strftime('%S')], 'A': [a_val], 'B': [b_val], 'C': [c_val] }) SData = pd.concat([SData, new_second_row], ignore_index=True) # 只保留最近30秒的秒级数据,避免数据量过大 SData = SData[SData['Time'] >= datetime.now() - timedelta(minutes=0.5)] # 每分钟计算一次均值 if last_minute != current_minute_str: if last_minute is not None: # 获取上一分钟的所有数据 last_minute_start = now - timedelta(minutes=1) minute_data = SData[ (SData['Time'] >= last_minute_start) & (SData['Time'] < now) ] if not minute_data.empty: # 计算均值 avg_a = round(minute_data['A'].mean(), 1) avg_b = round(minute_data['B'].mean(), 1) avg_c = round(minute_data['C'].mean(), 1) # 添加到分钟均值数据框 new_minute_row = pd.DataFrame({ 'Time': [last_minute_start.replace(second=0)], 'hh': [last_minute_start.strftime('%H')], 'mm': [last_minute_start.strftime('%M')], 'A': [avg_a], 'B': [avg_b], 'C': [avg_c] }) MData = pd.concat([MData, new_minute_row], ignore_index=True) # 只保留最近24小时的分钟数据 MData = MData[MData['Time'] >= datetime.now() - timedelta(hours=24)] # 更新上一分钟标记 last_minute = current_minute_str # 仅在实时秒级模式下更新绘图 if current_plot == 'second': # 清空子图 p1.clear() p2.clear() p3.clear() # 绘制三个参数的实时曲线 p1.plot(SData['Time'], SData['A'], color='blue', label='A') p2.plot(SData['Time'], SData['B'], color='green', label='B') p3.plot(SData['Time'], SData['C'], color='red', label='C') # 设置Y轴范围 p1.set_ylim([0, 10]) p2.set_ylim([0, 10]) p3.set_ylim([0, 10]) # 设置X轴范围为最近30秒 p1.set_xlim([datetime.now() - timedelta(minutes=0.5), datetime.now() + timedelta(seconds=5)]) # 添加图例 p1.legend() p2.legend() p3.legend() # 自动格式化时间轴 fig.autofmt_xdate() # 重绘画布 canvas.draw() except Exception as e: print(f"数据处理错误: {e}") return def toggle_plot(): """切换绘图模式的按钮点击事件""" global current_plot, ani if current_plot == 'second': # 切换到分钟均值图 current_plot = 'minute' Button1.config(text='实时秒级图') ani.event_source.stop() # 停止实时动画 # 清空子图 p1.clear() p2.clear() p3.clear() # 绘制分钟均值数据(最近24小时) if not MData.empty: p1.plot(MData['Time'], MData['A'], color='blue', marker='o', label='A') p2.plot(MData['Time'], MData['B'], color='green', marker='o', label='B') p3.plot(MData['Time'], MData['C'], color='red', marker='o', label='C') # 设置轴范围和标签 p1.set_ylim([0, 10]) p2.set_ylim([0, 10]) p3.set_ylim([0, 10]) p1.set_xlim([datetime.now() - timedelta(hours=24), datetime.now()]) p1.set_xlabel('时间') p1.set_ylabel('参数A') p2.set_ylabel('参数B') p3.set_ylabel('参数C') # 添加图例 p1.legend() p2.legend() p3.legend() # 自动格式化时间轴 fig.autofmt_xdate() # 重绘画布 canvas.draw() else: # 切换回实时秒级图 current_plot = 'second' Button1.config(text='分钟均值图') ani.event_source.start() # 重启实时动画 # 初始化GUI窗口 root = Tk.Tk() root.geometry('1200x900') Tk.Label(root, text="实时数据绘图系统").place(x=500, y=10) # 创建切换按钮 Button1 = Tk.Button(root, text='分钟均值图', command=toggle_plot) Button1.place(x=500, y=860) # 创建Matplotlib图形和子图 fig = plt.Figure(figsize=(12, 8), tight_layout=True) p1 = fig.add_subplot(313) p2 = fig.add_subplot(312, sharex=p1) p3 = fig.add_subplot(311, sharex=p1) # 隐藏上层子图的X轴标签 plt.setp(p2.get_xticklabels(), visible=False) plt.setp(p3.get_xticklabels(), visible=False) # 将图形嵌入Tkinter窗口 canvas = FigureCanvasTkAgg(fig, root) canvas.get_tk_widget().place(x=10, y=50) canvas.draw() # 启动实时动画 ani = FuncAnimation(fig, animate, interval=1000, blit=False) # 启动GUI主循环 Tk.mainloop() # 关闭串口连接 ser.close()
关键修改说明:
数据累积修复:
- 将
SData和MData改为全局变量,每次读取新数据后追加到数据框中 - 限制
SData仅保留最近30秒数据,MData仅保留最近24小时数据,避免内存占用过大
- 将
分钟均值计算:
- 新增
last_minute变量跟踪上一次计算均值的分钟 - 每分钟自动计算上一分钟数据的平均值并保存到
MData
- 新增
绘图切换功能:
- 新增
current_plot标记当前绘图模式 - 实现
toggle_plot函数处理按钮点击事件,切换时停止/重启动画,清空并重绘子图 - 按钮文本随模式切换自动更新
- 新增
鲁棒性提升:
- 添加异常捕获处理,避免串口数据错误导致程序崩溃
- 校验数据长度,过滤无效数据
绘图优化:
- 使用时间列作为X轴,替代原整数索引,时间显示更直观
- 添加图例和轴标签,提升可读性
- 自动格式化时间轴,避免标签重叠
内容的提问来源于stack exchange,提问作者raja
相关产品推荐
相关产品推荐

