详解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中dir函数用法分析
Apr 17 Python
python获取各操作系统硬件信息的方法
Jun 03 Python
使用Python解析JSON数据的基本方法
Oct 15 Python
Python使用cookielib模块操作cookie的实例教程
Jul 12 Python
python之Character string(实例讲解)
Sep 25 Python
关于Django显示时间你应该知道的一些问题
Dec 25 Python
Python3.遍历某文件夹提取特定文件名的实例
Apr 26 Python
Python RabbitMQ消息队列实现rpc
May 30 Python
示例详解Python3 or Python2 两者之间的差异
Aug 23 Python
python代码如何注释
Jun 01 Python
详解KMP算法以及python如何实现
Sep 18 Python
Python3 用什么IDE开发工具比较好
Nov 28 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类
2006/11/27 PHP
php实现的遍历文件夹下所有文件,编辑删除
2010/01/05 PHP
laravel config文件配置全局变量的例子
2019/10/13 PHP
为JavaScript提供睡眠功能(sleep) 自编译JS引擎
2010/08/16 Javascript
JavaScript中的字符串操作详解
2013/11/12 Javascript
jQuery使用append在html元素后同时添加多项内容的方法
2015/03/26 Javascript
JavaScript中的some()方法使用详解
2015/06/09 Javascript
微信小程序  自定义创建详细介绍
2016/10/27 Javascript
一句jQuery代码实现返回顶部效果(简单实用)
2016/12/28 Javascript
vue监听scroll的坑的解决方法
2017/09/07 Javascript
判断div滑动到底部的scroll实例代码
2017/11/15 Javascript
vue2.0 + element UI 中 el-table 数据导出Excel的方法
2018/03/02 Javascript
Node.js npm命令运行node.js脚本的方法
2018/10/10 Javascript
Python编程实现及时获取新邮件的方法示例
2017/08/10 Python
Python实现学校管理系统
2018/01/11 Python
python excel使用xlutils类库实现追加写功能的方法
2018/05/02 Python
利用python在excel里面直接使用sql函数的方法
2019/02/08 Python
解决django 新增加用户信息出现错误的问题
2019/07/28 Python
Python编写打字训练小程序
2019/09/26 Python
3行Python代码实现图像照片抠图和换底色的方法
2019/10/10 Python
python运用sklearn实现KNN分类算法
2019/10/16 Python
Python模块zipfile原理及使用方法详解
2020/08/04 Python
python进度条显示-tqmd模块的实现示例
2020/08/23 Python
阿联酋团购网站:Groupon阿联酋
2016/10/14 全球购物
新加坡一家在线男士皮具品牌:Faire Leather Co.
2019/12/01 全球购物
俄罗斯奢侈品牌衣服、鞋子和配饰的在线商店:INTERMODA
2020/07/17 全球购物
美国工业用品采购网站:Zoro.com
2020/10/27 全球购物
EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的
2015/07/24 面试题
社区包粽子活动方案
2014/01/21 职场文书
2014年餐厅服务员工作总结
2014/11/18 职场文书
工作失误检讨书范文
2015/01/26 职场文书
导游词欢迎词
2015/02/02 职场文书
爱国主题班会教案
2015/08/14 职场文书
解决SpringBoot文件上传临时目录找不到的问题
2021/07/01 Java/Android
vue实现移动端div拖动效果
2022/03/03 Vue.js
Win10本地连接不见了怎么恢复? win10系统电脑本地连接不见了解决方法
2023/01/09 数码科技