Python 的主要优势之一是探索性数据科学和可视化生态体系。一般的工具链为Pandas、numpy、sklearn 进行数据分析和使用matplotlib进行绘图。

但是如果需要自己自定义一个个性化的图形界面工具,则可能不是很合适。为了实现这种需求,可以使用PyQt构建GUI应用程序,但是可以使用上面的生态系工具构建自定义的复杂的数据驱动应用程序和交互式仪表板。

对于简单且高度交互的绘图,本文介绍一个Python GUI的绘图PyQtGraph。

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(1)

概述

PyQtGraph建立在Qt QGraphiCSScene的原生库,可提供更好更高性能绘图能力,特别是对于实时数据,可以提供交互性和使用Qt图形小部件轻松自定义绘图的能力。

PyQtGraphzh主要特点有:

各种线图和散点图;

数据平移/缩放鼠标;

实时数据更新和显示,交互数据的快速绘制;

图像显示与互动的查找表和水平控制;

支持完全的类型(常见类型和Qt类型,比如RGB,RGBA,或亮度,QColor)

2D交互视图绘制;

交互式视窗旋转/缩放鼠标;

视频流的显示和实时交互;

网格的等值面渲染;

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(2)

三维图形系统;

三维表面图和散点图;

实验数据切片的多维图像任意角度的函数(比如,对MRI数据处理);

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(3)

更容易编程的基本的3D场景图;

对感兴趣的控制区选择和数据显示;

交互标记垂直/水平的地块的位置和区域;

从图像中选择任意区域的部件和自动切片数据匹配;

安装

为了使用PyQtGraph做图,必须先安装该模块。PyQtGraph依赖Pyhon 3.7和PyQt,所以需要先安装这些库。

在Pyhon 3.7环境下,可以用:

pip install qt pip install pyqt

然后

pip install pyqtgraph

为了方便可以使用anaconda环境,这样只需安装

conda install qt conda install pyqt

然后

conda install pyqtgraph

PyQtGraph小部件

在PyQtGraph中,所有图都是使用PlotWidget小部件。小部件提供了canvas,可以在其上添加和配置任何类型的绘图。在hood下,绘图小部件使用本机Qt QGraphicsScene。例如,我们创建一个PlotWidget至于任何其他小部件的例子:

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(4)

from PyQt6 import QtWidgets from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys import os class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.graphWidget = pg.PlotWidget() self.setCentralWidget(self.graphWidget) x = [1, 2, 3, 4, 5, 6, 7, 8] y = [3567.44,3462.95,3309.75,3167.13,3508.70,3284.83,3313.58,3268.02] self.graphWidget.plot(x, y) def main(): app = QtWidgets.QApplication(sys.argv) main = MainWindow() main.show() sys.exit(app.exec()) if __name__ == '__main__': main()

PyQtGraph 的默认绘图样式非常简单——黑色背景和细(几乎不可见)白线。在下一节中,我们将看看 PyQtGraph 中有哪些可用选项来改善绘图的外观和可用性。

造型图

PyQtGraph继承了Qt的QGraphicsScene渲染图表,可以实现使用所有标准Qt线条和形状样式选项。PyQtGraph提供了一个API用于使用这些来绘制绘图和管理绘图画布。

下面我们将介绍创建和自定义绘图所需的最常见的样式功能。

背景色

可以通过调用.setBackground来改变背景颜色。PlotWidget实例(在 self.graphWidget)。下面的代码将通过传入字符串“w”将背景设置为白色。

self.graphWidget.setBackground('w')

可以随时设置(和更新)绘图的背景颜色。

from PyQt5 import QtWidgets from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys import os class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.graphWidget = pg.PlotWidget() self.setCentralWidget(self.graphWidget) x = [1, 2, 3, 4, 5, 6, 7, 8] y = [3567.44,3462.95,3309.75,3167.13,3508.70,3284.83,3313.58,3268.02] self.graphWidget.setTitle("Chongchong", color="b", size="30pt") self.graphWidget.plot(x, y) def main(): app = QtWidgets.QApplication(sys.argv) main = MainWindow() main.show() sys.exit(app.exec()) if __name__ == '__main__': main()

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(5)

有许多使用单个字母的简单颜色表示,这是基于matplotlib. 主意这种表示中表示黑色的是“k”。

除了这些单字母代码之外,还可以使用十六进制的RGB和RGBA 设置更复杂的颜色,

比如 #672922

self.graphWidget.setBackground('#bbccaa')

