1

I have just started learning python to plot realtime gragh. I have tried solutions provided on stackoverflow but none of them are working. Below is my code and it isn't woorking. Please help

import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation

%matplotlib notebook

binSize = 512
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
def animate(i):
    try:
        while True:
            x, y = pg.position()
            f.append(x)
    except KeyboardInterrupt:
           print('') 
#     f.append(15) 
    if len(f)<binSize :
        plt.cla()
        plt.plot(f, color='c',LineWidth=1.5,label="Noisy") 
    else:
        plt.cla()
        plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Noisy")
ani = FuncAnimation(plt.gcf(),animate,interval=1);

So I have updated the code and trying to draw two subplots but after sometime

  1. Upper graph stopped clearing the canvas (Mouse X coordinates)
  2. Lower graph stopped updating the plot (FFT)
  3. When data grows beyond the binSize, notebook freezes and plots update really slowly


    %matplotlib notebook
    
    binSize = 256
    # fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
    f = []
    t = 0
    dt = 1
    fig,axs = plt.subplots(2,1) 
    
    def animate(i):
        x, y = pg.position() 
        f.append(x) 
        n = len(f)
        if n<binSize : 
            plt.sca(axs[0])
            plt.cla()
            plt.plot(f, color='c',LineWidth=1.5,label="MOUSE") 
        else:
            fhat = np.fft.fft(f,binSize)
            PSD = fhat*np.conj(fhat)/binSize
            freq  = (1/(dt*binSize))*np.arange(binSize)
            L = np.arange(1,np.floor(binSize/2),dtype='int')
            
            # update the code third time
      
               
            axs[0].clear() 
            axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE") 
#           axs[0].xlim(0,binSize) # this stopped the FFT graph to be plotted
        
#           plt.cla()
            axs[1].clear()  
            axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")  
    #         plt.xlim(t[0],t[-1])
    #         plt.legend()
    
    #         plt.sca(axs[1])
    #         plt.plot(freq[L],PSD[L],color='c',LineWidth=2,label="Mouse FFT") 
    #         plt.xlim(0,300)
    #         plt.legend()
    #         plt.cla()
    #         plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Mouse")
            
    ani = FuncAnimation(plt.gcf(),animate,interval=dt)  
Greyfrog
  • 926
  • 1
  • 8
  • 19
  • 1
    if you use FuncAnimation then you shouldn't use while loop. `animate()` should generate data/plots for one frame and `FuncAnimation will run it many times to get frames and it will display them. – furas Jun 29 '20 at 03:24
  • what means "isn't working" ? Do you get error message ? always put full error message (starting at word "Traceback") in question (not comment) as text (not screenshot). There are other useful information. Do you get wrong animation? Describe what you get and what you expected. – furas Jun 29 '20 at 04:40
  • @furas thank you for your reply. I guess you were right about not using while loop so I did removed it and it worked. Though when I tried to plot two sub plots I encountered some problems which I have updated in the question. Please check. Although, there was no error log which I can post here... – Greyfrog Jun 29 '20 at 23:20
  • when you have to much data then it may slowdown. You should remove some data when you add new one. BTW: `L = np.arange(...)` always give the same list so you could calculate it only once - outside `animate()`. OR maybe you shoud simply use `freq[:(binSize//2)], PSD[:(binSize//2)]` – furas Jun 30 '20 at 00:48

2 Answers2

2

To make it faster you may reduce data like in other answer

f.pop(0)

I use also different method to update plot which works much faster on my computer.

I create empty plots at start

# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2,   label="FFT")

and later only update data in plots without clear() and plot() again

        xdata = range(len(f))
        ydata = f
        p1.set_data(xdata, ydata)

