數據繪圖方案
Python語言 的數據可視化(繪圖) 方法,常見的有 Matplotlib 和 PyQtGraph
- Matplotlib
說到 Python語言 的數據作圖, Matplotlib 當然是最有名的。
優點: 功能完備、成熟穩定、社區生態圈龐大。
缺點: 某些作圖場景性能不高。
- PyQtGraph
PyQtGraph 是基於Qt 的純Python 庫。
優點: 大數據量的作圖性能高於 Matplotlib, 動態更新圖的性能也比Matplotlib高。
並且和Qt圖形界面框架完美融合,因為它的2D作圖就是基於 Qt View Framework 開發的。
缺點: 作圖功能沒有Matplotlib多,開發社區沒有Matplotlib大。
那么,我們應該使用哪種方案呢?我的建議是:
如果你已經使用Qt開發圖形界面程序了,並且作圖功能是PyQtGraph支持的, 建議使用 PyQtGraph,因為它和Qt界面無縫結合。
否則 使用 Matplotlib。
本文先介紹 PyQtGraph 的使用示例。
PyQtGraph 安裝
pip install pyqtgraph
官方文檔 和 案例
PyQtGraph 官方文檔可以點擊這里查閱
其中包含了很多示例代碼,我們只需運行如下代碼,即可查看這些demo和對應的代碼
import pyqtgraph.examples pyqtgraph.examples.run()
曲線圖 示例
下面是一個最常見的 根據x/y軸對應的值 作曲線圖的例子
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
# 創建 繪制窗口類 PlotWindow 對象,內置一個繪圖控件類 PlotWidget 對象
pw = pg.plot()
# 設置圖表標題、顏色、字體大小
pw.setTitle("氣溫趨勢",color='008080',size='12pt')
# 背景色改為白色
pw.setBackground('w')
# 顯示表格線
pw.showGrid(x=True, y=True)
# 設置上下左右的label
# 第一個參數 只能是 'left', 'bottom', 'right', or 'top'
pw.setLabel("left", "氣溫(攝氏度)")
pw.setLabel("bottom", "時間")
# 設置Y軸 刻度 范圍
pw.setYRange(min=-10, # 最小值
max=50) # 最大值
# 創建 PlotDataItem ,缺省是曲線圖
curve = pw.plot( pen=pg.mkPen('b')) # 線條顏色
hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]
curve.setData(hour, # x坐標
temperature # y坐標
)
QtGui.QApplication.instance().exec_()
清除畫圖區,重新繪制
如果使用新的數據再次繪圖,可以先調用clear方法清除原來的內容(plotitem),如下
# 清除原來的plot內容
pw.clear()
# 創建 PlotDataItem ,缺省是曲線圖
curve = pw.plot( pen=pg.mkPen('b')) # 線條顏色
hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
temperature = [130, 132, 134, 132, 133,131, 129, 132, 135, 145]
curve.setData(hour, # x坐標
temperature # y坐標
)
PlotWidget 和 GraphicsLayoutWidget
PyQtGraph 中 繪圖控件類 有兩種 PlotWidget 和 GraphicsLayoutWidget, 都是 GraphicsView 子類。GraphicsView 是 Qt 的 QGraphicsView 子類,在其基礎上改進了一些功能。
PlotWidget 只能內置一個 繪圖對象PlotItem, 而 GraphicsLayoutWidget 可以內置多個 繪圖對象。 通常我們使用最多的是PlotWidget
PlotWidget對象的 內置繪圖對象 PlotItem ,可以通過 getPlotItem() 方法獲取。
為了方便,大部分的PlotItem方法都可以直接通過 PlotWidget對象調用。 比如我們上面示例代碼中的 setTitle、showGrid、setLabel、setYRange、plot 等。
調用 plot方法,會創建一個PlotDataItem, 缺省是曲線圖
關於PyQtGraph繪圖基本架構的更多細節,點擊這里查看官方文檔
嵌入到Qt程序界面中
上面的例子,圖表是在單獨的程序中運行顯示。
如果我們要把它 嵌入到我們的Qt程序界面 中,主要通過 pyqtgraph 的 PlotWidget 或者 GraphicsLayoutWidget 控件類, 代碼如下所示
from PySide2 import QtWidgets
import pyqtgraph as pg
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('pyqtgraph作圖示例')
# 創建 PlotWidget 對象
self.pw = pg.PlotWidget()
# 設置圖表標題
self.pw.setTitle("氣溫趨勢",color='008080',size='12pt')
# 設置上下左右的label
self.pw.setLabel("left","氣溫(攝氏度)")
self.pw.setLabel("bottom","時間")
# 背景色改為白色
self.pw.setBackground('w')
hour = [1,2,3,4,5,6,7,8,9,10]
temperature = [30,32,34,32,33,31,29,32,35,45]
# hour 和 temperature 分別是 : x, y 軸上的值
self.pw.plot(hour,
temperature,
pen=pg.mkPen('b') # 線條顏色
)
# 創建其他Qt控件
okButton = QtWidgets.QPushButton("OK")
lineEdit = QtWidgets.QLineEdit('點擊信息')
# 水平layout里面放 edit 和 button
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(lineEdit)
hbox.addWidget(okButton)
# 垂直layout里面放 pyqtgraph圖表控件 和 前面的水平layout
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.pw)
vbox.addLayout(hbox)
# 設置全局layout
self.setLayout(vbox)
if __name__ == '__main__':
app = QtWidgets.QApplication()
main = MainWindow()
main.show()
app.exec_()
柱狀圖
pyqtgraph 可以產生這樣的 柱狀圖

