This question actually builds further onto my question I posted last week. (How to partial fill_between in matplotlib, as in different colors for different values)
I'm trying to record the amount of data per second on a CANBus and plot this on a graph. I'm apologizing in advance for the lots of code, but the three methods I tried are somewhat similar.
This is the static situation, which works:
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
import sys
from collections import deque
import logging
import time
logging.basicConfig(level=logging.INFO)
buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]
bitthrough = deque(buffer_size*[0], buffer_size)
y = [107, 108, 105, 109, 107, 106, 107, 109, 106, 106, 94, 93, 94, 93, 93, 94, 95, 106, 108, 109, 107, 107, 106, 108, 105, 108, 107, 106, 107, 97, 93, 96, 94, 96, 95, 94, 104, 107, 106, 108, 107, 107, 106, 107, 105, 107, 108, 105, 107, 100, 93, 94, 93, 95, 104, 107, 107, 108, 108, 107, 107, 107, 107, 104, 94, 96, 95, 96, 94, 95, 94, 100, 107, 107, 105, 107, 107, 109, 107, 108, 107, 105, 108, 108, 106, 97, 94, 94, 94, 94, 95, 94, 94, 94, 96, 108, 108, 107, 106, 107, 107, 108, 107, 106, 95, 95, 95, 94, 94, 96, 105, 108, 107, 106, 106, 108, 107, 108, 106, 107]
bitthrough = y
fig, ax = plt.subplots()
ax.set_ylim(0, 120)
ax.set_xlim(0, 120)
ln, = plt.plot([], color='black')
plt.ion()
plt.show()
while True:
plt.pause(1)
ln.set_xdata(range(buffer_size))
ln.set_ydata(bitthrough)
wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
wh_red = [a > b for a, b, in zip(bitthrough, lvl)]
ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
ax.fill_between(t, 0, bitthrough, where=wh_orange, color='orange', interpolate=True)
ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
fig.canvas.draw_idle()
This results in the following graph: (which is fine for me, now)
But when I want to update the graph live using threads, things break. This is the code I have. canbus
is a module I made to monitor a CANBus. PCAN.throughput()
is a asyncio coroutine function that counts the number of messages in a second. After the second is passed, it returns with the amount of kilobytes it received on the bus. The terminal shows the content of bitthrough
, which gets filled in a seperate thread.
from canbus import PCAN
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
import sys
import asyncio
from collections import deque
import logging
import time
import threading
from matplotlib.figure import Figure
logging.basicConfig(level=logging.INFO)
buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]
loop = asyncio.get_event_loop()
cn = PCAN()
loop.run_until_complete(cn.poll_ids())
loop.run_until_complete(cn.get_names())
print(cn.names)
bitthrough = deque(buffer_size*[0], buffer_size)
def get_bt(loop):
asyncio.set_event_loop(loop)
while True:
task = loop.run_until_complete(cn.throughput())
bitthrough.append(task[0] / 33. * 100.)
print(bitthrough)
thread = threading.Thread(target=get_bt, args=(loop,))
thread.daemon = True
thread.start()
fig, ax = plt.subplots()
ax.set_ylim(0, 120)
ax.set_xlim(0, 120)
ln, = plt.plot([], color='black')
plt.ion()
plt.show()
while True:
plt.pause(1)
ln.set_xdata(range(buffer_size))
ln.set_ydata(bitthrough)
wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
wh_red = [a > b for a, b, in zip(bitthrough, lvl)]
ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
ax.fill_between(t, 0, bitthrough, where=wh_orange, color='orange', interpolate=True)
ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
fig.canvas.draw_idle()
This code results in the following:
Notice how the color red keeps on the highest level it was before.
I also tried to implement it with the Qt backend of matplotlib. However, I just got an empty graph that couldn't get updated. (Without threads this method worked, but it slogged down my computer leaving me unable to do something else)
from canbus import PCAN
# import numpy as np
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import sys
import asyncio
from collections import deque
import logging
import time
import threading
import datetime
from matplotlib.backends.qt_compat import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
logging.basicConfig(level=logging.INFO)
buffer_size = 120
lvl = buffer_size * [100]
llvl = buffer_size * [95]
t = [t for t in range(buffer_size)]
loop = asyncio.get_event_loop()
cn = PCAN()
loop.run_until_complete(cn.poll_ids())
loop.run_until_complete(cn.get_names())
print(cn.names)
bitthrough = deque(buffer_size*[0], buffer_size)
def get_bt(loop):
asyncio.set_event_loop(loop)
while True:
task = loop.run_until_complete(cn.throughput())
bitthrough.append(task[0] / 33. * 100.)
print(bitthrough)
thread = threading.Thread(target=get_bt, args=(loop,))
thread.daemon = True
thread.start()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
dynamic_canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(dynamic_canvas)
self._dynamic_ax = dynamic_canvas.figure.subplots()
self._update_canvas()
def _update_canvas(self):
# p = loop.run_until_complete(cn.throughput())[0] / 500. * 100.
# bitthrough.append(p)
wh_green = [a <= b for a,b in zip(bitthrough, llvl)]
wh_orange = [a > b and a <= c for a, b, c in zip(bitthrough, llvl, lvl)]
wh_red = [a > b for a, b, in zip(bitthrough, lvl)]
self._dynamic_ax.clear()
self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_red, color='red', interpolate=True)
self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_orange,color='orange', interpolate=True)
self._dynamic_ax.fill_between(t, 0, bitthrough, where=wh_green, color='green', interpolate=True)
self._dynamic_ax.plot(t, bitthrough, color="black")
self._dynamic_ax.set_ylim(0, 120)
logging.info("redrawing graph")
self._dynamic_ax.figure.canvas.draw()
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
while True:
app._update_canvas()
time.sleep(1)
Also not good, and I'm kinda lost right now.
I found the problem is my last solution (using Qt backend of matplotlib). I called
while True:
app._update_canvas()
time.sleep(1)
outside the Qt application. So the graph didn't get updated, hence, an empty graph. Moving app._update_canvas()
to inside the class and calling it periodically with a QTimer
plots the graph correctly.
Added to the __init__
function of ApplicationWindow
:
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self._update_canvas)
self.timer.start(1000)