I gave an answer on this thread, talking about fading point on matplotlib. And I got curious about ImportanceOfBeingErnest's answer. So I tried to play around with his code.
First, here is my code.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.colors import LinearSegmentedColormap
def get_new_vals():
x = 0
y = 0
while True:
if x >= .9:
x = 0
y = 0
x += .1
y += .1
yield x, y
def update(t, x_vals, y_vals, intensity, scatter, gen):
# Get intermediate points
new_xvals, new_yvals = gen.next()
x_vals.extend([new_xvals])
y_vals.extend([new_yvals])
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals, y_vals])
# Calculate new color values
for index in range(len(intensity)):
if intensity[index] < .1:
intensity[index] = 0
intensity[index] *= .6
intensity.extend(1 for _ in xrange(len([new_xvals])))
intens_dup = np.array(intensity)
"""
intensity = np.concatenate((np.array(intensity) * .6, np.ones(len(new_xvals))))
"""
scatter.set_array(intens_dup)
# Set title
axis.set_title('Time: %0.3f' % t)
def anim_random_points(fig, axis):
x_vals = []
y_vals = []
intensity = []
iterations = 100
colors = [ [0, 0, 1, 0], [0, 0, 1, 0.5], [0, 0.2, 0.4, 1] ]
cmap = LinearSegmentedColormap.from_list("", colors)
scatter = axis.scatter(x_vals, y_vals, c=[], cmap=cmap, vmin=0, vmax=1)
gen_values = get_new_vals()
ani = matplotlib.animation.FuncAnimation(fig, update, frames=iterations,
interval=50, fargs=(x_vals, y_vals, intensity, scatter, gen_values),
repeat=False)
# Position 1 for plt.show()
plt.show()
if __name__ == '__main__':
fig, axis = plt.subplots()
axis.set_xlabel('X Axis', size = 12)
axis.set_ylabel('Y Axis', size = 12)
axis.axis([0,1,0,1])
anim_random_points(fig, axis)
# Position 2 for plt.show()
# plt.show()
I, then, noticed an odd thing. At least for me. Notice the Position 1 and Position 2 (at the end of code). The position 1 is placed just after the animation function, the other one is placed just after code-wise, since the function ends after position 1 and therefore goes to position 2.
Since FuncAnimation requires the figure to run the animation on, I am wondering why the plt.show() works on Position 1, but not on Position 2.
The matplotlib documentation states about FuncAnimation
It is critical to keep a reference to the instance object. The animation is advanced by a timer (typically from the host GUI framework) which the Animation object holds the only reference to. If you do not hold a reference to the Animation object, it (and hence the timers), will be garbage collected which will stop the animation.
If you put plt.show() outside of the anim_random_points function, the variable ani, which holds the reference to the animation, will be garbage collected and there will be no animation to be shown any more.
The solution for that case would be to return the animation from that function
def anim_random_points(fig, axis):
# ...
ani = matplotlib.animation.FuncAnimation(...)
return ani
if __name__ == '__main__':
# ...
ani = anim_random_points(...)
plt.show()
You should really ask two separate questions.
I can answer the first one. The difference between the two positions is due to the fact that ani is a local variable to your function anim_random_points(). It is automatically deleted when the execution reaches the end of the function. Therefore plt.show() in position 2 has nothing to display.
If you want to use plt.show() in position 2, you need to return the ani object from your function, and keep a reference to it in the main part of your code.
def anim_random_points(fig, axis):
(...)
ani = matplotlib.animation.FuncAnimation(...)
return ani
if __name__ == '__main__':
(...)
ani = anim_random_points(fig, axis)
# Position 2 for plt.show()
plt.show()
Related
Context: I am trying to create a teaching demo tool to show how the iteration guesses through a set of points to ultimately arrive at the root of an equation
Problem: I want to animate using matplotlib to show the iterations viusally. Specifically, given a curve (see along side) and an initial guess (say 1.5 in this particular case), I want to compose an animation of 3 scenes:
draw a vertical line at x = guess (=1.5), to meet the curve at y= 9 (aka value).
Draw a line through (guess, value) with a slope 'm' (generated by my code). Delete the vertical line at this stage and keep the second line
Delete the second line after a pause
For illustration, here is the image
Additionally here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
xdata2, ydata2 = [], []
ln, = plt.plot([], [])
def init():
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
return [ln]
def update(frame):
xdata.append(frame)
ydata.append(frame ** 3 + 4 * frame ** 2 + frame - 6)
ln.set_data(xdata, ydata)
return [ln]
def update2(frame):
xdata2.append(1.5)
ydata2.append(frame)
ln.set_data(xdata2,ydata2)
return[ln]
ani = FuncAnimation(fig, update, frames=np.linspace(-3, 3, 100),
init_func=init, blit=True)
ani2 = FuncAnimation(fig, update2, frames=np.linspace(0, 3, 100),blit=True)
plt.show()
This is a simplified version of the problem that I am trying to solve and is not part of the code that involves the calculation of the iterations etc. For now I am just trying to draw the curve in Update and post that, draw a vertical line at x=1.5.
Results: At my end, the entire animation is a set of flickering where it is apparent that matplotlib switches "thread context" very rapidly between the two FuncAnimation calls
The desired animation in your next question can be achieved in the form of drawing a curve as a base graph, adding line graphs frame by frame, and deleting that graph object when necessary.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import time
fig, ax = plt.subplots()
x = np.linspace(-3, 3, 100)
y = x ** 3 + 4 * x **2 + x -6
xx = x[74]
yy = y[74]
#print(xx,yy)
xx2 = x[65]
yy2 = y[65]
#print(xx2,yy2)
ln, = ax.plot(x, y)
ln2, = ax.plot([], [])
ln3, = ax.plot([],[])
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
# Move axes center and spines off
ax.spines[['top', 'right']].set_visible(False)
ax.spines[['left', 'bottom']].set_position('center')
# Show ticks axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
def update(i):
ln2.set_data((xx, xx), (0, yy))
ln2.set_color('k')
if i == 2:
ln3.set_data((xx2, xx), (yy2, yy))
ln3.set_color('red')
ln3.set_width=3
if i >=3:
ln2.set_data([],[])
ln3.set_data([],[])
return ln2,ln3
ani = FuncAnimation(fig, update, frames=[0,1,2,3], interval=500, blit=True, repeat=True)
plt.show()
So I am trying to make a game of life using matplotlib's FuncAnimation function to update the grid I am displaying. However, the process is taking longer and longer, probably because I am not pointing out what I am updating. I am not very familiar with the concept of artists either.
For the moment, my code looks like this :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
import random as rd
import matplotlib.animation as animation
from time import process_time
Ln = 100 # length
La = 10 # width
data = np.ones((Ln, La)) * np.nan # matrix filled with 0 containing the squares to be colored
pause = False # puts in pause when clicking
Case = [(i, j) for i in range(Ln) for j in range(La)] # squares of the grid
Case = dict(zip([i for i in range(La*Ln)], Case))
def randometre(a):
'''
Colors a square.
'''
while Case:
if not pause:
xx, yy = Case.pop(rd.choice(list(Case.keys()))) # colors the next square in a random order
data[xx, yy] = 1 # square that is being colored
ax.fill_between([xx, xx + 1], [yy], [yy + 1], color=C)
break # to see the coloring process
return
def on_click(event):
global pause
pause ^= True
# random color generation
C = '#%02X%02X%02X' % (rd.randint(0,255), rd.randint(0,255), rd.randint(0,255))
xx = 0
yy = 0
# plotting
fig = plt.figure()
ax = fig.add_subplot(111)
fig.canvas.mpl_connect('button_press_event', on_click)
# drawing grid and squares
for y in range(La + 1):
ax.plot([0, Ln], [y, y], lw=2, color='k')
for x in range(Ln + 1):
ax.plot([x, x], [0, La], lw=2, color='k')
# loop coloring squares
ani = animation.FuncAnimation(fig, randometre, blit=False, interval=10, repeat=False, frames=La*Ln)
ax.axis('off')
plt.show()
So what I need is the fastest way to color the squares as well as being able to see the progress live without slowing down.
A similar issue has been raised here but I can't manage to adapt it to my code unfortunately...
Thank you very much for your time and help !
You should use blit=True, I did two modifications to your code. Now is fast.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
import random as rd
import matplotlib.animation as animation
from time import process_time
Ln = 100 # length
La = 10 # width
data = np.ones((Ln, La)) * np.nan # matrix filled with 0 containing the squares to be colored
pause = False # puts in pause when clicking
Case = [(i, j) for i in range(Ln) for j in range(La)] # squares of the grid
Case = dict(zip([i for i in range(La*Ln)], Case))
def randometre(a):
'''
Colors a square.
'''
while Case:
if not pause:
xx, yy = Case.pop(rd.choice(list(Case.keys()))) # colors the next square in a random order
data[xx, yy] = 1 # square that is being colored
poly = ax.fill_between([xx, xx + 1], [yy], [yy + 1], color=C)
break # to see the coloring process
return poly, # must return something to make blit=True to work
def on_click(event):
global pause
pause ^= True
# random color generation
C = '#%02X%02X%02X' % (rd.randint(0,255), rd.randint(0,255), rd.randint(0,255))
xx = 0
yy = 0
# plotting
fig = plt.figure()
ax = fig.add_subplot(111)
fig.canvas.mpl_connect('button_press_event', on_click)
# drawing grid and squares
for y in range(La + 1):
ax.plot([0, Ln], [y, y], lw=2, color='k')
for x in range(Ln + 1):
ax.plot([x, x], [0, La], lw=2, color='k')
# loop coloring squares, blit=True
ani = animation.FuncAnimation(fig, randometre, blit=True, interval=10, repeat=False, frames=La*Ln)
ax.axis('off')
I'm trying to animate a 2d path, and I would like it to have a sort of "Disappearing Tail", where at any given time, it shows only the last 5 (for example) particles.
What I currently have is quite far from this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
sample_path = np.random.uniform(size=(100,2))
fig, ax = plt.subplots()
x = np.arange(-1, 1, 0.01) # x-array
line, = ax.plot(sample_path[0,0], sample_path[0,1])
def connect(i):
(x0,y0) = sample_path[i-1,:]
(x1,y1) = sample_path[i,:]
plt.plot([x0,x1],[y0,y1],'ro-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), init_func=init,
interval=200, blit=True)
HTML(ani.to_html5_video())
This retains a 'full tail', i.e. after k steps, it shows all of the first k locations.
Is there a way to adapt what I've got so that the animation only shows the most recent history of the particle?
You would probably want to update the line instead of adding a lot of new points to the plot. Selecting the 5 most recent points can be done via indexing, e.g.
sample_path[i-5:i, 0]
Complete example, where we take care not to have a negative index and also don't use blit (which does not make sense if saving the animation).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
r = np.sin(np.linspace(0,3.14,100))
t = np.linspace(0, 10, 100)
sample_path = np.c_[r*(np.sin(t)+np.cos(t)), r*(np.cos(t)-np.sin(t))]/1.5
fig, ax = plt.subplots()
line, = ax.plot(sample_path[0,0], sample_path[0,1], "ro-")
def connect(i):
start=max((i-5,0))
line.set_data(sample_path[start:i,0],sample_path[start:i,1])
return line,
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), interval=200)
plt.show()
Not as good as ImportanceOfBeingErnest's answer technically, but it still does the job and looks pretty cool, just plot the latest points and clear the old ones. I added a few more and sped it up because I thought it looked better with a longer trail.
def connect(i):
#clear current points
plt.clf()
#prevent axis auto-resizing
plt.plot(0,0)
plt.plot(1,1)
#generate points to plot
(x0,y0) = sample_path[i-8,:]
(x1,y1) = sample_path[i-7,:]
(x2,y2) = sample_path[i-6,:]
(x3,y3) = sample_path[i-5,:]
(x4,y4) = sample_path[i-4,:]
(x5,y5) = sample_path[i-3,:]
(x6,y6) = sample_path[i-2,:]
(x7,y7) = sample_path[i-1,:]
(x8,y8) = sample_path[i,:]
#plot old points
plt.plot([x0,x1,x2,x3,x4,x5,x6,x7],[y0,y1,y2,y3,y4,y5,y6,y7],'ro-')
#plot new point in blue
plt.plot([x7,x8],[y7,y8],'bo-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, frames=np.arange(1, 100),
init_func=init,
interval=50, blit=True)
HTML(ani.to_html5_video())
I have a bunch of points in a scatterplot which overlap. I am using FuncAnimation to create an animation. In successive frames I would like to change which appear in front of the others.
As a simple MCVE, consider the code below in which each frame makes a different set of points be red. However, these red points are often largely obscured by other points. I would like to make these red points come to the front.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random
def update(time, plotted_points, N):
#in here I would like to update the order that the points are plotted in. I would like the red points to come to the front.
color_change_indices = set(random.choices(range(N), k=1000))
colors = ['red' if n in color_change_indices else 'gray' for n in range(N)]
plotted_points.set_color(colors)
return plotted_points
def animate(N):
x = [random.random() for val in range(N)]
y = [random.random() for val in range(N)]
fig = plt.figure()
ax = fig.add_subplot(111)
plotted_points = ax.scatter(x, y, color='gray')
fargs = (plotted_points, N)
ani = FuncAnimation(fig, update, frames = range(100), fargs = fargs)
plt.show()
animate(10000)
I can change their color. I can move their coordinates. However, so far I cannot modify their relative depth.
So far the best idea I have is that perhaps I should delete the plotted points and then replot them. But I don't have a deep understanding of matplotlib, and my attempts to delete them have failed so far.
You can have a second scatter plot, red coloured and with a higher zorder, and update its points:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random
plt.ion()
def update(time, red_plotted_points, points, N):
indexes = set([random.choice(range(N)) for i in range(int(N/2))]) # for instance!
new_points = [points[i] for i in indexes]
red_plotted_points.set_offsets(new_points)
def animate(N):
x = [random.random() for val in range(N)]
y = [random.random() for val in range(N)]
points = [[x[i], y[i]]for i in range(N)]
fig = plt.figure()
ax = fig.add_subplot(111)
plotted_points = ax.scatter(x, y, color='gray', zorder=1)
red_plotted_points = ax.scatter([], [], color='red', zorder=2) # starts empty
fargs = (red_plotted_points, points, N)
ani = FuncAnimation(fig, update, frames=range(100), fargs=fargs,
interval=200, repeat=True)
ani._start()
fig.show()
return fig, ani
if __name__ == '__main__':
fig, ani = animate(100)
(python 2.7.14, matplotlib 2.1.1)
Edit: Updated to also run on Python 3.6.3, matpotlib 2.1.0
I'm not sure why, but it seems that if a reference is not kept to the FuncAnimation it does not work on Python 3.6. Thanks to Joel (comments) for noticing.
In order to bring red points in front, you need to sort the data where red points come last. In a following code, instead of sorting the points by their color, it shuffles the data and draws last 1000 points with red, at each time update is called.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random
import numpy as np # np.random.shuffle
def update(time, plotted_points):
# Extract data and colors
data = plotted_points.get_offsets()
colors = plotted_points.get_facecolor()
# shuffle the data [N, 2] along the first axis
np.random.shuffle(data)
# Set shuffled data and reset colors
plotted_points.set_offsets(data)
plotted_points.set_color(colors)
return plotted_points
def animate(N):
x = [random.random() for val in range(N)]
y = [random.random() for val in range(N)]
fig = plt.figure()
ax = fig.add_subplot(111)
# Need to define colors at first.
colors = ['red' if idx < 1000 else 'gray' for idx in range(N)]
colors.reverse()
plotted_points = ax.scatter(x, y, color=colors)
fargs = (plotted_points,)
ani = FuncAnimation(fig, update, frames = range(100), fargs = fargs)
plt.show()
animate(10000)
I have made an animation using matplotlib and I'm trying to save it to a file. For this it seems I would need to turn off automatic repeating of the animation. If not, matplotlib will try to render a movie file that never ends.
But how do I keep the animation from looping? I have found that there is a keyword argument for the animation function, repeat, that can be set to False, but this has no apparent effect on my code! So what should I do? I've been googling for way to long with no avail.
The relevant code is as follows (last two lines is where I believe the error must be) (largely based on this):
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
# ax.axis('off')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, n_bodies))
# set up lines and points
lines = sum([ax.plot([], [], [], '-', c=c)
for c in colors], [])
pts = sum([ax.plot([], [], [], 'o', c=c)
for c in colors], [])
# prepare the axes limits
xmin_max = (np.min(r[:,:,0])/2, np.max(r[:,:,0])/2)
ymin_max = (np.min(r[:,:,1])/2, np.max(r[:,:,1])/2)
zmin_max = (np.min(r[:,:,2])/2, np.max(r[:,:,2])/2)
ax.set_xlim(xmin_max)
ax.set_ylim(ymin_max)
ax.set_zlim(zmin_max)
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt in zip(lines, pts):
line.set_data([], [])
line.set_3d_properties([])
pt.set_data([], [])
pt.set_3d_properties([])
return lines + pts
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % r.shape[1]
for line, pt, ri in zip(lines, pts, r):
# x, y, z = ri[:i].T
x, y, z = ri[i-1].T
line.set_data(x, y)
line.set_3d_properties(z)
pt.set_data(x, y)
# pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z)
# pt.set_3d_properties(z[-1:])
ax.legend(['t = %g' % (i/float(n_timesteps))])
#ax.view_init(30, 0.01 *0.3 * i )
fig.canvas.draw()
return lines + pts
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=n_timesteps, interval=10, blit=True,repeat=False)
anim.save('../fig/animation.mp4', writer = 'mencoder', fps=15)
print 'done!'
plt.show()
Have you run this? I have found that saving the animation produces a file with the number of frames set by FuncAnimation( , ,frames=numberofframes).
ani = animation.FuncAnimation(fig, update, frames=numberofframes, interval=1000/fps)
filename = 'doppler_plot'
ani.save(filename+'.mp4',writer='ffmpeg',fps=fps)
ani.save(filename+'.gif',writer='imagemagick',fps=fps)
If the output format is an animated GIF, this will usually repeat when played, but the file will only contain the number of frames specified.