详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)


Posted in Python onAugust 07, 2020

一、pyqt5的UI中嵌入matplotlib的方法

1、导入模块

导入模块比较简单,首先声明使用pyqt5,通过FigureCanvasQTAgg创建画布,可以将画布的图像显示到UI,相当于pyqt5的一个控件,后面的绘图就建立在这个画布上,然后把这个画布当中pyqt5的控件添加到pyqt5的UI上,其次要导入matplotlib.figure的Figure ,这里要注意的是matplotlib.figure中的Figure,不是matplotlib.pyplot模块中的Figure,要区分清楚。

import matplotlib
matplotlib.use("Qt5Agg") # 声明使用pyqt5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布
import matplotlib.pyplot as plt
# matplotlib.figure 模块提供了顶层的Artist(图中的所有可见元素都是Artist的子类),它包含了所有的plot元素
from matplotlib.figure import Figure

2、创建pyqt5画布,并简单设置样式

创建一个画布类,继承上面导入的FigureCanvasQTAgg,通过Figure 创建画布,并且作为参数传递给父类FigureCanvasQTAgg(这里是关键一步!没有这一步后面一切都是白费,不会添加成功!),最后一步添加绘图区self.axes

class MyMatplotlibFigure(FigureCanvasQTAgg):
  """
  创建一个画布类,并把画布放到FigureCanvasQTAgg
  """
  def __init__(self, width=10, heigh=10, dpi=100):
    plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色
    plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色
    self.width = width
    self.heigh = heigh
    self.dpi = dpi
    self.figs = Figure(figsize=(self.width, self.heigh), dpi=self.dpi)
    super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像
    self.axes = self.figs.add_subplot(111)

3、填上创建pyqt5画布挖的坑

上面自定义的画布类MyMatplotlibFigure写的时候不会提示错误,但是当你绘图的时候会傻眼了,因为没有报错但是闪退了!!!然后逐个把可疑的类和方法try… except … print(er),希望python能告诉你原因,抱歉!最终结果是什么都没有得到!使用debug单步调试慢慢分析,累死累活的一步一步看到最后添加画布到pyqt5时,跳到一个模块backend_qt5.py文件的第500行:if self.height() < 0 or self.width() < 0:从debug的变量分析中看到“(<class ‘TypeError'>, TypeError("‘int' object is not callable"), <traceback object at 0x000001C3E0397F08>)这是什么鬼?
其实这是一个很简单的错误,但是不小心犯了排查起来很麻烦!!!错误的原因就是有些程序员在自定义类内接收外部传参时经常把传递的参数转换为全局变量,比如这个例子中初始化__init__方法接收的三个参数 width、 heigh、 dpi,顺手写了个

self.width = width
self.heigh = heigh
self.dpi = dpi

 然后接着调用!问题就在这里了,其实不管你后面有没有调用,都会闪退!!!!!为什么呢?
因为FigureCanvasQTAgg父类中导入了backend_qt5.py模块,而backend_qt5模块内部也使用了相同的变量名self.width和self.heigh,所以呢,在这里用上面的写法就造成了对父类变量的覆盖。正确的写法:

class MyMatplotlibFigure(FigureCanvasQTAgg):
  """
  创建一个画布类,并把画布放到FigureCanvasQTAgg
  """
  def __init__(self, width=10, heigh=10, dpi=100):
    plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色
    plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色
    # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure
    # 这里还要注意,width, heigh可以直接调用参数,不能用self.width、self.heigh作为变量获取,因为self.width、self.heigh 在模块中已经FigureCanvasQTAgg模块中使用,这里定义会造成覆盖
    self.figs = Figure(figsize=(width, heigh), dpi=dpi)
    super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像(就是在画板上放置画布)
    self.axes = self.figs.add_subplot(111) # 添加绘图区

这里直接使用传参字符就可以了,这几个参数后面用不到了,如果你能用到就随便改个名字,比如self.w = width self.h = heigh

4、把画布添加到pyqt5的UI中

这里就比较简单了,创建一个简单的窗口,添加label,实例化上面创建的自定义画布类,用变量self.canvas接收实例,这就相当于pyqt5的控件了,在label上创建布局,布局中添加画布self.canvas
如果仅仅是把matplotlib的图像添加到Ui中,plotcos这个绘图方法放哪里都行,也可以在上面的自定义类中添加这个方法,只是最后绘图的两行简单修改即可:

class MainDialogImgBW_(QtWidgets.QMainWindow):
  """
  创建UI主窗口,使用画板类绘图。
  """
  def __init__(self):
    super(MainDialogImgBW_, self).__init__()
    self.setWindowTitle("显示matplotlib")
    self.setObjectName("widget")
    self.resize(800, 600)
    self.label = QtWidgets.QLabel(self)
    self.label.setGeometry(QtCore.QRect(0, 0, 800, 600))
    self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100)
    self.plotcos()
    self.hboxlayout = QtWidgets.QHBoxLayout(self.label)
    self.hboxlayout.addWidget(self.canvas)

  def plotcos(self):
    # plt.clf()
    t = np.arange(0.0, 5.0, 0.01)
    s = np.cos(2 * np.pi * t)
    self.canvas.aexs.plot(t, s)
    self.canvas.figs.suptitle("sin") # 设置标题

