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

如何加速PyQtGraph绘制百万级点数的图形?

优化PyQtGraph百万级点绘制速度的方案

一、先解决PyQtGraph本身的性能瓶颈

你的代码存在几个直接拖慢速度的问题,修正后可大幅提升性能:

  • 用numpy数组替代Python列表:PyQtGraph对numpy数组的支持远优于列表,无需将数据转成list。将channel_data = data.iloc[:, i].values.tolist()改为channel_data = data.iloc[:, i].values;x轴直接用np.arange(len(channel_data))生成numpy数组,替代list(range(len(channel_data)))。
  • 关闭不必要的绘图元素:当前代码设置了symbol='o'和symbolSize=5,百万级点绘制符号会极大消耗资源,建议直接去掉符号设置,仅保留线条渲染。
  • 启用OpenGL加速:给PlotWidget添加enableOpenGL=True参数,利用GPU加速渲染大量数据:
    self.graphWidget = PlotWidget(enableOpenGL=True)
    
  • 数据处理移至子线程:主线程处理百万级数据会导致UI卡顿,将数据读取、预处理逻辑放到子线程,处理完成后通过PyQt信号传递给主线程绘制。

二、用Numba优化数据预处理逻辑

如果需要对数据做自定义计算(如滤波、数值转换等),可使用Numba将计算函数编译为机器码,大幅提升运算速度。

1. 编写Numba加速的预处理函数

示例(以数据平滑为例):

from numba import jit, float64

