一、項目背景:
看了matplotlib for python developers這本書,基本掌握了在pyqt中顯示曲線的做法,于是自己寫一個。
二、需求描述:
1)X軸顯示時間點,顯示長度為1分鐘,每一秒鐘繪制一個點,X軸長度超過1分鐘,則左移1秒刻度,實現動態效果
2)Y軸顯示隨機變化的數值,1-100
三、準備工作
1環境:python3.3,eric5,pyqt4
四、開始動手:
使用Eric創建新項目:
 
在設計編碼前期主要用到Eric的兩個窗口:源碼和窗體瀏覽器,類似delphi。
 
 
在窗體瀏覽器中,右鍵,new Form,窗體類型選擇Main Window,如下:
 
保存時,取名為MplMainWindow。
 
在界面上放兩個PushButton,水平布局,然后放一個Widget,修改名稱、水平及垂直策略。
界面設計如下:
 
最后執行網格布局。
為了嵌入Matplotlib在mplCanvas中,需要將mplCanvas升級,右鍵執行PRomote,輸入類名稱為MplCanvasWrapper,這個類就是編寫matplotlib代碼的,文件名稱為mplCanvasWrapper。
 
點擊添加,然后點擊提升。
保存當前設計的窗體。
到此就完成了界面設計,qt的界面保存的內容是xml的,需要轉換成python代碼,有兩種方式:
方法1:使用Eric自帶的功能:在窗體瀏覽器中,右鍵窗體ui文件,執行compile form命令,此時會在當前ui文件目錄中生成Ui_MplMainWindow.py文件。
方法2:在cmd中執行命令【pyuic 4 –o 目的文件名 原文件名】,如下:
 
此時在項目文件夾中生成了一個MplMainWindow.py文件。
在此文檔中使用方式1,默認生成的文件名稱是Ui_MplMainWindow.py。
打開這個文件,做兩件事情:
1)在最后一行會有這么一句:“from mplCodeWrapper import MplCodeWrapper”,與提升時輸入的類名文件名完全一致,把這句話剪切到文件頂部,要不會報錯的。
2)將窗體的繼承由object改為QtGui.QMainWindow
然后我們要創建文件mplCodeWrapper.py
在Eric的源碼瀏覽器中,新建文件,保存為mplCodeWrapper.py,寫上兩句空代碼:
from PyQt4 import QtCore
from PyQt4 import QtGui
from Ui_MplMainWindow import Ui_MainWindow
class Code_MainWindow(Ui_MainWindow):#修改為從Ui_MainWindow繼承
def __init__(self, parent = None):
super(Code_MainWindow, self).__init__(parent)
pass
到此為止,整個框架搭起來了,界面文件和繪圖文件都有了。
下面為窗體添加事件處理。
本著界面和代碼分離的原則,我們新建一個py文件,用于編寫界面代碼
在當前目錄中新建文件Code_MplMainWindow.py,主要用來綁定按鈕事件及中間邏輯。
上面說了一堆,可能不是很明白為什么要這么改,在此畫出類圖如下:
 
PyQt生成的文件Ui_MplMainWindow屬于純界面文件,類似于C#的designer文件,Code_MplMainWindow文件類似于C#的cs文件,而繪圖的邏輯放在MplCanvasWrapper中,這樣界面和實現就分離了。
如何讓X軸顯示時間并動起來呢?
1) 關于X軸顯示時間,matplotlib提供了plot_date方法
2) 設計一個線程,用于產生數據和繪圖,根據功能單一原則,我們需要將產生數據和繪圖分成兩類來實現,一個數據處理類,一個畫板類。完善后的類圖如下:
 