二、实时刷新matplotlib图像的坑

实时刷新图像如果通过网络查询,基本千篇一律的结果都是先clean清除之前的图像、重新plot、加上重绘draw(),从其它帖子找了最具代表性的三部曲步骤如下:

self.axes.cla()
self.axes.plot(x, y, 'o',xx,yy)
self.draw()

 这三部曲是没错,但是只是他们说的有点简单了,有些细节需要注意,否则一样不会刷新或者报错闪退。需要注意的坑是draw(),因为他们帖子上写的简单,实在不知道他们的self有几个意思,一般情况下这么写是错的。**cla()清空了绘图区,plot()重新绘制了图像,这两个都是对绘图区的操作,但是要draw()要重绘的是画布层不是绘图区。而且仅仅draw()是不够的,还要flush_events()否则可能在刷新画布过程中中途偶然闪退。**完整的正确代码如下(上面的第4条例子大的写法是绘图方法在UI类内,下面的例子用另外一种写法,在画布类中创建绘图方法):

class MyMatplotlibFigure(FigureCanvasQTAgg):
  """
  创建一个画布类,并把画布放到FigureCanvasQTAgg
  """
  def __init__(self, width=10, heigh=10, dpi=100):
    plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色
    plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色
    # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure
    self.figs = Figure(figsize=(width, heigh), dpi=dpi)
    super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 
    self.axes = self.figs.add_subplot(111) # 添加绘图区
  def mat_plot_drow_axes(self, t, s):
    """
    用清除画布刷新的方法绘图
    :return:
    """
    self.axes.cla() # 清除绘图区

    self.axes.spines['top'].set_visible(False) # 顶边界不可见
    self.axes.spines['right'].set_visible(False) # 右边界不可见
    # 设置左、下边界在(0,0)处相交
    # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0
    self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0
    self.axes.plot(t, s, 'o-r', linewidth=0.5)
    self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas
    self.figs.canvas.flush_events() # 画布刷新self.figs.canvas

class MainDialogImgBW(QtWidgets.QMainWindow):
  """
  创建UI主窗口,使用画板类绘图。
  """
  def __init__(self):
    super(MainDialogImgBW_, self).__init__()
    self.setWindowTitle("显示matplotlib")
    self.setObjectName("widget")
    self.resize(800, 600)
    self.label = QtWidgets.QLabel(self)
    self.label.setGeometry(QtCore.QRect(0, 0, 800, 600))
    self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100)
    self.plotcos()
    self.hboxlayout = QtWidgets.QHBoxLayout(self.label)
    self.hboxlayout.addWidget(self.canvas)

  def plotcos(self):
    # plt.clf()
    t = np.arange(0.0, 5.0, 0.01)
    s = np.cos(2 * np.pi * t)
    self.canvas.mat_plot_drow_axes(t, s)
    self.canvas.figs.suptitle("sin") # 设置标题


