How to do a 3D animation - python

This is a double question. My main goal is to create an animated 3D plot. I started creating the same plot, but in 2D. But the animation does not work, and I don’t know why. This is the code:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
%matplotlib nbagg
'''
Each one of those are list of numbers:
x_earth, y_earth, z_earth, x_ryu, y_ryu, z_ryu, x_hay,y_hay,z_hay, vel, dist, t
'''
fig = plt.figure(figsize= (8,4))
ax1 = fig.add_subplot (121)
ax1.grid (True)
ax1.plot (0,0,'yo', markersize=20, label='Sun' )
ax1.set_xlim(min (min (x_earth), min(x_ryu), min(x_hay)), max (max (x_earth), max(x_ryu), max(x_hay)))
ax1.set_ylim(min (min (y_earth), min(y_ryu), min(y_hay)), max (max (y_earth), max(y_ryu), max(y_hay)))
pt1, = ax1.plot ([],[], 'bo', label='Earth')
pt2, = ax1.plot ([],[],'ko', label='Ryugu')
pt3, = ax1.plot ([],[], 'r^', label='Hayabusa 2')
l3, = ax1.plot ([],[], 'r--')
#ax1.legend()
ax2 = fig.add_subplot (122)
ax2.grid(True)
ax2.set_xlim(0, max (t))
ax2.set_ylim(0, max (max (vel), max(dist)))
l4, = ax2.plot ([],[], 'b-', label='Rendezvous Distance')
l5, = ax2.plot ([],[], 'g--', label = 'Hayabusa velocity')
ax2.legend()
def init():
pt1.set_data([],[])
pt2.set_data([],[])
pt3.set_data([],[])
l3.set_data([],[])
l4.set_data([],[])
l5.set_data([],[])
return (pt1,pt2,pt3,l3,l4,l5,)
def animate(i):
pt1.set_data(x_earth[i],y_earth[i])
pt2.set_data(x_ryu[i],y_ryu[i])
pt3.set_data(x_hay[i],y_hay[i])
l3.set_data(t[:i],dist[:i])
l4.set_data(t[:i],dist[:i])
l5.set_data(t[:i],vel[:i])
return (pt1,pt2,pt3,l3,l4,l5,)
ani = FuncAnimation (fig, animate, fargs=(pt1,pt2,pt3,l3,l4,l5),frames =len(t),init_func=init,interval=25, blit=True)
plt.show()
Then, I would like to transform the left subplot into a 3D plot, by adding a third column to the data. But I don’t know how to do that.

Related

Need help on animating a 2-D trajectory using FuncAnimation

