Although hours of browsing stackoverflow definitely improved my python animate code, I could not quite figure out one thing and therefore I'm turning towards the kind souls of the community in the hope that someone might be able to shred some light.
In a nutshell, I have a background image that is about 2000 x 1000 pixels let's say, I need to scatter some points over this image and animate the process and save whole thing as a video. I'm able to update the scatterplot as needed only with the exception that I can't remove the precedent scatterplot. So the output is not what I would really want. I would be glad if someone could have a glance at the code and see where the glitch is. I've used scat.remove() which seems to be doing nothing.
Thank you in advance folks.
import matplotlib.pyplot as plt
import pylab as pl
import numpy as np
from pylab import savefig
from matplotlib import animation
import matplotlib
######################################################
fig = plt.figure()
ax = plt.axes()
a = plt.imread('background.jpg')
im = plt.imshow(a)
#######################################################
def randpair(n):
x,y=[],[]
for i in xrange(n):
x.append(np.random.randint(100,1900))
y.append(np.random.randint(100,900))
return x,y
def animate(i):
scat = ax.scatter(0,0,color='white')
points = np.random.randint(5,size=10)
for j in points:
xy = randpair(j)
x = xy[0]
y = xy[1]
print x,y
if len(x) > 0 :
scat.remove()
scat = ax.scatter(x,y,color='r',s=18)
plt.xticks([])
plt.yticks([])
return scat,ax, # ax returns the text to be updated and scat returns the scatterplot.
anim = animation.FuncAnimation(fig, animate, 49,interval=300, blit=True)
writer = animation.writers['ffmpeg']
anim.save('film_3.mp4')
#plt.show()
In the code you already remove the last scatter before the loop is finished; so some scatter plots will be added and then removed immediately.
One can prevent this by collecting the scatters in a list, then removing the scatters in the list from the canvas using remove and cleaning the list.
Apart from that, returning the complete ax object is a little useless. So I would suggest to simply turn blitting off, since it will not matter for saving the animation.
Here is a complete code that worked for me:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation
fig = plt.figure()
ax = plt.axes()
scats = []
a = np.random.rand(8,18)
im = ax.imshow(a, cmap="YlGn", vmin=0, vmax=3, extent=[0,2000,0,1000])
plt.xticks([])
plt.yticks([])
def randpair(n):
x,y=[],[]
for i in xrange(n):
x.append(np.random.randint(100,1900))
y.append(np.random.randint(100,900))
return x,y
def animate(i):
global scats
# first remove all old scatters
for scat in scats:
scat.remove()
scats=[]
# now draw new scatters
points = np.random.randint(5,size=10)
for j in points:
x, y = randpair(j)
if len(x) > 0 :
scats.append(ax.scatter(x,y,color='r',s=18))
anim = matplotlib.animation.FuncAnimation(fig, animate, 50,
interval=1000, blit=False)
writer = matplotlib.animation.FFMpegWriter(fps=15,
codec="h264",
extra_args=["-preset", "veryslow","-crf","0"])
anim.save(__file__+".mp4", writer=writer)
plt.show()
Related
I am trying to create a crude animation, where each additional data point is plotted on the same graph. The problem is that the loop is generating a new graph for each data point.
Here's the code:
x = []
y = []
for i in range(3):
x.append(random.randint(0,10))
y.append(random.randint(0,10))
plt.scatter(x,y)
plt.pause(0.1)
This resulted in 3 separate plots stacked vertically. I would like all data points to update on the same graph, creating an animation. Thanks!
I've made an adaptation of your code (in particular, in each iteration I plot only another scatter point, because plotting each time ALL the points soon becomes unbearably slow).
If you will execute this file, as I invite you to do, $ python3 slow.py, it will print 0 50 100 150 200 and, initially fast, then slower and slower, it will produce a progressive display of the data points, all in the same Axes.
I have to confess that I don't understand your problem description because it's so different from what I've seen.
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
import random
def point():
return (random.randint(0,10), random.randint(0,10))
plt.xlim((-1, 11))
plt.ylim((-1, 11))
random.seed(20221229)
N = 200
cmap = plt.get_cmap('plasma')
plt.colorbar(ScalarMappable(cmap=cmap)).set_ticks((0,1), labels=("1",str(N)))
for i in range(N):
if not(i%50) : print(i)
plt.scatter(*point(), color=cmap(i/N), ec='black', s=80)
plt.pause(0.001)
print(N)
plt.show()
To update matplotlib graph you should use the module animation like Matplotlib is not very compatible with threads.
Here is an example adding a new point every 2 seconds :
import matplotlib.pyplot as pl
from matplotlib.animation import FuncAnimation
import random
datas = [0, 50]
fig = pl.figure()
ax = fig.add_subplot(1,1,1)
ax.scatter(x=datas, y=datas, marker = '+', c='red')
def update(frame):
global datas
ax.clear()
ax.scatter(x=datas, y=datas, marker = '+', c='red')
datas.append(random.randint(0,50))
animation = FuncAnimation(fig, update, interval=2000, repeat = True)
pl.show()
Interval (in milliseconds) in FuncAnimation function plays the role of the pause function you are looking for.
James, I think plt.scatter can't make an animation. All the code will be executed and then a chart with 3 points will be the result.
To avoid the generation of multiple figures you can use plt.subplots.
fig, ax = plt.subplots()
for i in range(3):
ax.scatter(x=random.randint(0,10),y= random.randint(0,10))
If you want to create some animated figure use matplotlib.animation.FuncAnimation , as in the answer of the following topic. How can i make points of a python plot appear over time?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
x = np.arange(10)
y = np.random.random(10)
size = np.random.randint(150, size=10)
colors = np.random.choice(["r", "g", "b"], size=10)
fig = plt.figure()
plt.xlim(0, 10)
plt.ylim(0, 1)
graph = plt.scatter([], [])
def animate(i):
graph.set_offsets(np.vstack((x[:i+1], y[:i+1])).T)
graph.set_sizes(size[:i+1])
graph.set_facecolors(colors[:i+1])
return graph
ani = FuncAnimation(fig, animate, repeat=False, interval=200)
HTML(ani.to_jshtml())
Mark the option loop, and click in the minus or plus sing to slow or speed up the animation
Alternative approach, using FuncAnimation
from matplotlib.pyplot import Normalize, get_cmap, subplots
from matplotlib.cm import ScalarMappable
from matplotlib.animation import FuncAnimation, PillowWriter
from numpy.random import rand, seed
def update(xyn):
x, y, n = xyn
ax.scatter(x, y, color=cmap(n/N), ec='grey')
def start():
ax.set_xlim((-0.1, 1.1)) ; ax.set_ylim((-0.1, 1.1))
cb = fig.colorbar(ScalarMappable(cmap=cmap, norm=Normalize(0, 1)))
cb.set_ticks((0, 1), labels=('First', 'Last'))
def points(n):
seed(20230102)
def _():
for n_ in range(n):
yield rand(), rand(), n_
return _
fig, ax = subplots()
cmap = get_cmap('Greys')
N = 80
FuncAnimation(fig, update, points(N), start, repeat=False).save(
'scatter.gif', writer=PillowWriter())
I'm trying to see the evolution of a system in phase space. I have a list of matrices corresponding to each iteration (inside the array "results") and i'm trying to use matplotlib to animate a gif or mp4 file.
I'm omitting the code that leads to the matrices inside results fro brevity but I believe I include everything about the issue itself.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
a = results[0]
def init():
global im
im = plt.imshow(a, cmap=plt.cm.copper,
interpolation='sinc',
extent=[-1, 1, -1, 1])
im.set_data(a)
return [im]
def animate(i):
data = results[i]
im.set_array(data)
return [im]
fig = plt.figure()
ax = plt.axes()
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=N, interval=0.1, blit=True)
anim.save('test2.gif', writer='pillow')
plt.show()
Currently what I have does produce an output but it is a gif that the image simply fills up to a single color (https://i.imgur.com/yXRxpLr.gif), which I know for a fact doesn't correspond to the final state of the system because when I use imshow on individual matrices from 'results', I obtain correct results - a pattern in 2D).
Maybe I'm overlapping everything and that's why it turns to a single color? I don't know exactly where the problem lies.
For these type of tasks I like to use celluloid (disclaimer: I'm the author). It isn't memory efficient like FuncAnimation is since it uses ArtistAnimation under the hood. That being said here's an example of how you would use it to make an animation from a series of imshow calls. You essentially just call camera.snap after each imshow.
import matplotlib.pyplot as plt
from celluloid import Camera
import numpy as np
from numpy import random as rng
X = np.zeros((100, 100)) + 0.5
fig = plt.figure()
camera = Camera(fig)
for i in range(20):
plt.imshow(X, interpolation='none', vmin=0, vmax=1)
camera.snap()
X += 0.5 * (rng.rand(100, 100) - 0.5)
X = np.clip(X, 0, 1)
anim = camera.animate()
anim.save('im.mp4')
I am a student and I am new to matplotlib animation.
I am trying to figure out how to animate zooming in towards the center of my 3d scatterplot, and I've included my code below. I am trying to get the zeroes to be at the middle of each axis so I am able to see the overall plot as a zoom in. I don't get an error whenever I run my code but when I run the animation the intervals change abruptly and don't seem to go in a certain pattern. Another thing I've noticed is that the zeroes are only sometimes in the middle of the axis, while the plot "glitches out".
Thank You.
import matplotlib.pylab as plt
import matplotlib.animation as animation
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
%matplotlib notebook
x = np.random.rand(100)*100
y = np.random.rand(100)*100
z = np.random.rand(100)*100
#setup figure
fig = plt.figure()
ax = fig.add_subplot(111, facecolor='LightCyan', projection = '3d')
#set up viewing window (in this case the 25 most recent values)
ax.set_xlim([-1, 1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
#sets up list of images for animation
plot = ax.scatter(x, y, z, color='b', marker= '*',)
def func(i):
x_lim = ax.set_xlim(-i,i)
y_lim = ax.set_ylim(-i, i)
z_lim = ax.set_zlim(-i, i)
return plot
ani = animation.FuncAnimation(fig, func, frames=100, interval=1000, blit=True)
I'm trying to grab data from a text file and plot it using the animation.FuncAnimation module from matplotlib. Here is my code that I'm trying to make run correctly
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
style.use('ggplot')
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
graph_data = open('example.txt', 'r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines:
x,y = line.split(',')
xs.append(x)
ys.append(y)
ax1.clear()
ax1.plot(xs, ys)
animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
example.txt is an 18-line text file (omitted for space reasons) which contain (x,y) pairs of data which I'd like to plot. However, matplotlib is not plotting the x values in order: once they reach 10, they 'wrap around' back to the beginning, sandwiching themselves between 1 and 2. Makes for a pretty bad graph.
I'm having some trouble figuring out what's wrong with my implementation. I've even tried sorting the values before plotting them, but the plot still comes out like this.
All help is appreciated! I've been scouring doc pages and StackOverflow for a while now, and I can't seem to find anyone whose had this same problem.
Quick answer: Working example below.
There are few aspects you should be aware of.
Firstly, FuncAnimation will execute animate function on each call, i.e. every interval milliseconds, which in your case is 1 second. You don't really want to read file again and again... do it once before and then update your view.
Secondly, creating each time whole axis (ax.plot) is very expensive and it'll slow down quickly.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
graph_data = open('example.txt', 'r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines[:-1]:
x,y = line.split(',')
xs.append(float(x))
ys.append(float(y))
# This is where you want to sort your data
# sort(x, y, reference=x) # no such function
style.use('ggplot')
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
x, y = [], [] # Prepare placeholders for animated data
ln, = plt.plot(x, y, animated=True)
ax1.set_xlim(min(xs), max(xs)) # Set xlim in advance
ax1.set_ylim(min(ys), max(ys)) # ylim
def animate(i):
x.append(xs[i])
y.append(ys[i])
ln.set_data(x, y)
return ln,
ani = animation.FuncAnimation(fig, animate, frames=range(len(xs)),
interval=1000, repeat=False, blit=True)
plt.show()
Notice that we used repeat flag to False. This means that once it goes through whole frames list then it stops.
The numbers are not in order because you are treating them as strings not numbers. So append them as floats and it will solve it. Try:
xs.append(float(x))
ys.append(float(y))
I would like to create a animation where my data points would gradually appear on my graph and freeze when all the data points have appeared. I've seen in done with correlations i'm just not too sure how to do it with just individual points themselves
This isn't something that will show anything particularly useful but i though it would look cool since i am trying to visualize some location data on a map
I know this isn't very clear so please as for clarifications, I'm not too sure how to phrase my problem very well.
Thanks
matplotlib.animation.FuncAnimation is the right tool for you. First create an empty graph, and then gradually add data points to it in the function. The following piece of code will illustrate it:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.arange(10)
y = np.random.random(10)
fig = plt.figure()
plt.xlim(0, 10)
plt.ylim(0, 1)
graph, = plt.plot([], [], 'o')
def animate(i):
graph.set_data(x[:i+1], y[:i+1])
return graph
ani = FuncAnimation(fig, animate, frames=10, interval=200)
plt.show()
The result (saved as gif file) is shown below:
EDIT: To make the animation look stopped when finished in matplotlib window, you need to make it infinite (omit frames parameter in FuncAnimation), and set the frame counter to the last number in your frame series:
def animate(i):
if i > 9:
i = 9
graph.set_data(x[:i+1], y[:i+1])
return graph
ani = FuncAnimation(fig, animate, interval=200)
Or, which is better, you can set repeat parameter in FuncAnimation to False, as per answer to this question.
EDIT 2: To animate a scatter plot, you need a whole bunch of other methods. A piece of code is worth a thousand words:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.arange(10)
y = np.random.random(10)
size = np.random.randint(150, size=10)
colors = np.random.choice(["r", "g", "b"], size=10)
fig = plt.figure()
plt.xlim(0, 10)
plt.ylim(0, 1)
graph = plt.scatter([], [])
def animate(i):
graph.set_offsets(np.vstack((x[:i+1], y[:i+1])).T)
graph.set_sizes(size[:i+1])
graph.set_facecolors(colors[:i+1])
return graph
ani = FuncAnimation(fig, animate, repeat=False, interval=200)
plt.show()