@jit(float64[:](float64[:]), nopython=True)
def smooth_data(data):
    # 移动平均平滑逻辑
    window_size = 5
    result = np.zeros_like(data)
    for i in range(len(data)):
        start = max(0, i - window_size//2)
        end = min(len(data), i + window_size//2 + 1)
        result[i] = np.mean(data[start:end])
    return result

2. 在子线程中调用Numba函数

将数据处理逻辑封装到子线程,避免阻塞UI主线程。

三、修改后的完整代码

结合所有优化点,最终代码如下:

import os
import sys
import numpy as np
from functools import partial
from numba import jit, float64

import pandas as pd

try:
    from pyqtgraph import PlotDataItem, PlotWidget, mkPen
    from PyQt5 import QtWidgets, QtCore
    from PyQt5.QtWidgets import QCheckBox, QDialog, QVBoxLayout, QHBoxLayout, QPushButton

    qt_imported = True
except ModuleNotFoundError:
    qt_imported = False

    class QDialog:
        pass

    class QCheckBox:
        pass

    class PlotDataItem:
        pass

if not qt_imported:
    def pyqtSignal(s):
        def fake_decorator(fn):
            return fn

        return fake_decorator

    def pyqtSlot(*args):
        def fake_decorator(fn):
            return fn

        return fake_decorator

from dataclasses import dataclass, field

@dataclass
class Points:
    x: np.ndarray = field(default_factory=lambda: np.array([]))
    y: np.ndarray = field(default_factory=lambda: np.array([]))

@dataclass
class Figure:
    check_box: QCheckBox
    line: PlotDataItem
    data: Points = Points()


COLORS = ["orange", "green", "blue", "red"]

# Numba加速的数据预处理函数
@jit(float64[:](float64[:]), nopython=True)
def preprocess_data(data):
    # 替换为你的自定义数据处理逻辑
    return data * 1.0


class DataLoaderThread(QtCore.QThread):
    data_processed = QtCore.pyqtSignal(str, np.ndarray, np.ndarray)

    def __init__(self, path, parent=None):
        super().__init__(parent)
        self.path = path

    def run(self):
        _, file_extension = os.path.splitext(self.path)
        data = None
        if file_extension == '.csv':
            data = pd.read_csv(filepath_or_buffer=self.path, sep=';', header=None).values
        elif file_extension == '.xlsx':
            data = pd.read_excel(io=self.path).values

        channels = data.shape[1]
        for i in range(channels):
            key = f'channel_{i + 1}'
            channel_data = data[:, i].astype(np.float64)
            processed_data = preprocess_data(channel_data)
            x_data = np.arange(len(processed_data), dtype=np.float64)
            self.data_processed.emit(key, x_data, processed_data)


class UfaIkGraphicsWidget(QDialog):
    def __init__(self, caption, path):
        super().__init__()
        self.caption = caption

        self.window_width = 900
        self.window_height = 700
        self.resize(self.window_width, self.window_height)

        # 启用OpenGL加速
        self.graphWidget = PlotWidget(enableOpenGL=True)
        self.graphWidget.setBackground('w')
        styles = {"color": "#f00", "font-size": "20px"}
        self.graphWidget.setLabel("left", "y", **styles)
        self.graphWidget.setLabel("bottom", "x, c", **styles)
        self.graphWidget.addLegend()
        self.graphWidget.showGrid(x=True, y=True)
        self.setWindowTitle(caption)

        self.figures = {}

        layout_v = QVBoxLayout()
        color_iterator = 0

        # 初始化通道控件
        _, file_extension = os.path.splitext(path)
        temp_data = pd.read_csv(path, sep=';', header=None, nrows=1) if file_extension == '.csv' else pd.read_excel(path, nrows=1)
        channels = temp_data.shape[1]

        for i in range(channels):
            key = f'channel_{i + 1}'
            color = COLORS[color_iterator % len(COLORS)]

            current_button = QCheckBox(f"{key}")
            current_button.setChecked(True)
            current_style = f"QCheckBox::indicator:checked {{background-color: {color};}}"
            current_button.setStyleSheet(current_style)
            current_button.clicked.connect(partial(self.press_check_box, key))

            pen = mkPen(color=color)
            line = self.graphWidget.plot([], [], pen=pen, skipFiniteCheck=True)
            line.setClipToView(True)

            self.figures[key] = Figure(check_box=current_button, line=line, data=Points())

            color_iterator += 1
            layout_v.addWidget(current_button)

        self.graphWidget.setXRange(0, 10)

        false_all = QPushButton('Снять все')
        false_all.clicked.connect(self.change_all_check_boxes_false)
        true_all = QPushButton('Поставить все')
        true_all.clicked.connect(self.change_all_check_boxes_true)
        layout_v.addWidget(true_all)
        layout_v.addWidget(false_all)

        layout_h = QHBoxLayout()
        layout_h.addWidget(self.graphWidget)
        layout_h.addLayout(layout_v)
        self.setLayout(layout_h)

        # 启动数据加载线程
        self.data_thread = DataLoaderThread(path)
        self.data_thread.data_processed.connect(self.update_plot_data)
        self.data_thread.start()

    def update_plot_data(self, key, x_data, y_data):
        self.figures[key].data.x = x_data
        self.figures[key].data.y = y_data
        self.figures[key].line.setData(x_data, y_data)

    def change_all_check_boxes_true(self):
        self.change_all_check_boxes(True)

    def change_all_check_boxes_false(self):
        self.change_all_check_boxes(False)

    def change_all_check_boxes(self, is_check: bool):
        for name in self.figures.keys():
            state = self.figures[name].check_box.isChecked()
            if state != is_check:
                self.figures[name].check_box.click()

    def press_check_box(self, name):
        if self.figures[name].check_box.isChecked():
            self.figures[name].line.show()
        else:
            self.figures[name].line.hide()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = UfaIkGraphicsWidget(caption='Тест', path='large_test.csv')
    w.show()
    sys.exit(app.exec_())

四、关键优化效果说明

  • OpenGL加速:利用GPU渲染大量数据,渲染速度提升数倍。
  • numpy数组:减少数据转换开销,PyQtGraph内部处理效率更高。
  • 子线程处理:避免UI卡顿,保证交互流畅。
  • Numba加速:自定义数据计算速度提升几十倍,适合复杂预处理逻辑。

通过以上优化,百万级点的绘制时间可从30秒缩短至几秒内。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 02:50:27