如何使用 matplotlib 修改基于 GUI 的实时绘图代码?

问题描述 投票:0回答:1

我有一个使用 PySimpleGUI 的 GUI,其中包含多个图,可以从蓝牙设备接收数据。我想实时绘制接收到的数据,理想情况下与接收到点的速度一样快。现在大约是每 20 毫秒 1 点。然而,在目前的状态下,它的速度慢得令人痛苦。在 GUI 赶上之前,硬件早已完成了测量。整个 GUI 陷入困境,甚至没有注册设备已完成其任务。


class DataPlot:
    #stuff

    def update(self, plot_func):
        self.ax.cla()
        self.ax.grid()
        plot_func()
        self.ax.set_title(self.title)
        self.ax.set_xlabel(self.x_label)
        self.ax.set_ylabel(self.y_label)
        plt.legend(loc="lower right", fontsize="5", ncol=2)
        self.figure_agg.draw()

class View:
    #stuff

    def update_demo_plots(
        self, calibration_sweeps: List[SensorSweep], test_sweeps: List[SensorSweep]
    ):
        def demo_well1_update(calibration_sweeps, test_sweeps):
            for c_num in range(8):
                cal_x = [sweep.applied_voltages[c_num] for sweep in calibration_sweeps]
                cal_y = [sweep.calculated_resistances[c_num] for sweep in calibration_sweeps]
                self.well1_plot.ax.plot(
                    cal_x,
                    cal_y,
                    "s",
                    color="blue",
                    markersize=2,
                    label="Calibration" if c_num == 0 else None,
                )

        self.well1_plot.update(
            lambda: demo_well1_update(calibration_sweeps, test_sweeps)
        )

        #other plotting

每次收到一个点,都会调用 update_demo_plots() 。这完全清除并重新绘制每个点的所有数据。

我已经确定,仅调用 self.figure_agg.draw() 并注释掉其他所有内容就足以显着减慢 GUI 的速度。我该如何改进并解决这个问题?

python matplotlib plot pysimplegui
1个回答
0
投票

您的数据速度太快,该代码无法绘制,因为它对收到的每个数据执行了很多操作。以下是解决您问题的一些解决方案:

  1. 避免每次都清除地块。它显着降低了 GUI 的速度。删除
    ax.cla()
    的使用,以防止不必要的绘图清除。
  2. 避免每次更新数据时都创建新行。使用
    Line2D
    对象。在初始化期间创建一次并在所有更新中重复使用它。
  3. 不要在每次更新时从头开始绘制所有数据点,而是使用
    set_xdata()
    set_ydata()
    直接用新数据更新现有线。这显着减少了处理时间。
  4. 使用固定大小的窗口,而不是无限期地累积数据。
  5. 主要原因之一可能是
    self.figure_agg.draw()
    ,因为它可能会阻塞主线程,导致 GUI 变慢。请使用
    self.figure_agg.draw_idle()
    来代替。

奖励:使用

set_xlim()
set_ylim()
进行动态调整的轴限制,使绘图以最新数据为中心,从而增强视觉体验。

我没有适当的设置来从蓝牙设备生成这些数据,因此我仅在 AI 的帮助下使用 Python 模拟数据。这是包含我上面提到的所有要点的最终代码。

# Importing necessary libraries
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import random
import threading
import time

# Class for managing data plotting
class DataPlot:
    def __init__(self, ax, title, x_label, y_label, window_size=50):
        # Setting up the plot with given parameters
        self.ax = ax
        self.title = title
        self.x_label = x_label
        self.y_label = y_label
        self.window_size = window_size  # Controls the number of points shown in the moving window
        self.line, = self.ax.plot([], [], 'b-', linewidth=1)  # Initialize the plot line
        self.ax.grid()
        self.ax.set_title(self.title)
        self.ax.set_xlabel(self.x_label)
        self.ax.set_ylabel(self.y_label)

        # Initialize empty lists to hold data
        self.x_data = []
        self.y_data = []

    def update(self, new_x, new_y):
        # Add new data points to the current list
        self.x_data.extend(new_x)
        self.y_data.extend(new_y)

        # Keep only the latest `window_size` number of points
        if len(self.x_data) > self.window_size:
            self.x_data = self.x_data[-self.window_size:]
            self.y_data = self.y_data[-self.window_size:]

        # Update the line data
        self.line.set_xdata(self.x_data)
        self.line.set_ydata(self.y_data)

        # Adjust the plot limits to keep the new data centered
        self.ax.set_xlim(min(self.x_data), max(self.x_data))
        self.ax.set_ylim(min(self.y_data) - 10, max(self.y_data) + 10)  # Dynamically adjust y-limits

        # Redraw the plot without blocking the GUI
        self.line.figure.canvas.draw_idle()

class View:
    def __init__(self, figure, ax):
        self.figure = figure
        self.ax = ax
        self.well1_plot = DataPlot(ax, "Moving Line Plot", "Time", "Value")

    def update_demo_plots(self, calibration_sweeps):
        # Prepare data for plotting
        cal_x = [i for i in range(len(calibration_sweeps))]  # Simulating time on the x-axis
        cal_y = [sweep['resistance'] for sweep in calibration_sweeps]  # Plotting resistance values
        self.well1_plot.update(cal_x, cal_y)

# Function to simulate incoming data
def data_generator(view):
    calibration_sweeps = []  # This list will store incoming data points
    while True:
        time.sleep(0.02)  # Simulate data arrival every 20 milliseconds
        # Generate random data points
        new_data = {'voltage': random.uniform(0, 5), 'resistance': random.uniform(10, 100)}
        calibration_sweeps.append(new_data)

        # Limiting the number of data points to keep the plot responsive
        if len(calibration_sweeps) > 50:  # Keep the last 50 points
            calibration_sweeps.pop(0)

        # Updating the plot with new data
        view.update_demo_plots(calibration_sweeps)

# Setting up the GUI window with PySimpleGUI
layout = [[sg.Canvas(key='-CANVAS-')], [sg.Button('Exit')]]
window = sg.Window('Real-time Plotting', layout, finalize=True)

fig, ax = plt.subplots()
view = View(fig, ax)
figure_agg = FigureCanvasTkAgg(fig, window['-CANVAS-'].TKCanvas)
figure_agg.draw()
figure_agg.get_tk_widget().pack(side='top', fill='both', expand=1)

thread = threading.Thread(target=data_generator, args=(view,), daemon=True)
thread.start()

while True:
    event, values = window.read(timeout=10)
    if event == sg.WIN_CLOSED or event == 'Exit':
        break

window.close()

这更新情节非常快。如果您在实施过程中有任何不清楚或出现问题,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.