I am designing an app that must plot a serial from a sensor through Arduino and Python. I am using matplotlib to animate my graph, and it works fine with the code that can be seen in a question I posted yesterday: Arduino Live Serial Plotting with a MatplotlibAnimation gets slow. Now, since I want to make a nice looking GUI, I want to embed my animation in PyQt5. For that I've taken as a reference this link https://pythonspot.com/en/pyqt5-matplotlib/ together with that one Getting blitting to work in funcAnimation embedded in PyQT4 GUI. My resulting code looks as follows:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, \
QPushButton
from PyQt5.QtGui import QIcon
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import serial
import time
class App(QMainWindow):
def __init__(self):
super().__init__()
self.left = 10
self.top = 10
self.title = 'PyQt5 matplotlib example - pythonspot.com'
self.width = 640
self.height = 400
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
m = PlotCanvas(self, width=5, height=4)
m.move(0, 0)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(500, 0)
button.resize(140, 100)
self.show()
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
global fig
fig = Figure(figsize=(width, height), dpi=dpi)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
#self.axes = fig.add_subplot(111)#, IYV: can be removed
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.plot()
self.animate()
def plot(self):
global xar, yar, optimal_frequency, ser, ax1
ser = serial.Serial("com3", 2400)
ser.readline()
optimal_frequency = 100
ax1 = self.figure.add_subplot(111)
xar = []
yar = []
print(time.ctime())
def animate(self):
self.anim = animation.FuncAnimation(fig, self.animate_loop(), interval=optimal_frequency)
self.draw()
def animate_loop(self):
global xar, yar
ser.readline()
for i in range(optimal_frequency):
a = str(ser.readline(), 'utf-8')
try:
b = float(a)
except ValueError:
ser.readline()
xar.append(str(time.time()))
print(time.ctime())
yar.append(int(b))
ax1.clear()
ax1.plot(xar, yar)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
But I get the error:
Traceback (most recent call last):
File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 113, in <module>
ex = App()
File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 35, in __init__
self.initUI()
File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 41, in initUI
m = PlotCanvas(self, width=5, height=4)
File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 71, in __init__
self.animate()
File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 87, in animate
self.draw()
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_qt5agg.py", line 159, in draw
FigureCanvasAgg.draw(self)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_agg.py", line 474, in draw
self.figure.draw(self.renderer)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\artist.py", line 62, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\figure.py", line 1165, in draw
self.canvas.draw_event(renderer)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backend_bases.py", line 1809, in draw_event
self.callbacks.process(s, event)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\cbook.py", line 563, in process
proxy(*args, **kwargs)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\cbook.py", line 430, in __call__
return mtd(*args, **kwargs)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 661, in _start
self._init_draw()
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 1221, in _init_draw
self._draw_frame(next(self.new_frame_seq()))
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 1243, in _draw_frame
self._drawn_artists = self._func(framedata, *self._args)
TypeError: 'NoneType' object is not callable
Exception ignored in: <bound method TimerQT.__del__ of <matplotlib.backends.backend_qt5.TimerQT object at 0x0000026C3260DD30>>
Traceback (most recent call last):
File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_qt5.py", line 201, in __del__
TypeError: 'method' object is not connected
Any help on how can I get this running? Cheers
As you can also see in the question you link to,
FuncAnimation
requires a method as its second argument. However in your call you provideNone
instead (sinceself.animate_loop()
evaluates toNone
). Change this toSecond, as can also be seen from the linked question,
self.animate_loop
needs to take an argument, so probably you would need to change this toApart from that there are some minor problems in your code, e.g. if
b = float(a)
fails,b
is undefined andyar.append(int(b))
will raise an error. Also usingglobal
inside classes seems very strange; it's not a problem, but makes the code hard to read. Better use class variables.Thanks a lot @ImportanceOfBeingErnest, that solved the problem. I also take your critic into consideration; I really need to gain a better insight into object-oriented programming.
The GUI though is slow-resposive: the window cannot easily be moved around or resized, and the button is hard to press. For this reason, I recommend discarding matplotlib for serial plotting, and use PyQtGraoh instead. The following code does the same, just with PyQtGraph/PyQt4 instead of Matplotlib/PyQt5. The code looks as:
I really wanted to use PyQt5 in order to being able to deply with PyQtDeploy, but I'll have to go all the way with PyInstaller, since this way of including PyQtGraph in PyQt5 seems pretty complicated for me : https://github.com/pyqtgraph/pyqtgraph/issues/33