繪制多個圖形
可以使用 GraphicsLayoutWidget,創建多個繪圖對形

實時更新圖
要畫動態的實時更新圖,只需要在把變更的內容重新plot即可。
示例代碼如下
from PySide2 import QtWidgets
from pyqtgraph.Qt import QtCore
import pyqtgraph as pg
import sys
from random import randint
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('pyqtgraph作圖')
# 創建 PlotWidget 對象
self.pw = pg.PlotWidget()
# 設置圖表標題
self.pw.setTitle("氣溫趨勢",
color='008080',
size='12pt')
# 設置上下左右的label
self.pw.setLabel("left","氣溫(攝氏度)")
self.pw.setLabel("bottom","時間")
# 設置Y軸 刻度 范圍
self.pw.setYRange(min=-10, # 最小值
max=50) # 最大值
# 顯示表格線
self.pw.showGrid(x=True, y=True)
# 背景色改為白色
self.pw.setBackground('w')
# 設置Y軸 刻度 范圍
self.pw.setYRange(min=-10, # 最小值
max=50) # 最大值
# 居中顯示 PlotWidget
self.setCentralWidget(self.pw)
# 實時顯示應該獲取 plotItem, 調用setData,
# 這樣只重新plot該曲線,性能更高
self.curve = self.pw.getPlotItem().plot(
pen=pg.mkPen('r', width=1)
)
self.i = 0
self.x = [] # x軸的值
self.y = [] # y軸的值
# 啟動定時器,每隔1秒通知刷新一次數據
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateData)
self.timer.start(1000)
def updateData(self):
self.i += 1
self.x.append(self.i)
# 創建隨機溫度值
self.y.append(randint(10,30))
# plot data: x, y values
self.curve.setData(self.x,self.y)
if __name__ == '__main__':
app = QtWidgets.QApplication()
main = MainWindow()
main.show()
app.exec_()
PyQtGraph圖形可以作為一個 Qt的 widget控件,嵌入到 Qt 程序主窗口中。
我們可以在 Qt Designer 中把 PyQtGraph圖形控件 作為第三方控件 加入。
比如,像下面這樣:

