PyQtGraph作为Python生态中专注于科学数据可视化的图形库,其核心优势在于处理大规模数据集时的实时渲染能力。与传统matplotlib相比,PyQtGraph底层基于Qt的GraphicsView框架,通过OpenGL加速实现了毫秒级的数据刷新速率。PlotWidget作为其最常用的可视化组件,实际上是一个高度封装的QGraphicsView子类,内部整合了坐标轴系统、图例管理、交互控制等完整功能模块。
在实际工程应用中,PlotWidget特别适合以下场景:
python复制import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
app = QtGui.QApplication([])
win = pg.GraphicsLayoutWidget(show=True)
plot = win.addPlot(title="Basic PlotWidget Example")
plot.plot([1,2,3,4,5], [1,3,2,4,3], pen='r')
QtGui.QApplication.instance().exec_()
这段基础代码揭示了PlotWidget的核心工作流程:创建Qt应用环境→构建图形布局容器→添加PlotWidget实例→绘制数据曲线→启动事件循环。值得注意的是,pyqtgraph默认采用链式API设计,大多数方法都会返回对象本身以支持连续调用。
PlotWidget的内部架构遵循Qt的Scene-View模式,其元素层级可分解为:
python复制# 获取PlotWidget内部组件示例
plot = pg.PlotWidget()
x_axis = plot.getAxis('bottom') # 获取底部坐标轴对象
plot_area = plot.getPlotItem() # 获取核心绘图区域
PlotWidget提供灵活的坐标轴配置能力,支持:
典型的时间序列坐标轴配置示例:
python复制import numpy as np
import pyqtgraph as pg
from datetime import datetime, timedelta
dates = [datetime.now() + timedelta(hours=i) for i in range(24)]
values = np.random.normal(size=24)
plot = pg.PlotWidget()
plot.plot(x=[x.timestamp() for x in dates], y=values)
axis = plot.getAxis('bottom')
axis.setTickFormatter(pg.DateAxisItem(orientation='bottom'))
关键技巧:处理高频数据时,建议关闭自动范围调整并手动设置刷新区间:
python复制plot.disableAutoRange() plot.setXRange(start, end, padding=0)
当处理超过百万级数据点时,常规绘图方法会导致性能急剧下降。PyQtGraph提供了几种优化方案:
python复制plot.setDownsampling(auto=True, mode='peak')
plot.setClipToView(True)
python复制pg.setConfigOptions(useOpenGL=True)
curve = plot.plot(pen='y', useOpenGL=True)
python复制class StreamingPlot:
def __init__(self, plot):
self.plot = plot
self.chunk_size = 100000
self.data = []
def append_data(self, new_data):
self.data.extend(new_data)
if len(self.data) > 2*self.chunk_size:
self.data = self.data[-self.chunk_size:]
self.plot.setData(self.data)
PlotWidget支持通过addItem()方法叠加多种图形元素:
python复制# 创建基础曲线
curve = plot.plot([1,2,3], [1,2,1], name="sin")
# 添加误差线
err = pg.ErrorBarItem(x=[1,2,3], y=[1,2,1], height=0.2)
plot.addItem(err)
# 叠加散点图
scatter = pg.ScatterPlotItem(x=[1,2,3], y=[1.2,1.8,1.1], size=10)
plot.addItem(scatter)
对于金融数据可视化,可以组合K线图与均线:
python复制# 创建K线数据项
candlestick = pg.CandlestickItem(data)
plot.addItem(candlestick)
# 添加移动平均线
ma5 = pg.PlotDataItem(calculate_ma(5), pen='b')
plot.addItem(ma5)
专业级数据可视化通常需要精确坐标读取功能:
python复制class Crosshair:
def __init__(self, plot):
self.plot = plot
self.vline = pg.InfiniteLine(angle=90, movable=False)
self.hline = pg.InfiniteLine(angle=0, movable=False)
self.label = pg.TextItem(anchor=(1,1))
plot.addItem(self.vline)
plot.addItem(self.hline)
plot.addItem(self.label)
plot.scene().sigMouseMoved.connect(self.mouse_moved)
def mouse_moved(self, evt):
pos = evt
if self.plot.sceneBoundingRect().contains(pos):
mouse_point = self.plot.vb.mapSceneToView(pos)
self.vline.setPos(mouse_point.x())
self.hline.setPos(mouse_point.y())
self.label.setText(f"x={mouse_point.x():.2f}, y={mouse_point.y():.2f}")
self.label.setPos(mouse_point.x(), mouse_point.y())
实现矩形选择区域功能:
python复制roi = pg.RectROI([0,0], [10,10], pen='r')
plot.addItem(roi)
def export_selected():
view_range = roi.getArraySlice(data, plot.getViewBox())
selected_data = data[view_range[0]]
np.savetxt('export.csv', selected_data, delimiter=',')
btn = QtGui.QPushButton('Export Selection')
btn.clicked.connect(export_selected)
使用timeit模块评估绘图性能:
python复制import timeit
def test_performance():
data = np.random.normal(size=(1000000,))
plot = pg.PlotWidget()
plot.plot(data)
t = timeit.timeit(test_performance, number=10)
print(f"Average rendering time: {t/10:.3f} seconds")
python复制try:
large_data = np.zeros((10000000,))
plot.plot(large_data)
except MemoryError:
plot.setDownsampling(auto=True)
plot.plot(large_data[::10])
python复制from PyQt5.QtCore import QTimer
class ThreadSafeUpdater:
def __init__(self, plot):
self.plot = plot
self.data_queue = []
self.timer = QTimer()
self.timer.timeout.connect(self.update_plot)
self.timer.start(50) # 20Hz刷新
def add_data(self, new_data):
self.data_queue.append(new_data)
def update_plot(self):
if self.data_queue:
self.plot.setData(self.data_queue.pop(0))
创建符合出版要求的学术图表样式:
python复制def setup_scientific_style(plot):
# 坐标轴样式
plot.getAxis('left').setPen('k', width=2)
plot.getAxis('bottom').setPen('k', width=2)
# 网格线配置
plot.showGrid(x=True, y=True, alpha=0.3)
# 背景色设置
plot.setBackground('w')
# 标题字体
title = plot.titleLabel
title.setColor('k')
title.setFont(QtGui.QFont('Arial', 12))
实现运行时主题切换功能:
python复制themes = {
'dark': {'bg': 'k', 'text': 'w', 'grid': (150,150,150)},
'light': {'bg': 'w', 'text': 'k', 'grid': (200,200,200)}
}
def apply_theme(plot, theme_name):
theme = themes[theme_name]
plot.setBackground(theme['bg'])
for axis in ['left', 'bottom', 'right', 'top']:
ax = plot.getAxis(axis)
if ax:
ax.setTextPen(theme['text'])
ax.setPen(theme['text'])
plot.showGrid(x=True, y=True, alpha=0.5, color=theme['grid'])
创建带箭头的标注项示例:
python复制class ArrowAnnotation(pg.GraphicsObject):
def __init__(self, x1, y1, x2, y2, text=""):
super().__init__()
self.pos1 = (x1, y1)
self.pos2 = (x2, y2)
self.text = text
self.generatePicture()
def generatePicture(self):
self.picture = QtGui.QPicture()
painter = QtGui.QPainter(self.picture)
# 绘制箭头线
painter.setPen(pg.mkPen('r', width=2))
painter.drawLine(QtCore.QPointF(*self.pos1), QtCore.QPointF(*self.pos2))
# 绘制箭头头部
angle = np.arctan2(self.pos2[1]-self.pos1[1], self.pos2[0]-self.pos1[0])
arrow_size = 10
p1 = QtCore.QPointF(
self.pos2[0] - arrow_size * np.cos(angle - np.pi/4),
self.pos2[1] - arrow_size * np.sin(angle - np.pi/4))
p2 = QtCore.QPointF(
self.pos2[0] - arrow_size * np.cos(angle + np.pi/4),
self.pos2[1] - arrow_size * np.sin(angle + np.pi/4))
painter.drawLine(QtCore.QPointF(*self.pos2), p1)
painter.drawLine(QtCore.QPointF(*self.pos2), p2)
# 添加文本
if self.text:
text_pos = ((self.pos1[0]+self.pos2[0])/2, (self.pos1[1]+self.pos2[1])/2)
text = pg.TextItem(self.text, anchor=(0.5,0.5))
text.setPos(*text_pos)
text.draw(painter)
painter.end()
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QtCore.QRectF(self.picture.boundingRect())
实现PyQtGraph与Matplotlib的协同工作:
python复制def matplotlib_to_pyqtgraph(fig):
"""将Matplotlib图形转换为PyQtGraph对象"""
# 将Matplotlib图形渲染到内存图像
fig.canvas.draw()
img = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
img = img.reshape(fig.canvas.get_width_height()[::-1] + (3,))
# 创建PyQtGraph图像项
pg_img = pg.ImageItem(img)
pg_img.setRect(0, 0, fig.get_figwidth(), fig.get_figheight())
return pg_img
# 使用示例
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1,2,3], [1,2,1])
pg_plot = pg.PlotWidget()
pg_plot.addItem(matplotlib_to_pyqtgraph(fig))