注意幾點:
1) 窗體關閉時,要有關閉確認提示,通過重寫closeEvent實現
2) 線程要有退出信號
完整代碼如下:
1) Ui_MplMainWindow.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MplMainWindow.ui'
#
# Created: Mon Aug 11 14:18:31 2014
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
from mplCanvasWrapper import MplCanvasWrapper
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.Qapplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
#inheritent from QtGui.QMainWindow
class Ui_MainWindow(QtGui.QMainWindow):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(690, 427)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName(_fromUtf8("centralWidget"))
self.gridLayout = QtGui.QGridLayout(self.centralWidget)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.btnStart = QtGui.QPushButton(self.centralWidget)
self.btnStart.setObjectName(_fromUtf8("btnStart"))
self.horizontalLayout.addWidget(self.btnStart)
self.btnPause = QtGui.QPushButton(self.centralWidget)
self.btnPause.setObjectName(_fromUtf8("btnPause"))
self.horizontalLayout.addWidget(self.btnPause)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.mplCanvas = MplCanvasWrapper(self.centralWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.mplCanvas.sizePolicy().hasHeightForWidth())
self.mplCanvas.setSizePolicy(sizePolicy)
self.mplCanvas.setObjectName(_fromUtf8("mplCanvas"))
self.gridLayout.addWidget(self.mplCanvas, 1, 0, 1, 1)
MainWindow.setCentralWidget(self.centralWidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.btnStart.setText(_translate("MainWindow", "開始", None))
self.btnPause.setText(_translate("MainWindow", "暫停", None))
2)Code_MplMainWindow.py
from PyQt4 import QtGui, QtCore
from Ui_MplMainWindow import Ui_MainWindow
class Code_MainWindow(Ui_MainWindow):
def __init__(self, parent = None):
super(Code_MainWindow, self).__init__(parent)
self.setupUi(self)
self.btnStart.clicked.connect(self.startPlot)
self.btnPause.clicked.connect(self.pausePlot)
def startPlot(self):
''' begin to plot'''
self.mplCanvas.startPlot()
pass
def pausePlot(self):
''' pause plot '''
self.mplCanvas.pausePlot()
pass
def releasePlot(self):
''' stop and release thread'''
self.mplCanvas.releasePlot()
def closeEvent(self,event):
result = QtGui.QMessageBox.question(self,
"Confirm Exit...",
"Are you sure you want to exit ?",
QtGui.QMessageBox.Yes| QtGui.QMessageBox.No)
event.ignore()
if result == QtGui.QMessageBox.Yes:
self.releasePlot()#release thread's resouce
event.accept()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Code_MainWindow()
ui.show()
sys.exit(app.exec_())
3)mplCanvasWrapper.py
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
from array import array
import time
import random
import threading
from datetime import datetime
from matplotlib.dates import date2num, MinuteLocator, SecondLocator, DateFormatter
X_MINUTES = 1
Y_MAX = 100
Y_MIN = 1
INTERVAL = 1
MAXCOUNTER = int(X_MINUTES * 60/ INTERVAL)
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.ax.set_xlabel("time of data generator")
self.ax.set_ylabel('random data value')
self.ax.legend()
self.ax.set_ylim(Y_MIN,Y_MAX)
self.ax.xaxis.set_major_locator(MinuteLocator()) # every minute is a major locator
self.ax.xaxis.set_minor_locator(SecondLocator([10,20,30,40,50])) # every 10 second is a minor locator
self.ax.xaxis.set_major_formatter( DateFormatter('%H:%M:%S') ) #tick label formatter
self.curveObj = None # draw object
def plot(self, datax, datay):
if self.curveObj is None:
#create draw object once
self.curveObj, = self.ax.plot_date(np.array(datax), np.array(datay),'bo-')
else:
#update data of draw object
self.curveObj.set_data(np.array(datax), np.array(datay))
#update limit of X axis,to make sure it can move
self.ax.set_xlim(datax[0],datax[-1])
ticklabels = self.ax.xaxis.get_ticklabels()
for tick in ticklabels:
tick.set_rotation(25)
self.draw()
class MplCanvasWrapper(QtGui.QWidget):
def __init__(self , parent =None):
QtGui.QWidget.__init__(self, parent)
self.canvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.ntb = NavigationToolbar(self.canvas, parent)
self.vbl.addWidget(self.ntb)
self.vbl.addWidget(self.canvas)
self.setLayout(self.vbl)
self.dataX= []
self.dataY= []
self.initDataGenerator()
def startPlot(self):
self.__generating = True
def pausePlot(self):
self.__generating = False
pass
def initDataGenerator(self):
self.__generating=False
self.__exit = False
self.tData = threading.Thread(name = "dataGenerator",target = self.generateData)
self.tData.start()
def releasePlot(self):
self.__exit = True
self.tData.join()
def generateData(self):
counter=0
while(True):
if self.__exit:
break
if self.__generating:
newData = random.randint(Y_MIN, Y_MAX)
newTime= date2num(datetime.now())
self.dataX.append(newTime)
self.dataY.append(newData)
self.canvas.plot(self.dataX, self.dataY)
if counter >= MAXCOUNTER:
self.dataX.pop(0)
self.dataY.pop(0)
else:
counter+=1
time.sleep(INTERVAL)
效果圖:
 
總結:
通過該程序,主要熟悉了以下幾點:
1) Eric、QtDesigner使用,界面與邏輯分離
2) 重寫窗體事件
3) 綁定信號和槽
4) 使用線程
5) 面向對象的matplotlib的使用
新聞熱點
疑難解答