if __name__ == "__main__":
  app = QtWidgets.QApplication(sys.argv)
  main = MainDialogImgBW()
  main.show()
  sys.exit(app.exec_())

**需要注意的地方就是画布重绘的写法,既不是self.draw()也不是self.axes.draw()或self.figs.draw(),而是self.figs.canvas.draw()和self.figs.canvas.flush_events(),这里比较坑的是写这两句代码时没有智能提醒!(我用的pycharm,也有可能是被这个坑了,不知道其他IDE是否会提醒)**这里还要提醒的是只有self.figs.canvas.draw()没有self.figs.canvas.flush_events()时也会重绘,但是有可能在运行过程中闪退,所以还是加上比较安全。

三、实时更新matplotlib的另一种方法

上面是使用axes.cla()的方式刷新图表,但是你有可能会遇到,你要展示的下一个图形于前面一次图表完全不同,包括画布背景色等都不同,那么用上面的axes.cla()只清理绘图区就不够了,需要用得到清理画布figure.clf(),这个地方你要看清楚,清理绘图区方法是cla(),而清理画布是clf()一字之差。另外一个需要注意的地方就是,清理画布后之前画布上的绘图区axes也清理了,需要重新添加axes,完整代码如下:

class MyMatplotlibFigure(FigureCanvasQTAgg):
  """
  创建一个画布类,并把画布放到FigureCanvasQTAgg
  """
  def __init__(self, width=10, heigh=10, dpi=100):
    # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure
    self.figs = Figure(figsize=(width, heigh), dpi=dpi)
    super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 
  def mat_plot_drow(self, t, s):
    """
    用清除画布刷新的方法绘图
    :return:
    """
    self.figs.clf() # 清理画布,这里是clf()
    self.axes = self.figs.add_subplot(111) # 清理画布后必须重新添加绘图区
 self.axes.patch.set_facecolor("#01386a") # 设置ax区域背景颜色
    self.axes.patch.set_alpha(0.5) # 设置ax区域背景颜色透明度
    self.figs.patch.set_facecolor('#01386a') # 设置绘图区域颜色
    self.axes.spines['bottom'].set_color('r') # 设置下边界颜色
    self.axes.spines['top'].set_visible(False) # 顶边界不可见
    self.axes.spines['right'].set_visible(False) # 右边界不可见
    # 设置左、下边界在(0,0)处相交
    # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0
    self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0
    self.axes.plot(t, s, 'o-r', linewidth=0.5)
    self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas
    self.figs.canvas.flush_events() # 画布刷新self.figs.canvas

class MainDialogImgBW(QtWidgets.QMainWindow):
  """
  创建UI主窗口,使用画板类绘图。
  """
  def __init__(self):
    super(MainDialogImgBW_, self).__init__()
    self.setWindowTitle("显示matplotlib")
    self.setObjectName("widget")
    self.resize(800, 600)
    self.label = QtWidgets.QLabel(self)
    self.label.setGeometry(QtCore.QRect(0, 0, 800, 600))
    self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100)
    self.plotcos()
    self.hboxlayout = QtWidgets.QHBoxLayout(self.label)
    self.hboxlayout.addWidget(self.canvas)

  def plotcos(self):
    # plt.clf()
    t = np.arange(0.0, 5.0, 0.01)
    s = np.cos(2 * np.pi * t)
    self.canvas.mat_plot_drow(t, s)
    self.canvas.figs.suptitle("sin") # 设置标题


if __name__ == "__main__":
  app = QtWidgets.QApplication(sys.argv)
  main = MainDialogImgBW()
  main.show()
  sys.exit(app.exec_())

四、animation的方式刷新matplotlib

