使用线程时在 matplotlib 中错误 fill_between

Wrongly fill_between in matplotlib when using threads

这个问题实际上是基于我上周发布的问题。 ()

我正在尝试记录 CANBus 上每秒的数据量并将其绘制在图表上。对于大量代码,我提前表示歉意,但我尝试的三种方法有些相似。

这是静态情况,有效:

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()

结果如下图:(现在对我来说很好)

但是当我想使用线程实时更新图形时,事情就坏了。这是我的代码。 canbus是我做的一个监控CANBus的模块。 PCAN.throughput()是一个asyncio协程函数,统计一秒内的消息数。第二次通过后,它 returns 加上它在总线上接收到的千字节数。终端显示 bitthrough 的内容,该内容在单独的线程中填充。

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()

此代码产生以下结果:

注意红色如何保持之前的最高水平。

我也试过用matplotlib的Qt后端来实现。但是,我只是得到一个无法更新的空图。 (在没有线程的情况下,此方法有效,但它拖累了我的计算机,使我无法做其他事情)

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)

这给了我这个:

也不好,我现在有点迷路了。

我发现问题是我最后的解决方案(使用 matplotlib 的 Qt 后端)。我打电话

while True:
    app._update_canvas()
    time.sleep(1)

在 Qt 应用程序之外。所以图表没有得到更新,因此是一个空图表。将 app._update_canvas() 移动到 class 内部并使用 QTimer 定期调用它可以正确绘制图形。

添加到ApplicationWindow__init__函数中:

self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self._update_canvas)
self.timer.start(1000)