在Qt Designer中加入第三方控件
通過 Qt Designer,我們可以預先把界面上的控件的位置大小設計好,然后動態加載。
但是 界面上擺放的都是 Qt內置的控件, 那么像 PyQtGraph 里面的 PlotWidget這種第三方控件怎么 放到 Qt Designer中呢? 我們的代碼又怎么去獲取到該控件對應的對象呢?
根據上面的視頻,產生的界面ui文件在下面的鏈接zip文件中
http://cdn1.python3.vip/files/qt/stock-01.zip
如果你使用的是PySide2, 對應的代碼如下,注意第14行 注冊的作用
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
import pyqtgraph as pg
class Stock:
def __init__(self):
loader = QUiLoader()
# pyside2 一定要 使用registerCustomWidget
# 來注冊 ui文件中的第三方控件,這樣加載的時候
# loader才知道第三方控件對應的類,才能實例化對象
loader.registerCustomWidget(pg.PlotWidget)
self.ui = loader.load("main.ui")
hour = [1,2,3,4,5,6,7,8,9,10]
temperature = [30,32,34,32,33,31,29,32,35,45]
# 通過控件名稱 historyPlot,找到Qt designer設計的 控件
self.ui.historyPlot.plot(hour,temperature)
app = QApplication([])
stock = Stock()
stock.ui.show()
app.exec_()
如果使用 PyQt5,就更簡單了, 無需注冊,對應的代碼如下
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtWidgets, uic
class Stock:
def __init__(self):
# PyQt5 直接加載ui文件
# 因為 第三方控件通過promote的定義
# 已經可以知道 控件類所在模塊的路徑
self.ui = uic.loadUi("main.ui")
hour = [1,2,3,4,5,6,7,8,9,10]
temperature = [30,32,34,32,33,31,29,32,35,45]
self.ui.historyPlot.plot(hour, temperature)
app = QApplication([])
stock = Stock()
stock.ui.show()
app.exec_()
軸刻度為字符串
上面的程序運行起來, X軸的刻度是 數字, 如果我們希望軸刻度是文字怎么做呢?
我們參考了這個網址的介紹: https://stackoverflow.com/questions/31775468/show-string-values-on-x-axis-in-pyqtgraph?lq=1
需要定義從數字到字符串的映射列表,參考如下代碼
import pyqtgraph as pg
# 刻度表,注意是雙層列表
xTick = [[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f')]]
x = [0,1,2,3,4,5]
y = [1, 2, 3, 4, 5, 6]
win = pg.GraphicsWindow()
stringaxis = pg.AxisItem(orientation='bottom')
stringaxis.setTicks(xTick)
plot = win.addPlot(axisItems={'bottom': stringaxis})
curve = plot.plot(x,y)
pg.QtGui.QApplication.exec_()
如果使用 PlotWidget,則要獲取軸對象,參考代碼如下
# self.ui.historyPlot 就是 PlotWidget對象
xax = self.ui.historyPlot.getAxis('bottom')
xax.setTicks(xTick)
獲取鼠標所在處刻度值
有時候,我們的程序需要獲取 鼠標在 pyqtgraph 圖形上移動時,鼠標所在對應的數據是什么。
可以參考下面代碼的作法
import pyqtgraph as pg
import PySide2
x = [1,2,3,4,5]
y = [1,2,3,4,5]
win = pg.GraphicsWindow()
plot = win.addPlot()
curve = plot.plot(x,y)
def mouseover(pos):
# 參數pos 是像素坐標,需要 轉化為 刻度坐標
act_pos = curve.mapFromScene(pos)
if type(act_pos) != PySide2.QtCore.QPointF:
return
print(act_pos.x(),act_pos.y())
# 有了 刻度坐標值,下面就可以根據該值處理一些信息
# 比如狀態欄展示 該處的 日期等
curve.scene().sigMouseMoved.connect(mouseover)
pg.QtGui.QApplication.exec_()
練習題
開發一個股票數據軟件,題目要求見 視頻說明 https://www.bilibili.com/video/BV1cJ411R7bP?p=14
