I am doing a small animation for a teaching course in which I need to draw a donut that moves around following a trajectory. However, I am having a problem with funcAnimation insofar as I don't manage to use blit to refresh the position. Here is my code with an example dataset
import numpy as np
x1 = np.random.randint(1,101,10)
y1 = np.random.randint(1,101,10)
The animation itself is done by
%matplotlib inline
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
fig, ax = plt.subplots()
def make_circle(r,x_c,y_c):
t = np.arange(0, np.pi * 2.0, 0.01)
t = t.reshape((len(t), 1))
x = r * np.cos(t) + x_c
y = r * np.sin(t) + y_c
return np.hstack((x, y))
def draw_donut(r_o,r_i,x_c,y_c):
Path = mpath.Path
inside_vertices = make_circle(r_i,x_c,y_c)
outside_vertices = make_circle(r_o,x_c,y_c)
codes = np.ones(len(inside_vertices), dtype=mpath.Path.code_type) * mpath.Path.LINETO
codes[0] = mpath.Path.MOVETO
vertices = np.concatenate((outside_vertices[::1],inside_vertices[::-1]))
all_codes = np.concatenate((codes, codes))
path = mpath.Path(vertices, all_codes)
patch = mpatches.PathPatch(path, facecolor='#885500', edgecolor='black')
return patch
def animate(i):
return ax.add_patch(draw_donut(10,5,x1[i],y1[i]))
anim = animation.FuncAnimation(fig, animate, frames = 10, interval=100, blit = False)
ax.set_xlim(-100, 100)
ax.set_ylim(-100, 100)
ax.set_aspect(1.0)
HTML(anim.to_jshtml())
If I set blit = True I get the error
TypeError: 'PathPatch' object is not iterable
blit = False just keep plotting more donuts. Any idea how to solve this?
Related
I am trying to save a 3d scatter plot animation where points appear one at a time. I made the animation work, but when I set the face colors of the points they do not take effect and all points appear blue. When I use the same color array but on static image, colors work well.
Animation Code:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation
import random
import seaborn as sns
import pandas as pd
import json
import os
from matplotlib.animation import FuncAnimation
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib import rc
from IPython.display import HTML
from itertools import product
x=[]
y=[]
for i in range(-80, 80, 10):
x.append(i)
y.append(i)
combs = list(product(x,y))
def obj(x, y):
global HISTORY
e = 2.718
res = 7*x*y/(e**(0.001*x**2 + 0.001*y**2))
return res
z = [obj(x,y) for x, y in combs]
x = [obj[0] for obj in combs]
y = [obj[1] for obj in combs]
data = [[x[i],y[i],z[i]] for i in range(len(x))]
cmap = sns.cubehelix_palette(as_cmap=True)
m = max(z) # Get the worst score so we can use it as the darkest area of the plot.
face_colors = np.array([cmap(i/m) for i in z]) # Map all of the values with cmap colors.
df = pd.DataFrame(data, columns=["x","y","z"])
fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
sc = ax.scatter([],[],[], alpha=0.5)
def update(i):
sc._offsets3d = (df.x.values[:i], df.y.values[:i], df.z.values[:i])
sc._facecolors3d = face_colors[:i]
sc._facecolors2d=sc._facecolors3d
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(min(x),max(x))
ax.set_ylim(min(y),max(y))
ax.set_zlim(min(z),max(z))
ani = matplotlib.animation.FuncAnimation(fig, update, frames=len(df), interval=70)
HTML(ani.to_html5_video())
When I do not use the animation and just call plt.scatter like this:
sc = ax.scatter(df.x.values,df.y.values,df.z.values, facecolors=face_colors)
My image works well:
How can I keep these colors in my animation as well?
Code for static image:
x=[]
y=[]
for i in range(-80, 80, 10):
x.append(i)
y.append(i)
combs = list(product(x,y))
def obj(x, y):
global HISTORY
e = 2.718
res = 7*x*y/(e**(0.001*x**2 + 0.001*y**2))
return res
z = [obj(x,y) for x, y in combs]
x = [obj[0] for obj in combs]
y = [obj[1] for obj in combs]
data = [[x[i],y[i],z[i]] for i in range(len(x))]
cmap = sns.cubehelix_palette(as_cmap=True)
m = max(z) # Get the worst score so we can use it as the darkest area of the plot.
face_colors = [cmap(i/m) for i in z] # Map all of the values with cmap colors.
df = pd.DataFrame(data, columns=["x","y","z"])
fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
sc = ax.scatter(df.x.values,df.y.values,df.z.values, facecolors=face_colors)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(min(x),max(x))
ax.set_ylim(min(y),max(y))
ax.set_zlim(min(z),max(z))
plt.show()
Might just be a typo. _facecolor3d instead of _facecolors3d try this:
def update(i):
sc._offsets3d = (df.x.values[:i], df.y.values[:i], df.z.values[:i])
sc._facecolor3d = face_colors[:i]
sc._edgecolor3d = face_colors[:i]
I have the problem that circle is not an iterable, how do I solve it? I'd like the parabolic shot to work with the circle.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
plt.style.use('dark_background')
ax = plt.axes(xlim=(-5, 25), ylim=(-5, 25))
patch = plt.Circle((1, -1), 0.5, fc='b') #figura
X = 30
Y = 30
gravity=9.81
angle=70
velocity=80
vx=velocity * np.cos(np.radians(angle))
vy=velocity * np.sin(np.radians(angle))
t=0
def setup():
patch.center = (10, 10)
ax.add_patch(patch)
return patch
def throwBall():
global X, Y, gravity, t,vx,vy
t +=0.02
X = vx*t
Y = 400 -(vy*t - (gravity/2)*t*t)
patch.center = (X, Y)
return patch
animen = animation.FuncAnimation(fig, throwBall,init_func=setup,frames=360,interval=15,blit=True)
HTML(animen.to_html5_video())
Pretty much what #JohanC said. According to the documentation, if blit=True "func must return an iterable of all artists that were modified or created." Therefore, you must return a list or a tuple of Artists, even if there only one artist modified.
I've also made some cosmetic changes. If you are incrementing t in your update function, why not pass directly the value of t as an argument to your update function using frames = np.arange(0,7.2,0.02) or something equivalent.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from matplotlib.patches import Circle
fig = plt.figure()
ax = fig.add_subplot(111, xlim=(0, 500), ylim=(0, 500), aspect='equal')
patch = Circle((1, -1), 10, fc='b')
ax.add_patch(patch)
X = 30
Y = 30
gravity=9.81
angle=70
velocity=80
vx=velocity * np.cos(np.radians(angle))
vy=velocity * np.sin(np.radians(angle))
def setup():
patch.set_center((X, Y))
return (patch,)
def throwBall(t):
global X, Y
X = vx*t
Y = 400 -(vy*t - (gravity/2)*t*t)
patch.set_center((X, Y))
return (patch)
ani = animation.FuncAnimation(fig, throwBall, init_func=setup, frames=np.arange(0,7.2,0.02), interval=20, blit=True)
The problem
I need the animation to go fluid, but it is ploting frame by frame. The code is running in Jupyter Notebook.
Here are the libraries
import numpy as np
from matplotlib import pyplot as plt
from scipy import signal as sp
Creating the functions to convolve
t_ini=0
t_final = 11
dt=0.1
t = np.arange(t_ini,t_final,dt)
expo = np.exp(-t)*np.piecewise(t,t>=0,[1,0])
t1 = np.arange(0,10,0.1)
s = np.sin(t1)
conv_=sp.convolve(s,expo,'full')
n_conv=np.arange(min(t1)+min(t),max(t1)+max(t)+0.1,0.1)
y = [0] * len(conv_)
t2 = [0] * len(n_conv)
Here is the plotting
i = 0
for x in n_conv:
y[i] = conv_[i]
plt.cla()
t2[i] = n_conv[i]
plt.plot(t2,y)
plt.show()
plt.pause(0.5)
i = i+1
matplotlib provides for instance ArtistAnimation that allows a seamless animation of precalculated graphs. I just added a couple of lines to your code. Only thing I changed was to use enumerate to improve your code
import numpy as np
from matplotlib import pyplot as plt
from scipy import signal as sp
import matplotlib.animation as anim
t_ini=0
t_final = 11
dt=0.1
t = np.arange(t_ini,t_final,dt)
expo = np.exp(-t)*np.piecewise(t,t>=0,[1,0])
t1 = np.arange(0,10,0.1)
s = np.sin(t1)
conv_=sp.convolve(s,expo,'full')
n_conv=np.arange(min(t1)+min(t),max(t1)+max(t)+0.1,0.1)
y = [0] * len(conv_)
t2 = [0] * len(n_conv)
#prepare figure for display
fig = plt.figure()
ax = plt.axes()
#create list to collect graphs for animation
img = []
for i, x in enumerate(n_conv):
y[i] = conv_[i]
t2[i] = n_conv[i]
#append new graphs to list
newpic, = ax.plot(t2, y, c= "blue")
img.append([newpic])
#animate the list of precalculated graphs
ani = anim.ArtistAnimation(fig, img, interval = 50)
plt.show()
Output:
I want arrows next to a curve. For example:
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(0,4*np.pi,10000)
Y = np.sin(X)
shift = 0.1
seg_size = 300
i = 0
plt.plot(X,Y,color='blue')
while i +seg_size < len(X):
x = X[i:i+seg_size]
y = Y[i:i+seg_size]+shift
plt.plot(x,y,color='black')
#input here command for arrow head
i += seg_size*2
plt.show()
I tried to calculate the angle next of the line at the end of the curve and plot the arrow head lines, but I'm doing something wrong and the arrow head are deformed. Any hints?
The FancyArrowPatch class takes a path as an argument, so I thought that you could use that.
1) For each line segment, create a matplotlib.path.Path instance.
2) Use path instance to draw arrow.
import numpy as np
import matplotlib.pyplot as plt; plt.ion()
from matplotlib.patches import FancyArrowPatch, PathPatch
from matplotlib.path import Path
def create_path(x,y):
vertices = zip(x,y)
codes = [Path.MOVETO] + (len(vertices)-1) * [Path.CURVE3]
return Path(vertices, codes)
X = np.linspace(0,4*np.pi,10000)
Y = np.sin(X)
fig, ax = plt.subplots(1,1)
ax.plot(X,Y,color='blue')
shift = 0.1
seg_size = 300
i = 0
while i +seg_size < len(X):
x = X[i:i+seg_size]
y = Y[i:i+seg_size]+shift
path = create_path(x,y)
# for testing path
# patch = PathPatch(path, facecolor='none', lw=2)
# ax.add_patch(patch)
arrow = FancyArrowPatch(path=path, color='r')
ax.add_artist(arrow)
i += seg_size*2
Unfortunately, that does not work, as the path that is passed to FancyArrowPatch cannot have more than 2 segments (not documented, but there is a check in ensure_quadratic_bezier).
So you have to cheat. Below I use the last 2 points of each segment to draw the arrow.
import numpy as np
import matplotlib.pyplot as plt; plt.ion()
from matplotlib.patches import FancyArrowPatch
X = np.linspace(0,4*np.pi,10000)
Y = np.sin(X)
fig, ax = plt.subplots(1,1)
ax.plot(X,Y,color='blue')
shift = 0.1
seg_size = 300
i = 0
while i +seg_size < len(X):
x = X[i:i+seg_size]
y = Y[i:i+seg_size]+shift
ax.plot(x, y, 'k')
posA, posB = zip(x[-2:], y[-2:])
edge_width = 2.
arrowstyle = "fancy,head_length={},head_width={},tail_width={}".format(2*edge_width, 3*edge_width, edge_width)
arrow = FancyArrowPatch(posA=posA, posB=posB, arrowstyle=arrowstyle, color='k')
ax.add_artist(arrow)
i += seg_size*2
How about using the
numpy.annotate()
function?
See examples of arrows created by the pyplot.annotate() function here:
https://matplotlib.org/examples/pylab_examples/annotation_demo.html
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()