如何加速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
相关产品推荐
相关产品推荐

