python - 如何在 PyQt5 GUI 中制作快速的 matplotlib 实时绘图

标签 python python-3.x matplotlib plot pyqt5

几年前,我已经尝试过实时嵌入 matplotlib情节在 PyQt5图形用户界面。实时绘图显示实时数据流,从传感器捕获,一些过程,......我得到了工作,你可以在这里阅读相关帖子:

  • Matplotlib animation inside your own GUI
  • How do I plot in real-time in a while loop using matplotlib?

  • 现在我需要再次做同样的事情。我记得我以前的方法有效,但跟不上快速的数据流。我在互联网上找到了几个示例代码,我想向您展示。其中一个明显比另一个快,但我不知道为什么。我想获得更多的见解。我相信更深入的了解将使我能够保持与 PyQt5 的互动。和 matplotlib高效的。

    1. 第一个例子

    这个例子基于这篇文章:
    https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html
    文章来自官方matplotlib网站,并解释了如何在 PyQt5 中嵌入 matplotlib 图 window 。

    我对示例代码做了一些小的调整,但基本原理仍然相同。请将以下代码复制粘贴到 Python 文件中并运行:

    #####################################################################################
    #                                                                                   #
    #                PLOT A LIVE GRAPH IN A PYQT WINDOW                                 #
    #                EXAMPLE 1                                                          #
    #               ------------------------------------                                #
    # This code is inspired on:                                                         #
    # https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html  #
    #                                                                                   #
    #####################################################################################
    
    from __future__ import annotations
    from typing import *
    import sys
    import os
    from matplotlib.backends.qt_compat import QtCore, QtWidgets
    # from PyQt5 import QtWidgets, QtCore
    from matplotlib.backends.backend_qt5agg import FigureCanvas
    # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    import matplotlib as mpl
    import numpy as np
    
    class ApplicationWindow(QtWidgets.QMainWindow):
        '''
        The PyQt5 main window.
    
        '''
        def __init__(self):
            super().__init__()
            # 1. Window settings
            self.setGeometry(300, 300, 800, 400)
            self.setWindowTitle("Matplotlib live plot in PyQt - example 1")
            self.frm = QtWidgets.QFrame(self)
            self.frm.setStyleSheet("QWidget { background-color: #eeeeec; }")
            self.lyt = QtWidgets.QVBoxLayout()
            self.frm.setLayout(self.lyt)
            self.setCentralWidget(self.frm)
    
            # 2. Place the matplotlib figure
            self.myFig = MyFigureCanvas(x_len=200, y_range=[0, 100], interval=20)
            self.lyt.addWidget(self.myFig)
    
            # 3. Show
            self.show()
            return
    
    class MyFigureCanvas(FigureCanvas):
        '''
        This is the FigureCanvas in which the live plot is drawn.
    
        '''
        def __init__(self, x_len:int, y_range:List, interval:int) -> None:
            '''
            :param x_len:       The nr of data points shown in one plot.
            :param y_range:     Range on y-axis.
            :param interval:    Get a new datapoint every .. milliseconds.
    
            '''
            super().__init__(mpl.figure.Figure())
            # Range settings
            self._x_len_ = x_len
            self._y_range_ = y_range
    
            # Store two lists _x_ and _y_
            self._x_ = list(range(0, x_len))
            self._y_ = [0] * x_len
    
            # Store a figure ax
            self._ax_ = self.figure.subplots()
    
            # Initiate the timer
            self._timer_ = self.new_timer(interval, [(self._update_canvas_, (), {})])
            self._timer_.start()
            return
    
        def _update_canvas_(self) -> None:
            '''
            This function gets called regularly by the timer.
    
            '''
            self._y_.append(round(get_next_datapoint(), 2))     # Add new datapoint
            self._y_ = self._y_[-self._x_len_:]                 # Truncate list _y_
            self._ax_.clear()                                   # Clear ax
            self._ax_.plot(self._x_, self._y_)                  # Plot y(x)
            self._ax_.set_ylim(ymin=self._y_range_[0], ymax=self._y_range_[1])
            self.draw()
            return
    
    # Data source
    # ------------
    n = np.linspace(0, 499, 500)
    d = 50 + 25 * (np.sin(n / 8.3)) + 10 * (np.sin(n / 7.5)) - 5 * (np.sin(n / 1.5))
    i = 0
    def get_next_datapoint():
        global i
        i += 1
        if i > 499:
            i = 0
        return d[i]
    
    if __name__ == "__main__":
        qapp = QtWidgets.QApplication(sys.argv)
        app = ApplicationWindow()
        qapp.exec_()
    
    

    您应该会看到以下窗口:

    enter image description here

    2. 第二个例子

    我发现了另一个活的例子 matplotlib图表在这里:
    https://learn.sparkfun.com/tutorials/graph-sensor-data-with-python-and-matplotlib/speeding-up-the-plot-animation
    但是,作者没有使用PyQt5嵌入他的现场情节。因此,我稍微修改了代码,以便在 PyQt5 中获得绘图。 window :

    #####################################################################################
    #                                                                                   #
    #                PLOT A LIVE GRAPH IN A PYQT WINDOW                                 #
    #                EXAMPLE 2                                                          #
    #               ------------------------------------                                #
    # This code is inspired on:                                                         #
    # https://learn.sparkfun.com/tutorials/graph-sensor-data-with-python-and-matplotlib/speeding-up-the-plot-animation  #
    #                                                                                   #
    #####################################################################################
    
    from __future__ import annotations
    from typing import *
    import sys
    import os
    from matplotlib.backends.qt_compat import QtCore, QtWidgets
    # from PyQt5 import QtWidgets, QtCore
    from matplotlib.backends.backend_qt5agg import FigureCanvas
    # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    import matplotlib as mpl
    import matplotlib.figure as mpl_fig
    import matplotlib.animation as anim
    import numpy as np
    
    class ApplicationWindow(QtWidgets.QMainWindow):
        '''
        The PyQt5 main window.
    
        '''
        def __init__(self):
            super().__init__()
            # 1. Window settings
            self.setGeometry(300, 300, 800, 400)
            self.setWindowTitle("Matplotlib live plot in PyQt - example 2")
            self.frm = QtWidgets.QFrame(self)
            self.frm.setStyleSheet("QWidget { background-color: #eeeeec; }")
            self.lyt = QtWidgets.QVBoxLayout()
            self.frm.setLayout(self.lyt)
            self.setCentralWidget(self.frm)
    
            # 2. Place the matplotlib figure
            self.myFig = MyFigureCanvas(x_len=200, y_range=[0, 100], interval=20)
            self.lyt.addWidget(self.myFig)
    
            # 3. Show
            self.show()
            return
    
    class MyFigureCanvas(FigureCanvas, anim.FuncAnimation):
        '''
        This is the FigureCanvas in which the live plot is drawn.
    
        '''
        def __init__(self, x_len:int, y_range:List, interval:int) -> None:
            '''
            :param x_len:       The nr of data points shown in one plot.
            :param y_range:     Range on y-axis.
            :param interval:    Get a new datapoint every .. milliseconds.
    
            '''
            FigureCanvas.__init__(self, mpl_fig.Figure())
            # Range settings
            self._x_len_ = x_len
            self._y_range_ = y_range
    
            # Store two lists _x_ and _y_
            x = list(range(0, x_len))
            y = [0] * x_len
    
            # Store a figure and ax
            self._ax_  = self.figure.subplots()
            self._ax_.set_ylim(ymin=self._y_range_[0], ymax=self._y_range_[1])
            self._line_, = self._ax_.plot(x, y)
    
            # Call superclass constructors
            anim.FuncAnimation.__init__(self, self.figure, self._update_canvas_, fargs=(y,), interval=interval, blit=True)
            return
    
        def _update_canvas_(self, i, y) -> None:
            '''
            This function gets called regularly by the timer.
    
            '''
            y.append(round(get_next_datapoint(), 2))     # Add new datapoint
            y = y[-self._x_len_:]                        # Truncate list _y_
            self._line_.set_ydata(y)
            return self._line_,
    
    # Data source
    # ------------
    n = np.linspace(0, 499, 500)
    d = 50 + 25 * (np.sin(n / 8.3)) + 10 * (np.sin(n / 7.5)) - 5 * (np.sin(n / 1.5))
    i = 0
    def get_next_datapoint():
        global i
        i += 1
        if i > 499:
            i = 0
        return d[i]
    
    if __name__ == "__main__":
        qapp = QtWidgets.QApplication(sys.argv)
        app = ApplicationWindow()
        qapp.exec_()
    
    

    生成的实时绘图完全相同。但是,如果您开始玩弄 interval来自 MyFigureCanvas() 的参数构造函数,您会注意到第一个示例将无法遵循。第二个例子可以更快。

    3. 问题

    我有几个问题想向您提出:
  • QtCoreQtWidgets类可以这样导入:from matplotlib.backends.qt_compat import QtCore, QtWidgets或者像这样:from PyQt5 import QtWidgets, QtCore两者都同样有效。是否有理由偏爱其中之一?
  • FigureCanvas可以这样导入:from matplotlib.backends.backend_qt5agg import FigureCanvas或者像这样:from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas但我已经知道为什么了。 backend_qt5agg文件似乎定义了 FigureCanvas作为 FigureCanvasQTAgg 的别名.
  • 为什么第二个例子比第一个例子快这么多?老实说,这让我感到惊讶。第一个示例基于来自官方 matplotlib 网站的网页。我希望那个更好。
  • 你有什么建议可以让第二个例子更快吗?


  • 4. 编辑

    基于网页:
    https://bastibe.de/2013-05-30-speeding-up-matplotlib.html
    我修改了第一个例子 以提高其速度。请看一下代码:

    #####################################################################################
    #                                                                                   #
    #                PLOT A LIVE GRAPH IN A PYQT WINDOW                                 #
    #                EXAMPLE 1 (modified for extra speed)                               #
    #               --------------------------------------                              #
    # This code is inspired on:                                                         #
    # https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html  #
    # and on:                                                                           #
    # https://bastibe.de/2013-05-30-speeding-up-matplotlib.html                         #
    #                                                                                   #
    #####################################################################################
    
    from __future__ import annotations
    from typing import *
    import sys
    import os
    from matplotlib.backends.qt_compat import QtCore, QtWidgets
    # from PyQt5 import QtWidgets, QtCore
    from matplotlib.backends.backend_qt5agg import FigureCanvas
    # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    import matplotlib as mpl
    import numpy as np
    
    class ApplicationWindow(QtWidgets.QMainWindow):
        '''
        The PyQt5 main window.
    
        '''
        def __init__(self):
            super().__init__()
            # 1. Window settings
            self.setGeometry(300, 300, 800, 400)
            self.setWindowTitle("Matplotlib live plot in PyQt - example 1 (modified for extra speed)")
            self.frm = QtWidgets.QFrame(self)
            self.frm.setStyleSheet("QWidget { background-color: #eeeeec; }")
            self.lyt = QtWidgets.QVBoxLayout()
            self.frm.setLayout(self.lyt)
            self.setCentralWidget(self.frm)
    
            # 2. Place the matplotlib figure
            self.myFig = MyFigureCanvas(x_len=200, y_range=[0, 100], interval=1)
            self.lyt.addWidget(self.myFig)
    
            # 3. Show
            self.show()
            return
    
    class MyFigureCanvas(FigureCanvas):
        '''
        This is the FigureCanvas in which the live plot is drawn.
    
        '''
        def __init__(self, x_len:int, y_range:List, interval:int) -> None:
            '''
            :param x_len:       The nr of data points shown in one plot.
            :param y_range:     Range on y-axis.
            :param interval:    Get a new datapoint every .. milliseconds.
    
            '''
            super().__init__(mpl.figure.Figure())
            # Range settings
            self._x_len_ = x_len
            self._y_range_ = y_range
    
            # Store two lists _x_ and _y_
            self._x_ = list(range(0, x_len))
            self._y_ = [0] * x_len
    
            # Store a figure ax
            self._ax_ = self.figure.subplots()
            self._ax_.set_ylim(ymin=self._y_range_[0], ymax=self._y_range_[1]) # added
            self._line_, = self._ax_.plot(self._x_, self._y_)                  # added
            self.draw()                                                        # added
    
            # Initiate the timer
            self._timer_ = self.new_timer(interval, [(self._update_canvas_, (), {})])
            self._timer_.start()
            return
    
        def _update_canvas_(self) -> None:
            '''
            This function gets called regularly by the timer.
    
            '''
            self._y_.append(round(get_next_datapoint(), 2))     # Add new datapoint
            self._y_ = self._y_[-self._x_len_:]                 # Truncate list y
    
            # Previous code
            # --------------
            # self._ax_.clear()                                   # Clear ax
            # self._ax_.plot(self._x_, self._y_)                  # Plot y(x)
            # self._ax_.set_ylim(ymin=self._y_range_[0], ymax=self._y_range_[1])
            # self.draw()
    
            # New code
            # ---------
            self._line_.set_ydata(self._y_)
            self._ax_.draw_artist(self._ax_.patch)
            self._ax_.draw_artist(self._line_)
            self.update()
            self.flush_events()
            return
    
    # Data source
    # ------------
    n = np.linspace(0, 499, 500)
    d = 50 + 25 * (np.sin(n / 8.3)) + 10 * (np.sin(n / 7.5)) - 5 * (np.sin(n / 1.5))
    i = 0
    def get_next_datapoint():
        global i
        i += 1
        if i > 499:
            i = 0
        return d[i]
    
    if __name__ == "__main__":
        qapp = QtWidgets.QApplication(sys.argv)
        app = ApplicationWindow()
        qapp.exec_()
    
    

    结果非常惊人。修改使 第一个例子 肯定要快得多!但是,我不知道这是否使 第一个例子 现在同样快到 第二个例子 .他们肯定彼此接近。谁知道谁赢了?

    另外,我注意到左侧的一条垂直线和顶部的一条水平线丢失了:
    enter image description here
    这没什么大不了的,但我只是想知道为什么。

    最佳答案

    第二种情况(使用 FuncAnimation)更快,因为 it uses "blitting" ,这避免了重绘在帧之间不会改变的东西。

    matplotlib 网站上提供的用于嵌入 qt 的示例没有考虑速度,因此性能较差。你会注意到它调用了 ax.clear()ax.plot()在每次迭代中,导致每次都重新绘制整个 Canvas 。如果您要使用与 FuncAnimation 相同的代码(也就是说,创建一个 Axes 和一个艺术家,并更新艺术家中的数据而不是每次都创建一个新的艺术家)你应该非常接近我相信的相同表现。

    关于python - 如何在 PyQt5 GUI 中制作快速的 matplotlib 实时绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57891219/

    相关文章:

    python - 尝试从Darksky API获取值列表时出错

    python - Matplotlib 箱线图 : what algorithm is used to calculate range and identify outliers?

    python - Matplotlib 箭袋量表

    python - 使用列表理解而不是函数

    python - Validation_data 和 Validation_split

    python - Pandas 数据帧索引 : KeyError:none of [columns] are in the [columns]

    python - 在 python 中复制 R/ggplot2 颜色

    python - 如何调试使用 kivy 和推土机制作的我的 android 应用程序(apk)?每次都会崩溃?

    python - 拆分 python 数据包?

    python - 计算两个值并计算Django中的百分比差异