How to make a fast matplotlib live plot in a PyQt5 GUI(如何在 PyQt5 GUI 中制作快速 matplotlib 实时绘图)
问题描述
几年前,我已经尝试在 PyQt5 GUI 中嵌入实时 matplotlib 图.实时图显示了从传感器捕获的实时数据流、一些过程……我得到了它的工作,您可以在此处阅读相关帖子:
- 2.第二个例子- 我在这里找到了另一个实时 - matplotlib图示例:
 这没什么大不了的,但我只是想知道为什么.解决方案- 第二种情况(使用 - FuncAnimation)更快,因为它使用blitting",这可以避免重绘帧之间不会改变的东西.- matplotlib 网站上提供的嵌入 qt 的示例没有考虑速度,因此性能较差.您会注意到它在每次迭代时调用 - ax.clear()和- ax.plot(),导致每次都重新绘制整个画布.如果要使用与- FuncAnimation中的代码相同的代码(也就是说,创建一个 Axes 和一个艺术家,并更新艺术家中的数据,而不是每次都创建一个新艺术家)你应该得到非常接近我相信的相同性能.- Some years ago, I already experimented with embedding live - matplotlibplots in a- PyQt5GUI. Live plots show a data-stream real-time, captured from a sensor, some process, ... I got that working, and you can read the related posts here:- Matplotlib animation inside your own GUI 
- How do I plot in real-time in a while loop using matplotlib? 
 - Now I need to do the same thing again. I remember my previous approach worked, but couldn't keep up with fast datastreams. I found a couple of example codes on the internet, that I'd like to present to you. One of them is clearly faster than the other, but I don't know why. I'd like to gain more insights. I believe a deeper understanding will enable me to keep my interactions with - PyQt5and- matplotlibefficient.- 1. First example- This example is based on this article: 
 https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html
 The article is from the official- matplotlibwebsite, and explains how to embed a matplotlib figure in a- PyQt5window.- I did a few minor adjustments to the example code, but the basics are still the same. Please copy-paste the code below to a Python file and run it: - ##################################################################################### # # # 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_()- You should see the following window: - 2. Second example- I found another example of live - matplotlibgraphs here:
 https://learn.sparkfun.com/tutorials/graph-sensor-data-with-python-and-matplotlib/speeding-up-the-plot-animation
 However, the author doesn't use- PyQt5to embed his live plot. Therefore, I've modified the code a bit, to get the plot in a- PyQt5window:- ##################################################################################### # # # 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_()- The resulting live plot is exactly the same. However, if you start playing around with the - intervalparameter from the- MyFigureCanvas()constructor, you will notice that the first example won't be able to follow. The second example can go much faster.- 3. Questions- I've got a couple of questions I'd like to present to you: - The - QtCoreand- QtWidgetsclasses can be imported like this:
 - from matplotlib.backends.qt_compat import QtCore, QtWidgets
 or like this:
 - from PyQt5 import QtWidgets, QtCore
 Both work equally well. Is there a reason to prefer one over the other?
 
- The - FigureCanvascan be imported like this:
 - from matplotlib.backends.backend_qt5agg import FigureCanvas
 or like this:- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 But I already figured out why. The- backend_qt5aggfile seems to define- FigureCanvasas an alias for- FigureCanvasQTAgg.
 
- Why exactly is the second example so much faster than the first one? Honestly, it surprises me. The first example is based on a webpage from the official matplotlib website. I'd expect that one to be better. 
 
- Do you have any suggestions to make the second example even faster? 
 - 4. Edits- Based on the webpage: 
 https://bastibe.de/2013-05-30-speeding-up-matplotlib.html
 I modified the first example to increase its speed. Please have a look at the code:- ##################################################################################### # # # 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_()- The result is pretty amazing. The modifications make the first example definitely much faster! However, I don't know if this makes the first example equally fast now to the second example. They're certainly close to each other. Anyone an idea who wins? - Also, I noticed that one vertical line on the left, and one horizontal line on top is missing: It's not a big deal, but I just wonder why. 解决方案- The second case (using - FuncAnimation) is faster because it uses "blitting", which avoids redrawing things that do not change between frames.- The example provided on the matplotlib website for embedding in qt was not written with speed in mind, hence the poorer performance. You'll notice that it calls - ax.clear()and- ax.plot()at each iteration, causing the whole canvas to be redrawn everytime. If you were to use the same code as in the code with- FuncAnimation(that is to say, create an Axes and an artist, and update the data in the artist instead of creating a new artists every time) you should get pretty close to the same performance I believe.- 这篇关于如何在 PyQt5 GUI 中制作快速 matplotlib 实时绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网! 
本文标题为:如何在 PyQt5 GUI 中制作快速 matplotlib 实时绘图
 
				
         
 
            
        基础教程推荐
- 使用大型矩阵时禁止 Pycharm 输出中的自动换行符 2022-01-01
- 求两个直方图的卷积 2022-01-01
- 包装空间模型 2022-01-01
- 在同一图形上绘制Bokeh的烛台和音量条 2022-01-01
- PANDA VALUE_COUNTS包含GROUP BY之前的所有值 2022-01-01
- Plotly:如何设置绘图图形的样式,使其不显示缺失日期的间隙? 2022-01-01
- 无法导入 Pytorch [WinError 126] 找不到指定的模块 2022-01-01
- 修改列表中的数据帧不起作用 2022-01-01
- 在Python中从Azure BLOB存储中读取文件 2022-01-01
- PermissionError: pip 从 8.1.1 升级到 8.1.2 2022-01-01
 
    	 
    	 
    	 
    	 
    	 
    	 
    	 
    	 
				 
				 
				 
				