第一次接触pyqtgraph时,最让我困惑的就是那一堆相似的绘图组件——PlotWidget、PlotItem、GraphicsLayout,它们看起来都能画图,但到底该用哪个?经过多个数据可视化项目的实战,我终于摸清了它们的门道。简单来说,这就像装修房子:PlotWidget是精装公寓,开箱即用;PlotItem是毛坯房,需要自己装修;GraphicsLayout则是整层楼,可以自由隔间。
pyqtgraph的绘图体系实际上分为三个层级。最底层是PlotDataItem这样的数据载体,相当于家具;中间层是PlotItem这样的容器,相当于房间;最上层是PlotWidget这样的完整组件,相当于带家具的样板间。这种分层设计给了开发者极大的灵活性——你可以直接使用现成的PlotWidget快速出图,也可以通过组合PlotItem和GraphicsLayout构建复杂的仪表盘。
我常用的选择策略是:快速原型用PlotWidget,需要精细控制用PlotItem,多图排版用GraphicsLayout。比如最近做传感器数据监控时,单个传感器的实时曲线用PlotWidget,需要添加参考线的用PlotItem,而四个传感器的对比面板则用GraphicsLayout实现。这种组合拳让开发效率提升了至少三倍。
PlotWidget绝对是pyqtgraph里的"快枪手",三行代码就能画出专业级图表。记得我第一次用的时候,简直不敢相信这么简单:
python复制import pyqtgraph as pg
win = pg.PlotWidget()
win.plot([1,4,2,3,5], pen='r')
红色折线图瞬间呈现,连Qt的主循环都不用写——因为pg.plot()会自动创建QApplication。这种设计特别适合Jupyter Notebook里的快速可视化,我经常用它来检查算法中间结果。
但实际项目中更常见的用法是嵌入PyQt界面。这里有个小技巧:在QMainWindow里直接setCentralWidget,在QDialog里则要配合布局管理器。上周帮同事调试时,就发现他忘了加布局导致PlotWidget不显示:
python复制# 错误示例 - Widget不会显示
from PyQt5.QtWidgets import QDialog, QVBoxLayout
dialog = QDialog()
plot = pg.PlotWidget()
dialog.show()
# 正确写法
layout = QVBoxLayout()
layout.addWidget(plot)
dialog.setLayout(layout)
虽然PlotWidget开箱即用,但它的定制能力超乎想象。通过background参数可以设置画布颜色,我习惯用'#F0F0F0'这种浅灰色,比纯白色更护眼。title参数支持HTML标签,比如:
python复制plot.setTitle('<span style="color: red; font-size: 18pt">CPU温度监控</span>')
最实用的是axisItems参数,可以完全自定义坐标轴。去年做光谱分析时,我需要把X轴换成对数坐标,只需要:
python复制plot = pg.PlotWidget(axisItems={'bottom': pg.AxisItem(scale='log')})
PlotWidget还内置了丰富的交互功能:鼠标滚轮缩放、右键拖动平移、中键呼出上下文菜单。通过pyqtgraph.setConfigOptions()可以全局配置这些行为,比如关闭抗锯齿提升性能:
python复制pg.setConfigOptions(antialias=False) # 对高频更新图表特别有用
当PlotWidget无法满足需求时,就该PlotItem登场了。它就像绘图领域的乐高积木,可以自由组合各种元素。我最近做的ECG心电图项目就充分体现了它的价值——需要在同一个坐标系显示原始信号、滤波后信号和R峰标记。
创建PlotItem的标准流程是:
python复制view = pg.GraphicsView()
plot_item = pg.PlotItem()
view.setCentralItem(plot_item)
# 添加曲线
curve1 = plot_item.plot(ecg_raw, pen='b')
curve2 = plot_item.plot(ecg_filtered, pen='g')
# 添加散点
scatter = pg.ScatterPlotItem(r_peaks, symbol='x', size=10)
plot_item.addItem(scatter)
这种解耦设计的好处是,我们可以单独控制每个元素。比如通过plot_item.vb.setRange()设置可视范围,通过plot_item.getAxis('left').setLabel()设置轴标签。有个容易踩的坑是:直接修改PlotItem的属性不会立即生效,需要调用update()或autoRange()。
PlotItem最强大的功能在于它的多视图支持。通过setXLink和setYLink方法,可以实现多个PlotItem的联动。上周做的多角度摄像机标定项目就用了这个特性:
python复制plot1 = pg.PlotItem()
plot2 = pg.PlotItem()
plot2.setXLink(plot1) # X轴同步缩放
layout = pg.GraphicsLayout()
layout.addItem(plot1, row=0, col=0)
layout.addItem(plot2, row=1, col=0)
另一个杀手级功能是RegionOfInterest(ROI)。在音频分析工具中,我通过LinearRegionItem实现了频段选择:
python复制region = pg.LinearRegionItem()
plot_item.addItem(region)
region.sigRegionChanged.connect(update_spectrum) # 实时回调
当项目需要仪表盘式的多图布局时,GraphicsLayout就是终极武器。它比Matplotlib的subplot更灵活,每个子图都是独立的PlotItem。我的环境监测系统就用它同时展示温湿度、PM2.5和CO2数据:
python复制layout = pg.GraphicsLayout()
win = pg.GraphicsLayoutWidget()
win.setCentralItem(layout)
# 温度图
temp_plot = layout.addPlot(row=0, col=0)
temp_plot.plot(temperature, pen='r')
# 湿度图
humidity_plot = layout.addPlot(row=0, col=1)
humidity_plot.plot(humidity, pen='b')
# 共享X轴
humidity_plot.setXLink(temp_plot)
这里有个实用技巧:通过rowSpan和colSpan参数可以实现合并单元格。比如让下面的PM2.5图表占据整行:
python复制pm_plot = layout.addPlot(row=1, col=0, colspan=2)
GraphicsLayout不仅能排列图表,还能添加文本标签和色条。我的光谱分析报告就自动生成这样的排版:
python复制layout.addLabel("实验数据报告", row=0, colspan=3)
layout.addPlot(row=1, col=0, rowspan=2)
layout.addPlot(row=1, col=1)
layout.addPlot(row=1, col=2)
layout.addColorBar(item, row=2, col=1, colspan=2)
最近还发现个黑科技:在GraphicsLayout中嵌入QWidget。这意味着我们可以把PyQt的按钮、滑块等控件直接整合到图表布局中,实现真正的交互式分析界面。