2

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.

3
  • 1
    You shouldn't be using plt.show() inside of a loop. You want to show the window only once after all. Commented Dec 21, 2021 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? Commented Dec 21, 2021 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. Commented Dec 22, 2021 at 11:28

4 Answers 4

7

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()
Sign up to request clarification or add additional context in comments.

6 Comments

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?
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 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)
@dumbPotato21 got it from stackoverflow.com/questions/38531076/… I was passing fargs instead of frames
@pippo1980 I mean this example is contrived and I doubt has any real world significance but here 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.
|
1

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()

Comments

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).

Comments

0

This example from the matplotlib documentation is what might help you. The idea is to collect all the Artist objects generated by your loop in a list and then use matplotlib.animation.ArtistAnimation to convert that into an animation. I am pasting the code example from the matplotlib documentation below:

import matplotlib.pyplot as plt
import numpy as np

import matplotlib.animation as animation

fig, ax = plt.subplots()


def f(x, y):
    return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)

# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
    x += np.pi / 15
    y += np.pi / 30
    im = ax.imshow(f(x, y), animated=True)
    if i == 0:
        ax.imshow(f(x, y))  # show an initial one first
    ims.append([im])

ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
                                repeat_delay=1000)

# To save the animation, use e.g.
#
# ani.save("movie.mp4")
#
# or
#
# writer = animation.FFMpegWriter(
#     fps=15, metadata=dict(artist='Me'), bitrate=1800)
# ani.save("movie.mp4", writer=writer)

plt.show()

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.