详解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抓取网页图片并放到指定文件夹
Apr 24 Python
Python 编码Basic Auth使用方法简单实例
May 25 Python
python安装numpy&amp;安装matplotlib&amp; scipy的教程
Nov 02 Python
详解django自定义中间件处理
Nov 21 Python
详解Python正则表达式re模块
Mar 19 Python
CentOS6.9 Python环境配置(python2.7、pip、virtualenv)
May 06 Python
python flask解析json数据不完整的解决方法
May 26 Python
Python中asyncio模块的深入讲解
Jun 10 Python
python监控进程状态,记录重启时间及进程号的实例
Jul 15 Python
Python图像处理模块ndimage用法实例分析
Sep 05 Python
django admin管理工具自定义时间区间筛选器DateRangeFilter介绍
May 19 Python
python如何用matplotlib创建三维图表
Jan 26 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
PHP+DBM的同学录程序(3)
2006/10/09 PHP
php利用cookie实现访问次数统计代码
2011/05/19 PHP
PHP获取本周第一天和最后一天示例代码
2014/02/24 PHP
CodeIgniter集成smarty的方法详解
2016/05/26 PHP
PHP后端银联支付及退款实例代码
2017/06/23 PHP
php进程daemon化的正确实现方法
2018/09/06 PHP
phpStorm+XDebug+chrome 配置详解
2019/04/01 PHP
javascript 面向对象编程基础:继承
2009/08/21 Javascript
JavaScript中String和StringBuffer的速度之争
2010/04/01 Javascript
JSON 教程 json入门学习笔记
2020/09/22 Javascript
使用 JScript 创建 .exe 或 .dll 文件的方法
2011/07/13 Javascript
利用NodeJS和PhantomJS抓取网站页面信息以及网站截图
2013/11/18 NodeJs
jQuery 获取、设置HTML或TEXT内容的两种方法
2014/05/23 Javascript
在JavaScript应用中实现延迟加载的方法
2015/06/25 Javascript
Bootstrap复选框和单选按钮美化插件(推荐)
2016/11/23 Javascript
Vue中的ref作用详解(实现DOM的联动操作)
2017/08/21 Javascript
基于es6三点运算符的使用方法(实例讲解)
2017/10/12 Javascript
mockjs+vue页面直接展示数据的方法
2018/12/19 Javascript
微信小程序结合Storage实现搜索历史效果
2019/05/18 Javascript
微信小程序实现批量倒计时功能
2020/11/01 Javascript
通过Kettle自定义jar包供javascript使用
2020/01/29 Javascript
基于Django用户认证系统详解
2018/02/21 Python
Python实现将json文件中向量写入Excel的方法
2018/03/26 Python
教你使用python画一朵花送女朋友
2018/03/29 Python
机器学习之KNN算法原理及Python实现方法详解
2018/07/09 Python
python3 中文乱码与默认编码格式设定方法
2018/10/31 Python
python绘制分布折线图的示例
2020/09/24 Python
Django正则URL匹配实现流程解析
2020/11/13 Python
Hotels.com爱尔兰:全球酒店预订
2017/02/24 全球购物
BudgetAir印度:预订航班、酒店和汽车租赁
2019/07/07 全球购物
生产主管岗位职责
2013/11/10 职场文书
服装电子商务创业计划书
2014/01/30 职场文书
2014年基层党组织公开承诺书
2014/03/29 职场文书
《放小鸟》教学反思
2014/04/20 职场文书
任长霞观后感
2015/06/16 职场文书
2019幼儿教师求职信(3篇)
2019/09/20 职场文书