如果你在UI中的刷新频率非常高,比如股票或期货的tick数据,上面的刷新方式就有点不够用了,虽然也能刷新但是又可能会闪屏的情况很不舒服,高频刷新还是用animation方式刷新。
使用animation 需要增加导入matplotlib.animation模块的FuncAnimation方法,全部导入模块如下:

import matplotlib
matplotlib.use("Qt5Agg") # 声明使用pyqt5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布
import matplotlib.pyplot as plt
from matplotlib.figure import Figure 
from matplotlib.animation import FuncAnimation

FuncAnimation的基础使用这里就不赘述了,论坛内搜索就可以找到,只提一个可能存在坑的地方,数据更新函数是嵌套在绘图方法plot_tick内的(可不要以为这是格式错误)。这里直接上代码:

class MyMatPlotAnimation(FigureCanvasQTAgg):
  """
  创建一个画板类,并把画布放到容器(画板上)FigureCanvasQTAgg,再创建一个画图区
  """
  def __init__(self, width=10, heigh=10, dpi=100):
    # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure
    self.figs = Figure(figsize=(width, heigh), dpi=dpi)
    super(MyMatPlotAnimation, self).__init__(self.figs) 
    self.figs.patch.set_facecolor('#01386a') # 设置绘图区域颜色
    self.axes = self.figs.add_subplot(111)

  def set_mat_func(self, t, s):
    """
    初始化设置函数
    """
    self.t = t
    self.s = s
    self.axes.cla()
    self.axes.patch.set_facecolor("#01386a") # 设置ax区域背景颜色
    self.axes.patch.set_alpha(0.5) # 设置ax区域背景颜色透明度

    # self.axes.spines['top'].set_color('#01386a')
    self.axes.spines['top'].set_visible(False) # 顶边界不可见
    self.axes.spines['right'].set_visible(False) # 右边界不可见

    self.axes.xaxis.set_ticks_position('bottom') # 设置ticks(刻度)的位置为下方
    self.axes.yaxis.set_ticks_position('left') # 设置ticks(刻度) 的位置为左侧
    # 设置左、下边界在(0,0)处相交
    # self.axes.spines['bottom'].set_position(('data', 0)) # 设置x轴线再Y轴0位置
    self.axes.spines['left'].set_position(('data', 0)) # 设置y轴在x轴0位置
    self.plot_line, = self.axes.plot([], [], 'r-', linewidth=1) # 注意‘,'不可省略
    
  def plot_tick(self):
    plot_line = self.plot_line
    plot_axes = self.axes
    t = self.t
    
    def upgrade(i): # 注意这里是plot_tick方法内的嵌套函数
      x_data = [] # 这里注意如果是使用全局变量self定义,可能会导致绘图首位相联
      y_data = []
      for i in range(len(t)):
        x_data.append(i)
        y_data.append(self.s[i])
      plot_axes.plot(x_data, y_data, 'r-', linewidth=1)
      return plot_line, # 这里也是注意‘,'不可省略,否则会报错
      
    ani = FuncAnimation(self.figs, upgrade, blit=True, repeat=False)
    self.figs.canvas.draw() # 重绘还是必须要的

class MainDialogImgBW(QtWidgets.QMainWindow):
  def __init__(self):
    super(MainDialogImgBW_, self).__init__()
    self.setWindowTitle("显示matplotlib")
    self.setObjectName("widget")
    self.resize(800, 600)
    self.label = QtWidgets.QLabel(self)
    self.label.setGeometry(QtCore.QRect(0, 0, 800, 600))
    self.canvas = MyMatPlotAnimation(width=5, heigh=4, dpi=100)
    self.plotcos()
    self.hboxlayout = QtWidgets.QHBoxLayout(self.label)
    self.hboxlayout.addWidget(self.canvas)

  def plotcos(self):
    t = np.arange(0.0, 5.0, 0.01)
    s = np.cos(2 * np.pi * t)
    self.canvas.set_mat_func(t, s)
    self.canvas.plot_tick()


if __name__ == "__main__":
  app = QtWidgets.QApplication(sys.argv)
  main = MainDialogImgBW()
  main.show()
  sys.exit(app.exec_())