RGB和RGBA值可以分别作为3元组或4元组传入,使用值 0-255。

self.graphWidget.setBackground((100,50,255)) # RGB each 0-255 self.graphWidget.setBackground((100,50,255,25))

最后,还可以使用Qt的颜色表示法QColor:

from PyQt5 import QtGui self.graphWidget.setBackground(QtGui.QColor(100,50,254,25))

如果使用特定的QColor应用程序中其他位置的对象,或将绘图背景设置为默认的GUI背景颜色。

color = self.palette().color(QtGui.QPalette.Window) self.graphWidget.setBackground(color)

线条颜色、宽度和样式

PyQtGraph中的线条也是使用标准Qt绘制的 QPen类型,可以像在任何其他操作中一样完全控制线条绘制QGraphicsScene绘画。要使用笔绘制一条线,只需创建一个新的QPen实例并将其传递给 plot方法。

下面的代码创建一个QPen对象,传入一个3元组int指定 GB值(全红色)的值。也可以通过传递 'r' 或 Qcolor。然后通过pen参数将其它传入plot。

pen = pg.mkPen(color=(255, 0, 0)) self.graphWidget.plot(hour, temperature, pen=pen)

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(6)

效果如下:

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(7)

线条颜色

通过改变QPen可以改变线条的外观,包括以像素为单位的线条宽度和使用标准Qt线条样式的样式(虚线、点线等)。比如创建一条15像素宽的红色虚线,代码为:

pen = pg.mkPen(color=(255, 0, 0), width=15, style=QtCore.Qt.DashLine)

结果如下:

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(8)

标准Qt线条样式都可以使用,包括Qt.SolidLine, Qt.DashLine, Qt.DotLine, Qt.DashDotLine和 Qt.DashDotDotLine,起对样样式官方图如下

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(9)

线标记

对于许多绘图,在绘图上添加标记或代替线条放置标记可能会有所帮助。要在绘图上绘制标记,请在调用时传递符号以用作标记.plot。

self.graphWidget.plot(hour, temperature, symbol=' ')

此外symbol你也可以传入symbolSize,symbolBrush和 ymbolPen参数。传递的值作为 symbolBrush可以是任何颜色或QBrush样式,symbolPen中可以中使用颜色参数或QPen实例。

画笔

画用于绘制形状的轮廓,用于填充。

下面的代码将在粗红线上给出一个大小为30的蓝色十字标记。

pen = pg.mkPen(color=(255, 0, 0), width=15, style=QtCore.Qt.DashLine) self.graphWidget.plot(hour, temperature, pen=pen, symbol=' ', symbolSize=30, symbolBrush=('b'))

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(10)

也支持传入任何 QPainterPath对象,然后完全自定义的标记形状。

画板标题

画板标题对于为给定图表上显示的内容提供上下文非常重要。在 PyQtGraph中,可以使用 PlotWidget对象的setTitle()方法,设定标题字符串。

self.graphWidget.setTitle("Chongchong")

可以通过传递其他参数将文本样式(包括颜色、字体大小和粗细)应用于标题(以及 PyQtGraph中的任何其他标签)。

比如设置标题蓝色,字体大小为 30px。

self.graphWidget.setTitle("Chongchong", color="b", size="30pt")

也支持使用HTML标记语法来设置标题的样式,比如:

self.graphWidget.setTitle("<span style=\"color:blue;font-size:30pt\">Chongchong</span>")

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(11)

轴标识

与标题类似,可以使用setLabel()方法来创建我们的轴标识。这需要两个参数, position和text。position参数可以设置'left,'right','top','bottom'的值,用来支指出放置文本的轴的位置。参数text是要显示标题的文本。

可以将其他样式参数传递给该方法。但与标题略有不同,必须是有效的CSS名称-值对。例如,大小为font-size. 因为名字font-size有连字符,不能直接作为参数传递,必须使用 **dictionary方法。

styles = {'color':'r', 'font-size':'20px'} self.graphWidget.setLabel('left', '上证指数', **styles) self.graphWidget.setLabel('bottom', '月', **styles)

也支持HTML样式的语法,比如

self.graphWidget.setLabel('left', "<span style=\"color:red;font- size:20px\">上证指数</span>") self.graphWidget.setLabel('bottom', "<span style=\"color:red;font-size:20px\">月</span>")

图例

