0

I would like to update my matplotlibplot with values calculated in each iteration of a for loop. The idea is that I can see in real time which values are calculated and watch the progress iteration by iteration as my script is running. I do not want to first iterate through the loop, store the values and then perform the plot.

Some sample code is here:

from itertools import count
import random

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt


def animate(i, x_vals, y_vals):
    plt.cla()
    plt.plot(x_vals, y_vals)

if __name__ == "__main__":
    x_vals = []
    y_vals = []
    fig = plt.figure()
    index = count()

    for i in range(10):
        print(i)
        x_vals.append(next(index))
        y_vals.append(random.randint(0, 10))
        ani = FuncAnimation(fig, animate, fargs=(x_vals, y_vals))
        plt.show()

Most of the examples I have seen online, deal with the case where everything for the animation is global variables, which I would like to avoid. When I use a debugger to step through my code line by line, the figure does appear and it is animated. When I just run the script without the debugger, the figure displays but nothing is plot and I can see that my loop doesn't progress past the first iteration, first waiting for the figure window to be closed and then continuing.

Aesir
  • 2,033
  • 1
  • 28
  • 39
  • 1
    You shouldn't be using `plt.show()` inside of a loop. You want to show the window only once after all. – dumbPotato21 Dec 21 '21 at 15:14
  • Where should I put the call to plt.show()? If I put it outside of the for loop, then it will only display the figure window after the for loop has finished executing. Conversely if I place the show() command before the for loop it will block execution until the figure window is closed. With both of these options the plot isn't available to see during the for loop execution, am I missing something here to make it work correctly? – Aesir Dec 21 '21 at 15:18
  • use plt.show() outside the loop, clear your axes at each iteration of the loop and redraw it with the new values from inseide your loop, dont use animate. – pippo1980 Dec 22 '21 at 11:28

3 Answers3

5

You should never be using a loop when animating in matplotlib.

The animate function gets called automatically based on your interval.

Something like this should work

def animate(i, x=[], y=[]):
    plt.cla()
    x.append(i)
    y.append(random.randint(0, 10))
    plt.plot(x, y)


if __name__ == "__main__":
    fig = plt.figure()
    ani = FuncAnimation(fig, animate, interval=700)
    plt.show()
dumbPotato21
  • 5,669
  • 5
  • 21
  • 34
  • 2
    Thanks, this looks helpful but I am confused because now the for loop is gone (and controlled by the animate function). My loop is where the main work happens in the program and many things are calculated. With this approach it looks like I would have to put all of my inner-loop logic into this animate function, which I think should rather just be used for the actual visualisation. Is there a good way to separate and combine these things? Or is it considered ok, to dump everything into animate functions? What would be the best practice here? – Aesir Dec 21 '21 at 16:01
  • 1
    You could create an object and mutate its state in another function(which does the actual work). Then pass that object to the `animate` function. Again, that's just what I would do. I suppose creating another function and just calling it inside of `animate` would work as well. – dumbPotato21 Dec 21 '21 at 16:08
  • @dumbPotato21 could you post an example of "pass that object to the animate function." I tried it and wasnt able to make it work passing it as argument (fargs) – pippo1980 Dec 24 '21 at 08:26
  • @dumbPotato21 got it from https://stackoverflow.com/questions/38531076/how-is-the-function-argument-defined-in-matplotlib-animation I was passing fargs instead of frames – pippo1980 Dec 24 '21 at 08:36
  • @pippo1980 I mean this example is contrived and I doubt has any real world significance but [here](https://gist.github.com/TheTrio/8b3a46278c432dc45635e5b994887be4) it is. However, if you're doing this in an actual application, its more likely that you're getting your data from the internet - not generating random numbers. In that case, you will have to run 2 threads - one to output the graph, and the second to update the data which has to be plotted. The need for two threads would've been eliminated had matplotlib supported async, but afaik it doesn't. – dumbPotato21 Dec 24 '21 at 15:17
  • thanks, tried about threads here: https://stackoverflow.com/questions/70298863/how-to-run-animation-in-parallel-with-another-code-with-python-matplotlib/70364499#70364499 using just one, since read somewhere that animation.FuncAnimation has to be in main thread. I dont know what async is I'll try to figure it out, I would be interested in your view about https://stackoverflow.com/questions/70298863/how-to-run-animation-in-parallel-with-another-code-with-python-matplotlib/70364499#70364499 – pippo1980 Dec 24 '21 at 15:35
0

There are a number of alternatives which might come in handy in different situations. Here is one that I have used:

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



x = np.linspace(0, 30, 51)
y = np.linspace(0, 30, 51)
xx, yy = np.meshgrid(x, y)


# plt.style.use("ggplot")
plt.ion()
fig, ax = plt.subplots()
fig.canvas.draw()

for n in range(50):
    # compute data for new plot
    zz = np.random.randint(low=-10, high=10, size=np.shape(xx))

    # erase previous plot
    ax.clear()

    # create plot
    im = ax.imshow(zz, vmin=-10, vmax=10, cmap='RdBu', origin='lower')

    # Re-render the figure and give the GUI event loop the chance to update itself
    # Instead of the two lines one can use "plt.pause(0.001)" which, however gives a
    # decepracted warning.
    # See https://github.com/matplotlib/matplotlib/issues/7759/ for an explanation.
    fig.canvas.flush_events()
    sleep(0.1)


# make sure that the last plot is kept
plt.ioff()
plt.show()

Additionally, the set_data(...) method of a line plot or imshow object is useful if only the data changes and you don't want to re-drw the whole figure (as this is very time consuming).

BanDoP
  • 79
  • 5
0

trying to elaborate on @dumbpotato21 answer, here my attempt:

import random

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt



def data():
    cnt = 0
    x = []
    y = []
        
    for i in  range(1,10):
        # x = []
        # y = []
        

        x.append(cnt*i)
        y.append(random.randint(0, 10))

        
        cnt += 1
        
        
        yield  x, y, cnt
    
    input('any key to exit !!!')
    quit()


def init_animate():
    pass

def animate( data, *fargs) :
    
    print('data : ', data, '\n data type : ', type(data), ' cnt : ', data[2])
    
    plt.cla()
    
    x = [i*k for i in data[0]]
    
    y = [i*p for i in data[1]]
    
    plt.plot(x,y)


if __name__ == "__main__":
    fig = plt.figure()
    
    k = 3
    p = 5
    
    ani = FuncAnimation(fig, animate, init_func=init_animate, frames=data,  interval=700, fargs = [k,p])
    plt.show()

pippo1980
  • 2,181
  • 3
  • 14
  • 30