PyQt5嵌入Matplotlib图形并实时刷新的完整指南与避坑

听雨眠
听雨眠
发布时间:2026-10-15| 3人浏览过
原创

本文详细讲解如何在PyQt5界面中嵌入Matplotlib绘图组件,并重点解决实时刷新时的常见错误。内容涵盖画布创建、变量覆盖导致的闪退问题、正确的重绘与事件刷新方法,以及针对高频数据更新的Animation模块使用技巧,提供完整代码示例与避坑指南。

在PyQt5 UI中嵌入Matplotlib图形的方法

一、在PyQt5 UI中嵌入Matplotlib图形的方法

1、导入必要的模块

导入过程相对直接。首先需声明使用PyQt5,并通过FigureCanvasQTAgg创建画布对象。该画布可作为图像显示载体,相当于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>)。这看似简单,但排查过程繁琐。


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

错误根源在于部分程序员在自定义类接收外部参数时,习惯将参数转换为全局变量。例如本例中,__init__方法接收width、heigh、dpi三个参数时,直接赋值给self.width、self.heigh、self.dpi并随后调用。无论后续是否调用,均会导致闪退。原因是FigureCanvasQTAgg父类导入了backend_qt5.py模块,而该模块内部也使用了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接收实例,此时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图像的注意事项

二、实时刷新Matplotlib图像的注意事项

关于通过网络查询实现的图像实时刷新,常见方案多为先clean清除旧图像、重新plot、再调用重绘draw()。从其他帖子摘录的代表性三步操作如下:


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

这三步本身无误,但描述过于简略,忽略细节可能导致无法刷新或报错闪退。关键注意点在于draw()。由于原文描述模糊,self指向不明,通常直接调用draw()是错误的。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()虽能重绘,但运行中可能闪退,因此建议加上flush_events()以确保安全。

实时更新Matplotlib的另一种方法

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

前述方法使用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

四、使用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 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。