除了轴和绘图标题之外,通常会要显示一个图例来标识给定线所代表的内容。比如添加了多条线时,可以通过调用来为绘图添加图例。可以通过PlotWidget对象的.addLegent。但是需要在调用时为每一行提供一个名称 .plot()。比如,以下代码在绘制的线分配了一个名称“2022年” .plot(). 此名称将用于标识图例中的行。

self.graphWidget.plot(x, y, name = "2022年", pen = pen, symbol=' ', symbolSize=30, symbolBrush=('b')) self.graphWidget.addLegend()

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(12)

图例默认显示在左上角。可以通过将2元组传递给offset创建图例时的参数。

背景网格

添加背景网格可以让绘图更易于阅读,尤其是在尝试将相对x和y值相互比较时。可以通过调用PlotWidget的.showGrid为打开背景网格,可以独立切换x和y网格。

self.graphWidget.showGrid(x=True, y=True)

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(13)

设置轴限制

有时候会有限制绘图上可见的数据范围或将轴锁定在一致的范围内。在 PyQtGraph 中,这可以使用.setXRange()和.setYRange()方法来强制绘图仅显示轴上指定范围内的数据。

self.graphWidget.setXRange(2, 5, padding=0) self.graphWidget.setYRange(3300, 3500, padding=0)

一个可选的填充参数导致范围设置为大于指定的分数(默认情况下在0.02和0.1之间,取决于ViewBox的大小)。如果要完全删除此填充,请传递0。

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(14)

绘制多条线

一个图中绘制多条线是很常见的。在PyQtGraph中,只需在PlotWidget中多次调用.plot()即可. 下面的例子,我绘制两条相似的数据线,每条线使用相同的线型、粗细等,但改变线的颜色。为了方便,定义一个plot函数,接受x和y要绘制的参数、线的名称(用于图例)和颜色。将颜色用于线条和标记颜色。

def plot(self, x, y, plotname, color): pen = pg.mkPen(color=color) self.graphWidget.plot(x, y, name=plotname, pen=pen, symbol=' ', symbolSize=30, symbolBrush=(color)) self.plot(x, y1, "2021", 'r') self.plot(x, y, "2022", 'b')

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(15)

其结果如下图:

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(16)

清除画板

有事可能希望定期清除和刷新绘图可以通过.clear()调用来实现。

self.graphWidget.clear()

将会从图中删除线条,但保持所有其他属性相同。

更新画板

虽然简单地清除画板并重新绘制所有元素,这样一来Qt必须销毁并重新创建 QGraphicsScene对象和所有元素。对于小型或简单的绘图,这没什么问题,但对一个比较复杂耗时的图,可能更好的方法是跟新局部数据,而不是从头来一遍。PyQtGraph 获取新数据并更新绘制的线,而不会影响画板的其他部分。

要更新画线,需要对线条对象的引用。首次使用创建行时返回此引用.plot可以简单地将它存储在一个变量中。请注意,这是对线不是对画板的引用。

my_line_ref = graphWidget.plot(x, y)

有了引用变量,只需其.setData关更新数据。

from PyQt5 import QtWidgets, QtCore from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys import os from random import randint class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.graphWidget = pg.PlotWidget() self.setCentralWidget(self.graphWidget) self.x = list(range(100)) self.y = [randint(0,100) for _ in range(100)] self.graphWidget.setBackground('w') pen = pg.mkPen(color=(255, 0, 0)) self.data_line = self.graphWidget.plot(self.x, self.y, pen=pen) app = QtWidgets.QApplication(sys.argv) w = MainWindow() w.show() sys.exit(app.exec())

pyqt5做一个画图工具(使用PyQtGraph自定义绘图)(17)

每50毫秒更新一次数据,尽管 PyQtGraph 绘制数据的速度比这快得多,但很难观察!为此,可以定义了一个Qt计时器,并将其设置为调用自定义方法update_plot_data更改数据,这样就会生成滚动的动态图:

self.timer = QtCore.QTimer() self.timer.setInterval(50) self.timer.timeout.connect(self.update_plot_data) self.timer.start() def update_plot_data(self): self.x = self.x[1:] self.x.append(self.x[-1] 1) self.y = self.y[1:] self.y.append( randint(0,100)) self.data_line.setData(self.x, self.y)

总结

本文我们介绍了,使用PyQtGraph绘制简单的图并自定义线条、标记和标签。实际上PyQtGraph是一个强大的绘图工具,作为一个入门介绍,想更深入一步的研究和使用可以参考PyQtGraph官方更详细的文档和实例程序。

,