and

        # replace data in plot
        xdata = range(binSize)
        ydata = f[-binSize:]
        p1.set_data(xdata, ydata)
        #p1.set_xdata(xdata)
        #p1.set_ydata(ydata)

        # replace data in plot
        xdata = freq[:(binSize//2)]
        ydata = PSD[:(binSize//2)]
        p2.set_data(xdata, ydata)

It needs only to run code which rescale plot

    # rescale view
    axs[0].relim()
    axs[0].autoscale_view(True,True,True)
    axs[1].relim()
    axs[1].autoscale_view(True,True,True)

animate() has to also return new plots

    # return plots
    return p1, p2

And FuncAnimation() has to blit them

ani = FuncAnimation(..., blit=True)

EDIT:

Animation works much, much faster also because I run it normally python script.py, not in Jupuyter Notebook

EDIT:

when I run normally I found one problem which I could find solution: it doesn't update values/ticks on axes. Jupyter Notebook doesn't have this problem.


import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation

%matplotlib notebook

binSize = 256

f = []
t = 0
dt = 1
fig, axs = plt.subplots(2, 1) 

# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2,   label="FFT")

freq = np.arange(binSize)/(dt*binSize)

def animate(i):
    x, y = pg.position() 
    n = len(f)

    if n < binSize :  
        f.append(x)

        # replace data in plot        
        xdata = range(len(f))
        ydata = f
        p1.set_data(xdata, ydata)
        #p1.set_xdata(xdata)
        #p1.set_ydata(ydata)
    else:
        f.pop(0)
        f.append(x)
        
        fhat = np.fft.fft(f, binSize)
        PSD  = fhat * np.conj(fhat) / binSize
        
        # replace data in plot
        #xdata = range(binSize)
        ydata = f[-binSize:]
        #p1.set_data(xdata, ydata)
        #p1.set_xdata(xdata)
        p1.set_ydata(ydata)

        # replace data in plot
        xdata = freq[:(binSize//2)]
        ydata = PSD[:(binSize//2)]
        p2.set_data(xdata, ydata)

    # rescale view
    axs[0].relim()
    axs[0].autoscale_view(True,True,True)
    axs[1].relim()
    axs[1].autoscale_view(True,True,True)
        
    # return plots
    return p1, p2

ani = FuncAnimation(plt.gcf(), animate, interval=dt, blit=True)

plt.show()
furas
  • 134,197
  • 12
  • 106
  • 148
  • it is running without error but no plot is drawn on the canvas – Greyfrog Jun 30 '20 at 02:07
  • sorry, I didn't test it in Jupyter Notebook but noramlly `python script.py` – furas Jun 30 '20 at 02:21
  • you has to add `%matplotlib notebook` to see animation in Jupyter Notebook - but it works much, much slower then when I run it normally `python script.py` – furas Jun 30 '20 at 02:32
  • BTW: I moved `freq = (1/(dt*binSize))*np.arange(binSize)` outside `animate()` because it never change so it can be calculate only once. – furas Jun 30 '20 at 02:52
  • It would have been great if you can add some explanation to your changes. Thank you. – Greyfrog Jun 30 '20 at 16:08
  • I already described it in answer. I create empty plot at start `p1, = ...plot(...)`, and in `animate()` I replace `x,y` using `p1.set_data(x, y)`, and I don't use `clear()` and `plot()` to plot/display it at once but I use `return p1, p2` so `FuncAnimation(..., blit=True)` gets it and plot/blit it using own optimized function (instead of using standard `plot()`). This method need only `.relim()` to calculate x_min, x_max, y_min, y_max which is used by `autoscale_view()` to rescale plot (standard `plot()` does it automatically). – furas Jun 30 '20 at 18:43
1

You should try this. Instead of clearing the plt clear axs[0] and so on. Also, instead of plotting on plt.plot, plot on axs[0].plot

%matplotlib notebook

binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1) 
plt.sca(axs[0]) 
plt.sca(axs[1])

def animate(i):
    x, y = pg.position() 
    n = len(f)
    if n<binSize :  
        f.append(x*100) 
        axs[0].clear() 
        axs[0].plot(f, color='c',LineWidth=1.5,label="MOUSE") 
    else:
        f.pop(0)
        f.append(x) 
        fhat = np.fft.fft(f,binSize)
        PSD = fhat*np.conj(fhat)/binSize
        freq  = (1/(dt*binSize))*np.arange(binSize)
        L = np.arange(1,np.floor(binSize/2),dtype='int') # index array of  [1,2,3..... binsize/2] type int
        
        axs[0].clear() 
        axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")  
        
        axs[1].clear()  
        axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")   
        
ani = FuncAnimation(plt.gcf(),animate,interval=dt) 
plt.show()
B L Λ C K
  • 600
  • 1
  • 8
  • 24