I have an array x_trj that has shape (50,3), and I want to plot a 2-D trajectory using the 1st and the 2nd columns of this array (x & y coordinates respectively). This trajectory will be on top of a circle. Here is my code so far:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2*np.pi, 100)
plt.plot(r*np.cos(theta), r*np.sin(theta), linewidth=5)
ax = plt.gca()
def animate(n):
# Plot resulting trajecotry of car
for n in range(x_trj.shape[0]):
line.set_xdata(x_trj[n,0])
line.set_ydata(x_trj[n,1])
return line,
anim = FuncAnimation(fig, animate,frames=200, interval=20)
However, the animation turns out to be a stationary figure. I checked out the Matplotlib animation example on the documentation page, but I still can't figure out what my animate(n) function should look like in this case. Can someone give me some hints?
The code below makes the following changes:
added some test data
in animate:
remove the for loop
only copy the part of the trajectory until the given n
in the call to FuncAnimation:
`frames should be equal to the given number of points (200 frames and 50 points doesn't work well)
interval= set to a larger number, as 20 milliseconds make things too fast for only 50 frames
added plt.show() (depending on the environment where the code is run, plt.show() will trigger the animation to start)
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
# create some random test data
x_trj = np.random.randn(50, 3).cumsum(axis=0)
x_trj -= x_trj.min(axis=0, keepdims=True)
x_trj /= x_trj.max(axis=0, keepdims=True)
x_trj = x_trj * 8 - 4
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2 * np.pi, 100)
r = 4
ax.plot(r * np.cos(theta), r * np.sin(theta), linewidth=5)
def animate(n):
line.set_xdata(x_trj[:n, 0])
line.set_ydata(x_trj[:n, 1])
return line,
anim = FuncAnimation(fig, animate, frames=x_trj.shape[0], interval=200)
# anim.save('test_trajectory_animation.gif')
plt.show()

How do I animate the following lines in matplotlib using my `ndarray`?

I have the following function to generate a brownian motion:
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
from scipy.stats import uniform, norm
def walk(n):
angle = uniform.rvs( size=(n,), loc=.0, scale=2.*np.pi )
r = norm.rvs( size=n )
x = np.cumsum( r * np.cos(angle) )
y = np.cumsum( r * np.sin(angle) )
return np.array((x, y, r, angle))
If I call this like brownian = walk(1000), and plot it like ax.plot( brownian[0,:], brownian[1,:], color='k'), it plots it correctly, but now I want to animate it and do this (taken from here):
# Length of array (or how long motion is modeled)
motionLength = 1000
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
xyMin = brownian.min() * 1.2
xyMax = brownian.max() * 1.2
plt.axis('equal')
ax = plt.axes(xlim=(xyMin,xyMax), ylim=(xyMin,xyMax))
line, = plt.plot([], [], lw=1, color='k')
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
def iterr(i):
line.set_data(brownian[:i,0],brownian[[:i,1]) # problem here?
return line,
anim = animation.FuncAnimation(fig, iterr, init_func=init, frames=motionLength,
interval=100, blit=True)
anim.save('test_animation_2.mp4', fps=120, bitrate=-1,
extra_args=['-vcodec', 'libx264'])
But I cannot seem to get it to work. I guess the problem lies in my building the lists in iterr, because either 1) I'm not taking the correct values with my slices, or 2) I'm not getting getting from walk what I think I'm getting.
How do I rewrite iterr to work with my ndarray.

The animation function must return a sequence of Artist objects

I am trying to test a Matplotlib animation example on my Pycharm which is listed as following:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
n = 1000
def update(curr):
if curr == n:
a.event_source.stop()
subplot1.cla()
subplot2.cla()
subplot3.cla()
subplot4.cla()
# subplots re-set to standard positions
x1 = np.random.normal(0, 1, n)
x2 = np.random.gamma(2, 1, n)
x3 = np.random.exponential(2, n)
x4 = np.random.uniform(0, 6, n)
# increment the number of bins in every 10th frame
# bins = 20 + curr // 10
bins = 10 + curr
# drawing the subplots
subplot1.hist(x1, bins=bins, alpha=0.5, color='red')
subplot2.hist(x2, bins=bins, alpha=0.5, color='green')
subplot3.hist(x3, bins=bins, alpha=0.5, color='blue')
subplot4.hist(x4, bins=bins, alpha=0.5, color='darkorange')
# set all ticks to null
subplot1.set_xticks([])
subplot2.set_xticks([])
subplot3.set_xticks([])
subplot4.set_xticks([])
subplot1.set_yticks([])
subplot2.set_yticks([])
subplot3.set_yticks([])
subplot4.set_yticks([])
# name the subplots
subplot1.set_title('Normal')
subplot2.set_title('Gamma')
subplot3.set_title('Exponential')
subplot4.set_title('Uniform')
# the title will change to reflect the number of bins
fig.suptitle('No of bins: {}'.format(bins))
# no redundant space left for saving into mp4
plt.tight_layout()
# Set up formatting for the movie files
Writer = animation.writers['ffmpeg']
writer = Writer(fps=15, metadata=dict(artist='Kuba Siekierzynski', title='Distributions'), bitrate=1800)
fig, ([subplot1, subplot2], [subplot3, subplot4]) = plt.subplots(2, 2)
a = animation.FuncAnimation(fig, update, interval=100, save_count=500, blit=True, frames=100)
# will only work with ffmpeg installed!!!
a.save('distributions.mp4', writer=writer)
plt.show()
the terminal shows the error:
raise RuntimeError('The animation function must return a '
RuntimeError: The animation function must return a sequence of Artist objects.
I have try to several time but cannot figure it out
As explained here for the case of init_func, since you are setting the parameter blit = True in the FuncAnimation definition, your update function has to return the plotting object. As an alternative, you can set blit = False; in that case, you get this animation:

How to do dynamic matplotlib plotting with pandas? [duplicate]

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)

Matplotlib animation update function isn't setting data

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

Categories

Resources