I want to create an animation of a moving sphere in matplotlib. For some reason it isnt working:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + 10
y0 = r * np.outer(np.sin(u), np.sin(v)) + 10
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + 50
def init():
ax.plot_surface(x0,y0,z0)
return fig,
def animate(i):
ax.plot_surface(x0 + 1, y0 + 1, z0 + 1)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 90, interval = 300)
plt.show()
Here, I have attempted to move the sphere by (1,1,1) in each new iteration, but it fails to do so.
There are a couple of mistakes with your approach:
In your animate function you are adding a sphere at each iteration. Unfortunately, Poly3DCollection objects (created by ax.plot_surface) cannot be modified after they have been created, hence to animate a surface we need to remove the surface of the previous iteration and add a new one.
In your animation the sphere didn't move because at each iteration you were adding a new sphere at the same location as the previous one.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + 10
y0 = r * np.outer(np.sin(u), np.sin(v)) + 10
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + 50
surface_color = "tab:blue"
def init():
ax.plot_surface(x0, y0, z0, color=surface_color)
return fig,
def animate(i):
# remove previous collections
ax.collections.clear()
# add the new sphere
ax.plot_surface(x0 + i, y0 + i, z0 + i, color=surface_color)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 90, interval = 300)
plt.show()
Related
I run the following code to animate a moving sphere, in which the coordinates are in a text file:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
df = pd.read_csv('/path/to/text/file', sep=" ", header=None)
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + df[1][0]
y0 = r * np.outer(np.sin(u), np.sin(v)) + df[2][0]
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + df[3][0]
surface_color = "tab:blue"
def init():
ax.plot_trisurf(x0, y0, z0, linewidth=0, antialiased=False)
return fig,
def animate(i):
# remove previous collections
ax.collections.clear()
x = df[1][i]
y = df[2][i]
z = df[3][i]
# add the new sphere
ax.plot_trisurf(x, y, z, linewidth=0, antialiased=False)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 500, interval = 2)
plt.show()
I get the following error "ValueError: x and y must be equal-length 1D arrays" even though I'm sure the arrays are of equal size. How do I make them equal size and solve this error?
As a sample of whats in the file:
0.196812 19.992262 29.989437 30.040883 0.080273 39.999358 30.009271 30.052325
0.288626 19.998165 29.986778 30.083568 0.305931 39.993330 30.011351 30.126911
0.080401 20.012453 29.982994 30.138681 0.224338 39.986476 30.010048 30.204666
0.380893 20.017042 29.984149 30.196864 0.289713 39.984835 30.009015 30.285159
0.396571 20.009539 29.998625 30.259610 0.350441 39.993791 30.017738 30.361558
0.647959 20.012771 29.995641 30.328414 0.275493 39.992826 30.019380 30.433242
0.741711 20.000002 29.978545 30.397738 0.248958 39.992041 30.010427 30.508367
0.867323 19.991656 29.971294 30.464908 0.313612 39.999097 30.004667 30.591674
The text file is very large, around 20,000 lines.
If the surface you are about to plot has a parametric equation (such as a sphere), use the meshgrid approach (x, y, z must be 2D arrays) and call ax.plot_surface. Instead, you used 1D arrays and later called ax.plot_trisurf: this function is better suited when it's not easy to represent the surface with a meshgrid approach (which is not your case). Do not complicate your life: keep it simply!
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
df = pd.read_csv('path/to/file', sep=" ", header=None)
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 40)
v = np.linspace(0, np.pi, 20)
u, v = np.meshgrid(u, v)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + df[1][0]
y0 = r * np.outer(np.sin(u), np.sin(v)) + df[2][0]
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + df[3][0]
surface_color = "tab:blue"
def init():
ax.plot_surface(x0, y0, z0, color=surface_color)
return fig,
def animate(i):
# remove previous collections
ax.collections.clear()
x = df[1][i]
y = df[2][i]
z = df[3][i]
# add the new sphere
ax.plot_surface(x0 + x, y0 + y, z0 + z, color=surface_color)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 500, interval = 2)
plt.show()
I can use the set_xdata and set_ydata functions to update an existing matplotlib plot. But after updating I want to recenter the plot so that all the points fall into the "view" of the plot.
In the below example, the y data keeps getting bigger but the zoom level of the plot remains same so the data points quickly get out of the scope.
import time
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.ion()
figure, ax = plt.subplots(figsize=(10, 8))
(line1,) = ax.plot(x, y)
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
for i in range(1000):
new_y = np.sin(x - 0.5 * i) * i
line1.set_xdata(x)
line1.set_ydata(new_y)
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(0.1)
Adding ax.relim() and ax.autoscale() fixes the issue
import time
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.ion()
ax: plt.Axes
figure, ax = plt.subplots(figsize=(10, 8))
(line1,) = ax.plot(x, y)
ax.autoscale(True)
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
for i in range(1000):
new_y = np.sin(x - 0.5 * i) * i
line1.set_xdata(x)
line1.set_ydata(new_y)
# Rescale axes limits
ax.relim()
ax.autoscale()
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(0.1)
np.sin(x - 0.5 * i) has multiplied by i, which can be 1000. One alternative is to make the y-axis have a limit greater than 1000. So, you can include plt.ylim([-1100,1100]):
import time
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.ion()
figure, ax = plt.subplots(figsize=(10, 8))
(line1,) = ax.plot(x, y)
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.ylim([-1100,1100])
for i in range(1000):
new_y = np.sin(x - 0.5 * i) * i
line1.set_xdata(x)
line1.set_ydata(new_y)
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(0.1)
I am currently trying to animate the dynamics of a typical saddle node bifurcation ode: dx/dt = r + x^2. Snapshots at specific values of r are realised with the streamplot function from r = -1 to 1. Unfortunately the init function and the animate function are not working properly because .set_array does not work for streamplots. I am also not sure how to update the streams at each iteration in the animate function. My question is how I should modify the animate and init function so that the funcanimation function gives a proper animated plot of the flows.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
nx, ny = .02, .02
x = np.arange(-15, 15, nx)
y = np.arange(-10, 10, ny)
X, Y = np.meshgrid(x, y)
dy = -1 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
color = dyu
fig, ax = plt.subplots()
stream = ax.streamplot(X,Y,dxu, dyu, color=color, density=2, cmap='jet',arrowsize=1)
ax.set_xlabel('t')
ax.set_ylabel('x')
def init():
stream.set_array([])
return stream
def animate(iter):
dy = -1 + iter * 0.01 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
stream.set_array(dyu.ravel())
return stream
anim = animation.FuncAnimation(fig, animate, frames=100, interval=50, blit=False, repeat=False)
plt.show()
I worked around this by clearing the lines and arrows in every iteration:
ax.collections = [] # clear lines streamplot
ax.patches = [] # clear arrowheads streamplot
So, I modified your code like this:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
nx, ny = .02, .02
x = np.arange(-15, 15, nx)
y = np.arange(-10, 10, ny)
X, Y = np.meshgrid(x, y)
dy = -1 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
color = dyu
fig, ax = plt.subplots()
stream = ax.streamplot(X,Y,dxu, dyu, color=color, density=2, cmap='jet',arrowsize=1)
ax.set_xlabel('t')
ax.set_ylabel('x')
def animate(iter):
ax.collections = [] # clear lines streamplot
ax.patches = [] # clear arrowheads streamplot
dy = -1 + iter * 0.01 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
stream = ax.streamplot(X,Y,dxu, dyu, color=color, density=2, cmap='jet',arrowsize=1)
print(iter)
return stream
anim = animation.FuncAnimation(fig, animate, frames=100, interval=50, blit=False, repeat=False)
anim.save('./animation.gif', writer='imagemagick', fps=60)
# plt.show()
CAUTION: #SebastianBeyer's previously working answer no longer works in 2022. For unknown (and presumably indefensible) reasons, Matplotlib now prohibits attempts to manually replace the axes.patches list by raising a non-human-readable exception resembling:
AttributeError: can't set attribute 'patches'
Thankfully, yet another working workaround exists. Inspired by #Sheldore's working answer here, you must now iteratively search for and remove all matplotlib.patches.FancyArrowPatch child artists from the streamplot's axes: e.g.,
# Rather than this...
ax.patches = [] # clear arrowheads streamplot
# ...you must now do this.
from matplotlib.patches import FancyArrowPatch
for artist in ax.get_children():
if isinstance(artist, FancyArrowPatch):
artist.remove()
In full, the post-2020 working solution is now:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from matplotlib.patches import FancyArrowPatch
nx, ny = .02, .02
x = np.arange(-15, 15, nx)
y = np.arange(-10, 10, ny)
X, Y = np.meshgrid(x, y)
dy = -1 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
color = dyu
fig, ax = plt.subplots()
stream = ax.streamplot(X,Y,dxu, dyu, color=color, density=2, cmap='jet',arrowsize=1)
ax.set_xlabel('t')
ax.set_ylabel('x')
def animate(iter):
ax.collections = [] # clear lines streamplot
# Clear arrowheads streamplot.
for artist in ax.get_children():
if isinstance(artist, FancyArrowPatch):
artist.remove()
dy = -1 + iter * 0.01 + Y**2
dx = np.ones(dy.shape)
dyu = dy / np.sqrt(dy**2 + dx**2)
dxu = dx / np.sqrt(dy**2 + dx**2)
stream = ax.streamplot(X,Y,dxu, dyu, color=color, density=2, cmap='jet',arrowsize=1)
print(iter)
return stream
anim = animation.FuncAnimation(fig, animate, frames=100, interval=50, blit=False, repeat=False)
anim.save('./animation.gif', writer='imagemagick', fps=60)
# plt.show()
Thanks alot, post-2020 matplotlib. </facepalm>
I am having problems with matplotlibs 3dplot. If I plot two 3d objects, the one that is supposed to be in front is sometimes in the back. Take for example the following code
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as pl
import numpy as np
fig = pl.figure()
ax = fig.add_subplot(111, projection='3d')
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
X = 10 * np.outer(np.cos(u), np.sin(v))
Y = 10 * np.outer(np.sin(u), np.sin(v))
Z = 10 * np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_surface(X+10,Y,Z, rstride=4, cstride=4, color='b')
u=np.linspace(-2,2,100)
X = 10 * np.outer(np.ones(len(u)), u)
Y = 10 * np.outer(u, np.ones(len(u)))
Z = 10 * np.zeros((len(u), len(u)))
ax.plot_surface(Z,X,Y, rstride=4, cstride=4, color='b')
pl.show()
It is supposed to plot a plane, with a sphere in from of it, but the sphere appears to be behind the plane.
I have a three-variable function myfunc that is generated inside three for loops. I want to draw a contour plot of y vs x and animate this for different times t. However, I've looked at the various matplotlib examples on the webpage, and am still unsure of how to do this.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import animation
def myfunc(x,y,t):
w = 0.5*x + y + 4*np.sin(1.8*t)
return w
xlist = np.linspace(0,10,10)
ylist = np.linspace(-1,1,10)
tlist = np.linspace(0,50,50)
plt.figure()
for t in tlist:
for x in xlist:
for y in ylist:
w = myfunc(x,y,t)
w_vec = np.array(w)
w_contour = w_vec.reshape((xlist.size, ylist.size))
w_plot = plt.contourf(ylist,xlist,w_contour)
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.show()
Edit: I quite like the look of dynamic_image2.py in this tutorial. This seems to get things moving, but the axes are wrong:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
def f(x,y,t):
return 0.5*x + np.sin(y) + 4*np.sin(1.8*t)
x = np.linspace(0, 10, 10)
y = np.linspace(-1, 1, 10).reshape(-1, 1)
tlist = np.linspace(0,50,50)
ims = []
for t in tlist:
x += np.pi / 15.0
y += np.pi / 20.0
im = plt.imshow(f(x,y,t))
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=20, blit=True,
repeat_delay=1000)
plt.show()