Search code examples
matplotlibwhile-looplabellegendgpio

Legend not working for live data and while loop configuration


My code takes a continuously updating input from raspberry pi, which is then plotted onto a graph. I'm trying to use the legend to display the current frequency (most recent output of y_data) however I can't seem to get it to display. Placing plt.legend() just before plt.show() results in a display, however freezing of the graph. Any help would be greatly appreciated.

import matplotlib  
matplotlib.use('qt5agg')  
from matplotlib.figure import Figure  
import matplotlib.pyplot as plt  
import RPi.GPIO as GPIO  
import time  
import numpy as np  

x_data = []  
y_data = []

GPIO.setmode(GPIO.BCM)              
INPUT_PIN = 26                       
GPIO.setup(INPUT_PIN, GPIO.IN)

fig, ax = plt.subplots()  
line, = plt.plot([],[], 'k-',label = 'data', drawstyle = 'steps')  
avr, = plt.plot([],[], 'g--',label = 'mean')  
plt.show(block = False)  

def update(x_data, y_data, average):

    line.set_ydata(y_data)
    line.set_xdata(x_data)
    avr.set_xdata(x_data)
    avr.set_ydata([average]*len(x_data))
    fig.canvas.draw()
    ax.draw_artist(ax.patch)
    ax.draw_artist(line)
    ax.draw_artist(avr)
    ax.relim()
    ax.autoscale_view()
    data = round(y_data[-1], 1)
    ax.legend((line, avr), (data, 'mean'))
    fig.canvas.update()
    fig.canvas.flush_events()

while True:                             #Begin continuous loop
    NUM_CYCLES = 10                     #Loops to be averaged over
    start = time.time()
    for impulse_count in range(NUM_CYCLES):
        GPIO.wait_for_edge(INPUT_PIN, GPIO.FALLING)
    duration = time.time() - start      #seconds to run for loop

    frequency = NUM_CYCLES / duration   #Frequency in Hz
    bpm = (frequency/1000)*60           #Frequency / no. of cogs per breath * min

    x_data.append(time.time())  #add new data to data lists
    y_data.append(bpm)             

    average = sum(y_data)/float(len(y_data))
    update(x_data,y_data, average)       #call function to update graph contents

Solution

  • I think you should call fig.canvas.draw() at the end of the update function, not in the middle of it. I'm not sure why you add all the artists again in the update function, so you may leave that out. Concerning the legend, It's probably best to create it once at the beginning and inside the update function only update the relevant text.

    Commenting out all the GPIO stuff, this is a version which works fine for me:

    import matplotlib  
    #matplotlib.use('qt5agg')  
    from matplotlib.figure import Figure  
    import matplotlib.pyplot as plt  
    #import RPi.GPIO as GPIO  
    import time  
    import numpy as np  
    
    x_data = []  
    y_data = []
    
    #GPIO.setmode(GPIO.BCM)              
    #INPUT_PIN = 26                       
    #GPIO.setup(INPUT_PIN, GPIO.IN)
    
    fig, ax = plt.subplots()  
    line, = plt.plot([],[], 'k-',label = 'data', drawstyle = 'steps')  
    avr, = plt.plot([],[], 'g--',label = 'mean')
    # add legend already at the beginning
    legend = ax.legend((line, avr), (0.0, 'mean'))  
    plt.show(block = False)  
    
    def update(x_data, y_data, average):
    
        line.set_ydata(y_data)
        line.set_xdata(x_data)
        avr.set_xdata(x_data)
        avr.set_ydata([average]*len(x_data))
        #fig.canvas.draw()  <- use this at the end
        #ax.draw_artist(ax.patch) # useless?
        #ax.draw_artist(line)     # useless?
        #ax.draw_artist(avr)      # useless?
    
        ax.relim()
        ax.autoscale_view()
        data = round(y_data[-1], 1)
        # only update legend here
        legend.get_texts()[0].set_text(str(data))
    
        #fig.canvas.update() # <- what is this one needed for?
        fig.canvas.draw()
        fig.canvas.flush_events()
    
    
    while True:                             #Begin continuous loop
        NUM_CYCLES = 10                     #Loops to be averaged over
        start = time.time()
        #for impulse_count in range(NUM_CYCLES):
        #    GPIO.wait_for_edge(INPUT_PIN, GPIO.FALLING)
        a = np.random.rand(700,800) # <- just something that takes a little time
    
        duration = time.time() - start      #seconds to run for loop
    
        frequency = NUM_CYCLES / duration   #Frequency in Hz
        bpm = (frequency/1000)*60           #Frequency / no. of cogs per breath * min
    
        x_data.append(time.time())  #add new data to data lists
        y_data.append(bpm)             
    
        average = sum(y_data)/float(len(y_data))
        update(x_data,y_data, average)       #call function to update graph contents