You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现多选项实时数据绘图?含秒级实时图与分钟均值图切换

解决方案

以下是修改后的代码,实现了实时秒级图和分钟均值图的切换功能,同时修复了数据累积和绘图更新的问题:

# 导入模块
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()

关键修改说明:

  1. 数据累积修复:

    • 将SData和MData改为全局变量,每次读取新数据后追加到数据框中
    • 限制SData仅保留最近30秒数据,MData仅保留最近24小时数据,避免内存占用过大
  2. 分钟均值计算:

    • 新增last_minute变量跟踪上一次计算均值的分钟
    • 每分钟自动计算上一分钟数据的平均值并保存到MData
  3. 绘图切换功能:

    • 新增current_plot标记当前绘图模式
    • 实现toggle_plot函数处理按钮点击事件,切换时停止/重启动画,清空并重绘子图
    • 按钮文本随模式切换自动更新
  4. 鲁棒性提升:

    • 添加异常捕获处理,避免串口数据错误导致程序崩溃
    • 校验数据长度,过滤无效数据
  5. 绘图优化:

    • 使用时间列作为X轴,替代原整数索引,时间显示更直观
    • 添加图例和轴标签,提升可读性
    • 自动格式化时间轴,避免标签重叠

内容的提问来源于stack exchange,提问作者raja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:05:25