[英]How to make a fast matplotlib live plot in a PyQt5 GUI
Some years ago, I already experimented with embedding live matplotlib
plots in a PyQt5
GUI.几年前,我已经尝试在
PyQt5
GUI 中嵌入实时matplotlib
图。 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 您自己的 GUI 中的 Matplotlib 动画
How do I plot in real-time in a while loop using matplotlib? 如何使用 matplotlib 在 while 循环中实时绘图?
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
PyQt5
and matplotlib
efficient.我相信更深入的理解将使我能够保持与
PyQt5
和matplotlib
交互高效。
This example is based on this article:这个例子基于这篇文章:
https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html
The article is from the official matplotlib
website, and explains how to embed a matplotlib figure in a PyQt5
window.文章来自
matplotlib
官方网站,解释了如何在PyQt5
窗口中嵌入matplotlib图形。
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:
请将下面的代码复制粘贴到 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_()
You should see the following window:您应该看到以下窗口:
I found another example of live matplotlib
graphs here:我在这里找到了另一个实时
matplotlib
图的例子:
https://learn.sparkfun.com/tutorials/graph-sensor-data-with-python-and-matplotlib/speeding-up-the-plot-animation https://learn.sparkfun.com/tutorials/graph-sensor-data-with-python-and-matplotlib/speeding-up-the-plot-animation
However, the author doesn't use PyQt5
to embed his live plot.然而,作者并没有使用
PyQt5
来嵌入他的现场情节。 Therefore, I've modified the code a bit, to get the plot in a PyQt5
window:因此,我稍微修改了代码,以在
PyQt5
窗口中获取绘图:
#####################################################################################
# #
# 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
interval
parameter from the MyFigureCanvas()
constructor, you will notice that the first example won't be able to follow.但是,如果您开始使用
MyFigureCanvas()
构造函数中的interval
参数,您会注意到第一个示例将无法遵循。 The second example can go much faster.第二个例子可以更快。
I've got a couple of questions I'd like to present to you:我有几个问题想向您提出:
The QtCore
and QtWidgets
classes can be imported like this: QtCore
和QtWidgets
类可以这样导入:
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 FigureCanvas
can be imported like this: FigureCanvas
可以这样导入:
from matplotlib.backends.backend_qt5agg import FigureCanvas
or like this: from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
或者像这样:
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
But I already figured out why.但我已经知道为什么了。 The
backend_qt5agg
file seems to define FigureCanvas
as an alias for FigureCanvasQTAgg
.该
backend_qt5agg
文件似乎定义FigureCanvas
为的别名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.
第一个示例基于来自官方 matplotlib 网站的网页。 I'd expect that one to be better.
我希望那个更好。
Do you have any suggestions to make the second example even faster?你有什么建议可以让第二个例子更快吗?
Based on the webpage:基于网页:
https://bastibe.de/2013-05-30-speeding-up-matplotlib.html 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.第二种情况(使用
FuncAnimation
)更快,因为它使用 "blitting" ,这避免了重绘在帧之间不会改变的东西。
The example provided on the matplotlib website for embedding in qt was not written with speed in mind, hence the poorer performance. matplotlib 网站上提供的嵌入 qt 的示例没有考虑速度,因此性能较差。 You'll notice that it calls
ax.clear()
and ax.plot()
at each iteration, causing the whole canvas to be redrawn everytime.您会注意到它在每次迭代时调用
ax.clear()
和ax.plot()
,导致每次都重新绘制整个画布。 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.如果您使用与
FuncAnimation
的代码相同的代码(也就是说,创建一个 Axes 和一个艺术家,并更新艺术家中的数据而不是每次都创建一个新的艺术家),您应该非常接近我相信同样的表现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.