Matplotlib“实时”在python中绘图

Posted

技术标签:

【中文标题】Matplotlib“实时”在python中绘图【英文标题】:Matplotlib "real time" plotting in python 【发布时间】:2019-06-30 15:16:51 【问题描述】:

我在 python 3.7 上。我正在尝试从串行端口读取数据,它将是 7 个不同的字节。然后我想在不同的子图上绘制每个不同的字节。我想每 500 毫秒读取一次串行端口,每次读取时都将新数据添加到子图中。每次读取都会在每个子图上绘制更多数据。这基本上是传感器读数。

这是我写的代码:

从时间导入睡眠 导入序列号 将 matplotlib.pyplot 导入为 plt

f=plt.figure(1)
ax=[0 for x in range(7)]
for i in range(0,7):
    ax[i]=f.add_subplot(4,2,1+i)

ser = serial.Serial('COM3', 115200) # Establish the connection on a specific port
counter = 0 
byte=ser.readline() #first line not to be plotted
while True:
    counter +=1
    ser.write(b'9') # send a command to the arduino
    byte=ser.read(7) #read 7 bytes back
    for i in range(0,7):
        ax[i].plot(counter, byte[i]) # Trying to plot the new values to each different subplots
    plt.pause(0.01)
    sleep(.5) # Delay for one half of a second

该图正在显示,x 轴和 y 轴正在适应我想要 plt 的值,但图上根本没有数据。如果我使用 scatter 而不是 plot 它可以工作,但是它的通用性较差,我无法绘制我想要的图形类型。 我也尝试在不使用串行数据的情况下重现该问题,而只是像这样一个接一个地显示列表的点:

import matplotlib.pyplot as plt
from time import sleep

f=plt.figure()
series=[[4,3,2,1],[8,7,6,5],[12,11,10,9]]
counter=0
ax=[0 for x in range(7)]

for i in range(0,3):
    ax[i]=f.add_subplot(4,2,1+i)


for j in range (0,4):
    counter=counter+1
    for i in range(0,3):
        ax[i].plot(counter,series[i][j])
    plt.pause(0.01)
    sleep(1)

它在做同样的事情,我在图表上的最终图像是: 哪个显示轴采用了我想要绘制的内容,但没有绘制任何内容。 关键是我不想清除整个绘图并重绘所有内容,因为对于数据传感器,我将有大约 30 天的数据连续显示。 我写的代码有什么问题?

编辑: 在 ImportanceOfBeingErnest 发表评论后,我尝试实施给出的答案here。那么代码是:

from time import sleep
import serial
import matplotlib.pyplot as plt
import numpy

plt.ion()
f=plt.figure()
ax=[0 for x in range(7)]
lines=[0 for x in range(7)]
for i in range(0,7):
    ax[i]=f.add_subplot(4,2,1+i)
    lines[i]=ax[0].plot([],[])


def update_line(hl, new_datax, new_datay):
    hl.set_xdata(numpy.append(hl.get_xdata(), new_datax))
    hl.set_ydata(numpy.append(hl.get_ydata(), new_datay))
    plt.draw()

ser = serial.Serial('COM3', 115200) # Establish the connection on a specific port
counter = 0 
byte=ser.readline() #first line not to be plotted
while True:
    counter +=1
    ser.write(b'9') # send a command to the arduino
    byte=ser.read(7) #read 7 bytes back
    for i in range(0,7):
        update_line(lines[i][0], counter, byte[i]) # Trying to plot the new values to each different subplots
    plt.pause(0.01)
    sleep(.5) # Delay for one half of a second

但它仍然没有显示任何内容。我有点猜想我错过了一个情节和/或在某处清除,但在尝试了几个选项后无法让它发挥作用。

【问题讨论】:

你绘制一个单点的线图。但是在一个点和它自己之间不能建立一条线,而是线需要至少两个点才能成为一条线。当然,您可以在线条上添加一个标记 (marker="o") 以查看点(这与散点图相同)。但如果你真的想要一条线,你应该画出所有的点。 哦,我明白了。有没有办法从之前显示的上一点做一条线?您必须重新绘制所有内容吗? 嗯,通常你会用越来越多的点来更新行。参见例如this question 如何做到这一点。 我尝试了您链接的问题中给出的示例,但它仍然不会显示任何内容,我可能会错过一个情节,或者在某些时候清楚,但很难知道在情节中放置的位置和内容 附注 - 由于绘图可能会花费不可预测的时间,因此使用 multiprocessing 模块并在单独的进程中读取数据可能是个好主意。然后,您可以将收集的数据通过管道传输到绘图过程。 【参考方案1】:

作为一个在光学实验室工作并努力让 Matplotlib 执行实时绘图的人,我感到你的痛苦我强烈建议为此目的选择 Matplotlib 以外的其他东西(例如 @ 987654321@)。

也就是说,我已经让 Matplotlib 从传感器数据中执行一些实时绘图。我发现它有问题。 这里有一些想法以及使用matplotlib的解决方案:

尽可能使用字典。 为什么?因为访问字典很快,而且我发现字典键比列表索引更容易用于这些目的。

使用列表而不是 NumPy 数组。 为什么?因为每次调整或追加 NumPy 数组时,都必须将其完全重写为内存中的新对象。这是非常昂贵的。列表可以调整大小和附加,成本可以忽略不计。

下面的代码使用随机数据来模拟传入的传感器数据并使故障排除更加容易。

1.进口

from time import sleep
import matplotlib.pyplot as plt
import numpy as np
#import serial

2。设置你的 matplotlib 对象和数据容器

# specify how many points to show on the x-axis
xwidth = 10

# use real-time plotting
plt.ion()

# setup each of the subplots
ax = []
fig, ax[0:7] = plt.subplots(7, 1, sharex=False, sharey=False)

# set up each of the lines/curves to be plotted on their respective subplots
lines = index: Axes_object.plot([],[])[0] for index, Axes_object in enumerate(ax)

# cache background of each plot for fast re-drawing, AKA Blit
ax_bgs = index: fig.canvas.copy_from_bbox(Axes_object.bbox) 
          for index, Axes_object in enumerate(ax)

# initial drawing of the canvas
fig.canvas.draw()

# setup variable to contain incoming serial port data
y_data = index:[0] for index in range(len(ax))
x_data = [-1]

3.编写更新绘图和更新数据容器的函数

def update_data(new_byte, ):
    x_data.append(x_data[-1] + 1)
    for i, val in enumerate(new_byte): 
        y_data[i].append(val)

def update_graph():
    for i in y_data.keys():
        # update each line object
        lines[i].set_data(x_data, y_data[i])

        # try to set new axes limits
        try:
            ax[i].set_xlim([x_data[-1] - xwidth, x_data[-1]])
            if max(y_data[i][-xwidth:]) > ax[i].get_ylim()[1]:
                new_min = min(y_data[i][-xwidth:])
                new_max = max(y_data[i][-xwidth:])
                ax[i].set_ylim([new_min-abs(new_min)*0.2, new_max+abs(new_max)*0.2])
        except:
            continue

    fig.canvas.draw()

4.最后,运行循环

#ser = serial.Serial('COM3', 115200) # Establish the connection on a specific port
#byte=ser.readline() #first line not to be plotted


while x_data[-1] < 30:
    # ser.write(b'9') # send a command to the arduino
    # byte=ser.read(7) #read 7 bytes back
    byte = np.random.rand(7)

    update_data(byte)
    update_graph()

    sleep(.1) # Delay for an arbitrary amount of time

希望对你有帮助。

【讨论】:

以上是关于Matplotlib“实时”在python中绘图的主要内容,如果未能解决你的问题,请参考以下文章

基于Python实现matplotlib中动态更新图片(交互式绘图)

将数据从 C 缓冲区传输到 Python,以便在 Visual Studio 2019 中使用 Matplotlib 进行绘图

matplotlib绘图学习

『Python』Numpy学习指南第九章_使用Matplotlib绘图

如何使用 matplotlib 在 while 循环中实时绘图?

使用实时相机预览更新 matplotlib 中的帧