到此这篇关于详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)的文章就介绍到这了,更多相关pyqt5嵌入matplotlib图形内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python实现简单socket程序在两台电脑之间传输消息的方法
Mar 13 Python
python登录pop3邮件服务器接收邮件的方法
Apr 30 Python
python爬虫入门教程--快速理解HTTP协议(一)
May 25 Python
不可错过的十本Python好书
Jul 06 Python
Python生成8位随机字符串的方法分析
Dec 05 Python
Python import与from import使用及区别介绍
Sep 06 Python
python中partial()基础用法说明
Dec 30 Python
python ipset管理 增删白名单的方法
Jan 14 Python
在python下使用tensorflow判断是否存在文件夹的实例
Jun 10 Python
Python3.7基于hashlib和Crypto实现加签验签功能(实例代码)
Dec 04 Python
最新Python idle下载、安装与使用教程图文详解
Nov 28 Python
详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式
Jan 24 Python
Python configparser模块封装及构造配置文件
Aug 07 #Python
Python logging模块进行封装实现原理解析
Aug 07 #Python
Python定时任务APScheduler安装及使用解析
Aug 07 #Python
Python如何解除一个装饰器
Aug 07 #Python
解决Pycharm双击图标启动不了的问题(JetBrains全家桶通用)
Aug 07 #Python
Python实现上下文管理器的方法
Aug 07 #Python
Python 读取位于包中的数据文件
Aug 07 #Python
You might like
IIS下配置Php+Mysql+zend的图文教程
2006/12/08 PHP
PHP实现MySQL更新记录的代码
2008/06/07 PHP
PHP操作xml代码
2010/06/17 PHP
PHP-redis中文文档介绍
2013/02/07 PHP
Ajax,UTF-8还是GB2312 eval 还是execScript
2008/11/13 Javascript
Jquery+WebService 校验账号是否已被注册的代码
2010/07/12 Javascript
ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能
2012/02/03 Javascript
同步异步动态引入js文件的几种方法总结
2016/09/23 Javascript
nodejs学习笔记之路由
2017/03/27 NodeJs
详解如何在 vue 项目里正确地引用 jquery 和 jquery-ui的插件
2017/06/01 jQuery
js 概率计算(简单版)
2017/09/12 Javascript
关于微信小程序bug记录与解决方法
2018/08/15 Javascript
一秒学会微信小程序制作table表格
2019/02/14 Javascript
fastadmin中调用js的方法
2019/05/14 Javascript
element-ui 本地化使用教程详解
2019/10/28 Javascript
python实现apahce网站日志分析示例
2014/04/02 Python
详细解析Python中__init__()方法的高级应用
2015/05/11 Python
代码讲解Python对Windows服务进行监控
2018/02/11 Python
初探利用Python进行图文识别(OCR)
2019/02/26 Python
python自动识别文本编码格式代码
2019/12/26 Python
Pytorch转onnx、torchscript方式
2020/05/25 Python
Python字符串split及rsplit方法原理详解
2020/06/29 Python
WatchShop法国:英国排名第一的独立手表零售商
2020/02/17 全球购物
美国精品地毯网站:Boutique Rugs
2020/03/04 全球购物
3个CCIE对一个工程师的面试题
2012/05/06 面试题
GWebs公司笔试题
2012/05/04 面试题
请用Python写一个获取用户输入数字,并根据数字大小输出不同信息的脚本
2014/05/20 面试题
大一期末自我鉴定
2013/12/13 职场文书
项目经理聘任书
2014/03/29 职场文书
数控机床专业自荐信
2014/05/19 职场文书
2014年幼儿园园长工作总结
2014/12/17 职场文书
单身申明具结书
2015/02/26 职场文书
施工安全员岗位职责
2015/04/11 职场文书
干部作风纪律整顿心得体会
2016/01/23 职场文书
Flask response响应的具体使用
2021/07/15 Python
JS实现页面炫酷的时钟特效示例
2022/08/14 Javascript