0

I am on python 3.7. I am trying to read data from a serial port, it would be 7 different bytes. Then I would like to plot each different byte on a different subplot. I want to read the serial port every 500ms and each time I read add the new data to the subplots. Every read is giving one more data to plot on every subplot. That's basically sensor reading.

Here is the code I have written:

from time import sleep import serial import matplotlib.pyplot as 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

The figure is showing and the x axis and y axis are adapting to the value I want to plt but there is no data at all on the plot. If I use scatter instead of plot it works, but then it is less versatile and I can't draw te type of graph I want. I also try to reproduce the problem without using a serial data but just displaying points of a list one after the other like that:

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)

And it is doing exactly the same thing, the final image I have on the graph is that: enter image description here Which shows axis took what I wanted to plot but did not plot anything. The point is I do not want to clear the full plot and redraw everything because for the data sensor I will have about 30days of data to display in continuous. What am I doing wrong with the code I have written?

EDIT: After comment of ImportanceOfBeingErnest I have tried implementing the answer given here. The code is then:

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

But it still does not show anything. I kind of guess I am missing a plot and/or clear somewhere but after trying several options can't get it to work.

7
  • You plot a line plot of a single point. But no line can be established between a point and itself, rather lines need at least two points to be a line. Of course you can add a marker to the line (marker="o") to see the points (that would be the same as a scatter). But if you really want a line, you should plot all the points. Commented Feb 6, 2019 at 15:55
  • Oh I see I understand better. Is there no way to do a line from the previous point displayed before then? Do you have to replot everything? Commented Feb 6, 2019 at 16:03
  • Well, usually you would update the line with more and more points. See e.g. this question on how that can be done. Commented Feb 6, 2019 at 16:07
  • I tried the example given in the question you linked but it still won't display anything, I probably miss a plot and or clear at some point but struggling knowing where and what to put in the plot Commented Feb 6, 2019 at 17:20
  • 1
    A side remark - since plotting could take an unpredictable amount of time, it might be a good idea to use the multiprocessing module and read the data in a separate process. You can then pipe collected data to the plotting process. Commented Jul 5, 2019 at 16:36

1 Answer 1

2

As someone who worked in an optics lab and struggled to get Matplotlib to perform real-time plotting, I feel your pain and I strongly suggest choosing something other than Matplotlib for this purpose (such as pyqtgraph).

That said, I've gotten Matplotlib to perform some real-time plotting from sensor data. I've found it to be buggy. Here are some thoughts as well as a solution that uses matplotlib:

Use dictionaries where possible. Why? Because accessing dictionaries is fast, and I find that a dictionary key is easier to use than a list index for these purposes.

Use lists instead of NumPy arrays. Why? Because every time you resize or append a NumPy array it must be completely rewritten as a new object in memory. This is very costly. Lists can be resized and appended for negligible cost.

The code below uses random data to simulate incoming sensor data and to make troubleshooting easier.

1. Imports

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

2. Setup your matplotlib objects and data containers

# 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. Write functions for update the plot and for updating your data containers

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. Finally, run the loop

#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

I hope that helps.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.