详解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里disconnect UDP套接字的方法
Apr 23 Python
十个Python程序员易犯的错误
Dec 15 Python
深入理解Python中的内置常量
May 20 Python
Python中的__slots__示例详解
Jul 06 Python
python调用Delphi写的Dll代码示例
Dec 05 Python
解决DataFrame排序sort的问题
Jun 07 Python
Python multiprocessing多进程原理与应用示例
Feb 28 Python
python线程的几种创建方式详解
Aug 29 Python
Django中自定义模型管理器(Manager)及方法
Sep 23 Python
Python爬取爱奇艺电影信息代码实例
Nov 26 Python
python基于celery实现异步任务周期任务定时任务
Dec 30 Python
python之随机数函数的实现示例
Dec 30 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缓存技术详细总结
2013/08/07 PHP
codeigniter框架The URI you submitted has disallowed characters错误解决方法
2014/05/06 PHP
JavaScript对象链式操作代码(jquery)
2010/07/04 Javascript
JS刷新父窗口的几种方式小结(推荐)
2016/11/09 Javascript
js css自定义分页效果
2017/02/24 Javascript
jQuery+ajax读取json数据并按照价格排序示例
2018/03/28 jQuery
Node.js npm命令运行node.js脚本的方法
2018/10/10 Javascript
JavaScript实现滑动门效果
2020/01/18 Javascript
ES6学习教程之Promise用法详解
2020/11/22 Javascript
vue中配置scss全局变量的步骤
2020/12/28 Vue.js
JS实现公告上线滚动效果
2021/01/10 Javascript
[01:32]2016国际邀请赛中国区预选赛IG战队首日赛后采访
2016/06/27 DOTA
Python中logging模块的用法实例
2014/09/29 Python
python实现无证书加密解密实例
2014/10/27 Python
Python实现周期性抓取网页内容的方法
2015/11/04 Python
Python 搭建Web站点之Web服务器与Web框架
2016/11/06 Python
python中pandas.DataFrame排除特定行方法示例
2017/03/12 Python
python实现简单中文词频统计示例
2017/11/08 Python
python入门前的第一课 python怎样入门
2018/03/06 Python
对python 匹配字符串开头和结尾的方法详解
2018/10/27 Python
django 控制页面跳转的例子
2019/08/06 Python
python爬虫快速响应服务器的做法
2020/11/24 Python
python opencv实现图像配准与比较
2021/02/09 Python
Otticanet澳大利亚:最顶尖的世界名牌眼镜, 能得到打折季的价格
2018/08/23 全球购物
电子商务专业在校生实习自我鉴定
2013/09/29 职场文书
3.12植树节活动总结2014
2014/03/13 职场文书
师德师风个人反思
2014/04/28 职场文书
关爱残疾人演讲稿
2014/05/24 职场文书
素质教育标语
2014/06/27 职场文书
2014法院四风问题对照检查材料思想汇报
2014/10/04 职场文书
学习十八届四中全会精神思想汇报
2014/10/23 职场文书
2014年职称评定工作总结
2014/11/26 职场文书
运动会表扬稿
2015/01/16 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
Python中可变和不可变对象的深入讲解
2021/08/02 Python
Nebula Graph解决风控业务实践
2022/03/31 MySQL