I have the following code that produces an animation of drawing a circle.
from math import cos, sin
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_plot(num, x, y, line):
line.set_data(x[:num], y[:num])
line.axes.axis([-1.5, 1.5, -1.5, 1.5])
return line
def plot_circle():
x = []
y = []
for i in range(100):
x.append(cos(i/10.0))
y.append(sin(i/10.0))
fig, ax = plt.subplots()
line, = ax.plot(x, y, color = "k")
ani = animation.FuncAnimation(fig, update_plot, len(x), fargs=[x, y, line], interval = 1, blit = False)
plt.show()
plot_circle()
The line is longer than a full lap, and so to be able to still see the drawing when the line overlaps, I would like a marker that shows what is being drawn. I tried to add a scatter plot into the update call, like
scat = plt.scatter(0, 0)
ani = animation.FuncAnimation(fig, update_plot, len(x), fargs=[x, y, line, scat], interval = 1, blit = False)
and try to update the position of the scatter-plot point using x[num] and y[num] in update_plot without success. How can I achieve this effect?
You need to return scat in update_plot().
Here is another method, draw the line with markevery argument:
line, = ax.plot(x, y, "-o", color="k", markevery=100000)
reverse the points order:
line.set_data(x[:num][::-1], y[:num][::-1])
for example:
import numpy as np
import pylab as pl
t = np.linspace(0, 2, 100)
x = np.cos(t)
y = np.sin(t)
pl.plot(x[::-1], y[::-1], "-o", markevery=10000)
outputs:
I ended up finding a way to add the scatter plot to the same animation. The key was to use scat.set_offsets to set the data. The changes that are needed is as follows:
def update_plot(num, x, y, line, scat):
# ...
scat.set_offsets([x[num - 1], y[num - 1]])
return line, scat
def plot_circle():
# ...
scat = ax.scatter([0], [0], color = 'k') # Set the dot at some arbitrary position initially
ani = animation.FuncAnimation(fig, update_plot, len(x), fargs=[x, y, line, scat], interval = 1, blit = False)
plt.show()
Related
I have a dataframe called benchmark_returns and strategy_returns. Both have the same timespan. I want to find a way to plot the datapoints in a nice animation style so that it shows all the points loading in gradually. I am aware that there is a matplotlib.animation.FuncAnimation(), however this typically is only used for a real-time updating of csv files etc but in my case I know all the data I want to use.
I have also tried using the crude plt.pause(0.01) method, however this drastically slows down as the number of points get plotted.
Here is my code so far
x = benchmark_returns.index
y = benchmark_returns['Crypto 30']
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ']
y4 = benchmark_returns['S&P 500']
fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')
def update(num, x, y, y2, y3, y4, line):
line.set_data(x[:num], y[:num])
line2.set_data(x[:num], y2[:num])
line3.set_data(x[:num], y3[:num])
line4.set_data(x[:num], y4[:num])
return line, line2, line3, line4,
ani = animation.FuncAnimation(fig, update, fargs=[x, y, y2, y3, y4, line],
interval = 1, blit = True)
plt.show()
You could try matplotlib.animation.ArtistAnimation. It operates similar to FuncAnimation in that you can specify the frame interval, looping behavior, etc, but all the plotting is done at once, before the animation step. Here is an example
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.animation import ArtistAnimation
n = 150
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x),
'sin(x)' : np.sin(x),
'tan(x)' : np.tan(x),
'sin(cos(x))' : np.sin(np.cos(x))})
fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(10,10))
lines = []
artists = [[]]
for ax, col in zip(axs.flatten(), df.columns.values):
lines.append(ax.plot(df[col])[0])
artists.append(lines.copy())
anim = ArtistAnimation(fig, artists, interval=500, repeat_delay=1000)
The drawback here is that each artist is either drawn or not, i.e. you can't draw only part of a Line2D object without doing clipping. If this is not compatible with your use case then you can try using FuncAnimation with blit=True and chunking the data to be plotted each time as well as using set_data() instead of clearing and redrawing on every iteration. An example of this using the same data from above:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.animation import FuncAnimation
n = 500
nf = 100
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x),
'sin(x)' : np.sin(x),
'tan(x)' : np.tan(x),
'sin(cos(x))' : np.sin(np.cos(x))})
fig, axs = plt.subplots(2, 2, figsize=(5,5), dpi=50)
lines = []
for ax, col in zip(axs.flatten(), df.columns):
lines.append(ax.plot([], lw=0.5)[0])
ax.set_xlim(x[0] - x[-1]*0.05, x[-1]*1.05)
ax.set_ylim([min(df[col].values)*1.05, max(df[col].values)*1.05])
ax.tick_params(labelbottom=False, bottom=False, left=False, labelleft=False)
plt.subplots_adjust(hspace=0, wspace=0, left=0.02, right=0.98, bottom=0.02, top=0.98)
plt.margins(1, 1)
c = int(n / nf)
def animate(i):
if (i != nf - 1):
for line, col in zip(lines, df.columns):
line.set_data(x[:(i+1)*c], df[col].values[:(i+1)*c])
else:
for line, col in zip(lines, df.columns):
line.set_data(x, df[col].values)
return lines
anim = FuncAnimation(fig, animate, interval=2000/nf, frames=nf, blit=True)
Edit
In response to the comments, here is the implementation of a chunking scheme using the updated code in the question:
x = benchmark_returns.index
y = benchmark_returns['Crypto 30']
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ']
y4 = benchmark_returns['S&P 500']
line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')
n = len(x) # Total number of rows
c = 50 # Chunk size
def update(num):
end = num * c if num * c < n else n - 1
line.set_data(x[:end], y[:end])
line2.set_data(x[:end], y2[:end])
line3.set_data(x[:end], y3[:end])
line4.set_data(x[:end], y4[:end])
return line, line2, line3, line4,
ani = animation.FuncAnimation(fig, update, interval = c, blit = True)
plt.show()
or, more succinctly
cols = benchmark_returns.columns.values
# or, for only a subset of the columns
# cols = ['Crypto 30', 'Dow Jones 30', 'NASDAQ', 'S&P 500']
colors = ['k', 'b', 'r', 'g']
lines = []
for c, col in zip(cols, colors):
lines.append(ax.plot(benchmark_returns.index, benchmark_returns[col].values, c=c)[0])
n = len(benchmark_returns.index)
c = 50 # Chunk size
def update(num):
end = num * c if num * c < n else n - 1
for line, col in zip(lines, cols):
line.set_data(benchmark_returns.index, benchmark_returns[col].values[:end])
return lines
anim = animation.FuncAnimation(fig, update, interval = c, blit=True)
plt.show()
and if you need it to stop updating after a certain time simply set the frames argument and repeat=False in FuncAnimation().
You can just update the data into the line element like so:
fig = plt.figure()
ax = fig.add_subplot(111)
liner, = ax.plot()
plt.ion()
plt.show()
for i in range(len(benchmark_returns.values)):
liner.set_ydata(benchmark_returns['Crypto 30'][:i])
liner.set_xdata(benchmark_returns.index[:i])
plt.pause(0.01)
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()
I'm not a beginner, but I'm also not advanced dev of python code.
I'm been trying to animate points movement in scatter plot and to put annotation on every point. All I have done is animation of one point with no annotation. I've searched similar solutions, but it's so confusing. Any help is welcome. This is what I've done.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib.animation as animation
frame_count = 0
points = reading_file("some_data") # this method is not of intrest
def make_one_point(i):
global frame_count, points
ex = [1]
ey = [1]
ez = [1]
point = points[i]
frame = point[frame_count]
ex[0] = frame[0]
ey[0] = frame[1]
ez[0] = frame[2]
frame_count += 1
return ex, ey, ez
def update(i):
global frame_count, points
if frame_count < len(points[i]):
return make_one_point(i)
else:
frame_count = 0
return make_one_point(i)
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
ax1.set_xlim3d(-500, 2000)
ax1.set_ylim3d(-500, 2000)
ax1.set_zlim3d(0, 2000)
x = [1]
y = [1]
z = [1]
scat = ax1.scatter(x,y,z)
def animate(i):
scat._offsets3d = update(0)
ani = animation.FuncAnimation(fig, animate,
frames=len(points[10]),
interval=100, repeat=True)
plt.show()
How to animate more points at the same time, and put annontation on every one of them? There are 50 points, and I'm not so consern about efficiency, just to make it work.
This code output is moving one point animation
It turns out animating Text in 3D was harder than I anticipated. Not surprisingly, I was able to find the solution to the problem in an answer from #ImportanceOfBeingErnest. I then simply adapted the code I had already written in a previous answer, and produced the following code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, proj3d
import matplotlib.animation as animation
N_points = 10
def update(num, my_ax):
# the following corresponds to whatever logic must append in your code
# to get the new coordinates of your points
# in this case, we're going to move each point by a quantity (dx,dy,dz)
dx, dy, dz = np.random.normal(size=(3,N_points), loc=0, scale=1)
debug_text.set_text("{:d}".format(num)) # for debugging
x,y,z = graph._offsets3d
new_x, new_y, new_z = (x+dx, y+dy, z+dz)
graph._offsets3d = (new_x, new_y, new_z)
for t, new_x_i, new_y_i, new_z_i in zip(annots, new_x, new_y, new_z):
# animating Text in 3D proved to be tricky. Tip of the hat to #ImportanceOfBeingErnest
# for this answer https://stackoverflow.com/a/51579878/1356000
x_, y_, _ = proj3d.proj_transform(new_x_i, new_y_i, new_z_i, my_ax.get_proj())
t.set_position((x_,y_))
return [graph,debug_text]+annots
# create N_points initial points
x,y,z = np.random.normal(size=(3,N_points), loc=0, scale=10)
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, projection="3d")
graph = ax.scatter(x, y, z, color='orange')
debug_text = fig.text(0, 1, "TEXT", va='top') # for debugging
annots = [ax.text2D(0,0,"POINT") for _ in range(N_points)]
# Creating the Animation object
ani = animation.FuncAnimation(fig, update, fargs=[ax], frames=100, interval=50, blit=True)
plt.show()
I'm reading output data from some simulations in fortran to make a movie of orbits, after generating a couple graphs. At first, I didn't use blitting for the animation, so while it worked, it was very, very slow.
I originally thought that the animation I wanted lent itself to scatter, since I'd have five series of data with decreasing alphas to create a trailing effect. Here's my original (non-blit) update function:
def animate(frame):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
eptx, epty = ex[frame-3:frame], ey[frame-3:frame]
gptx, gpty = gx[frame-3:frame], gy[frame-3:frame]
iptx, ipty = ix[frame-3:frame], iy[frame-3:frame]
ax2.clear()
ax2.scatter(jptx, jpty, s=32, c=ablue, marker="s", label='Jupiter')
ax2.scatter(cptx, cpty, s=8, c=ared, marker="o", label='Callisto')
ax2.scatter(eptx, epty, s=8, c=agreen, marker="o", label='Europa')
ax2.scatter(gptx, gpty, s=8, c=ablack, marker="o", label='Ganymede')
ax2.scatter(iptx, ipty, s=8, c=ayellow, marker="o", label='Io')
ax2.set_xlim(-3, 7)
ax2.set_ylim(-3, 4)
animation = animation.FuncAnimation(fig2, animate, interval=0.5, frames=jt.size)
print('Begin saving animation')
animation.save('Tabbys Star.mp4', writer='ffmpeg', fps=60)
print('Animation saved')
plt.show()
Now, when I run the script, a window appears for a fraction of a second, and there is very clearly a yellow circle on the screen, indicating the background is being drawn. However, the window closes immediately after. This is the relevant code for the second attempt. The yellow circle was added in this attempt.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
# j_file = location + 'JUPITER.aei'
# jt, jx, jy, jz = read_data(j_file)
jt, jx, jy, jz = np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4])
# c_file = location + 'CALLISTO.aei'
# ct, cx, cy, cz = read_data(c_file)
ct, cx, cy, cz = np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4])
alphas = [0.25, 0.5, 0.75, 1]
ablue = np.zeros((4, 4))
ablue[:, 2] = 1.0
ablue[:, 3] = alphas
ared = np.zeros((4, 4))
ared[:, 0] = 1.0
ared[:, 3] = alphas
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
xdata, ydata = np.zeros((4,)), np.zeros((4,))
jpt, = plt.plot(xdata, ydata, marker='.', ms=32, c=ablue, label='Jupiter')
cpt, = plt.plot(xdata, ydata, marker='.', ms=8, c=ared, label='Callisto')
def init():
ax2.set_xlim(-3, 7)
ax2.set_ylim(-3, 4)
circle = plt.Circle((0, 0), 0.1, color='y')
ax2.add_patch(circle)
for pt in [jpt, cpt]:
pt.set_data(np.zeros((4,)), np.zeros((4,)))
return jpt, cpt
def animate(frame, j, c):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
j.set_data(jptx, jpty)
c.set_data(cptx, cpty)
return j, c
animation = animation.FuncAnimation(fig2, animate, fargs=(jpt, cpt), interval=0.5, frames=jt.size, init_func=init, blit=True)
print('Begin saving animation')
# animation.save('Tabbys Star.mp4', writer='ffmpeg', fps=60)
print('Animation saved')
plt.show()
I'd also eventually like to add a legend and some axis labels, but I believe that can be done normally.
So what's the problem with animate in the second code snippet?
Thanks
Edited for clarity (again)
Please make sure, that you render it for more than 1 frame, by setting frames to a high value. In the code you posted, the number of frames is not clearly defined, which may cause this problem.
You are confusing plt.plot and plt.scatter here. The error you get would even be produced without any animation.
While plt.plot has arguments color and ms to set the color and markersize respectively, they do not allow to use different values for different points. This is why there exists a scatter plot.
plt.scatter has arguments c and s to set the color and markersize respectively.
So you need to use scatter to obtain differently colored points.
jpt = plt.scatter(xdata, ydata, marker='.', s=32, c=ablue, label='Jupiter')
Then for the animation you would need to adjust your code for the use with scatter since it does not have a .set_data method, but a .set_offsets method, which expects a 2 column array input.
j.set_offsets(np.c_[jptx, jpty])
In total the script would look like
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
jt, jx, jy, jz = [np.random.random([100,4]) for _ in range(4)]
ct, cx, cy, cz = [np.random.random([100,4]) for _ in range(4)]
alphas = [0.25, 0.5, 0.75, 1]
ablue = np.zeros((4, 4))
ablue[:, 2] = 1.0
ablue[:, 3] = alphas
ared = np.zeros((4, 4))
ared[:, 0] = 1.0
ared[:, 3] = alphas
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
xdata, ydata = np.zeros((4,)), np.zeros((4,))
jpt = plt.scatter(xdata, ydata, marker='.', s=32, c=ablue, label='Jupiter')
cpt = plt.scatter(xdata, ydata, marker='.', s=8, c=ared, label='Callisto')
def init():
ax2.axis([0,1,0,1])
circle = plt.Circle((0, 0), 0.1, color='y')
ax2.add_patch(circle)
for pt in [jpt, cpt]:
pt.set_offsets(np.c_[np.zeros((4,)), np.zeros((4,))])
return jpt, cpt
def animate(frame, j, c):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
j.set_offsets(np.c_[jptx, jpty])
c.set_offsets(np.c_[cptx, cpty])
return j, c
animation = animation.FuncAnimation(fig2, animate, fargs=(jpt, cpt),
interval=50, frames=jt.size, init_func=init, blit=True)
plt.show()
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()