I am trying to animate a patch.Rectangle object using matplotlib. I want the said object to move along a path.Arc.
A roundabout way to do this would be (approximately) :
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(0, 10), ylim=(0, 10))
# generate the patch
patch = mpat.Rectangle((5, 5), 1, 4)
patch.rotation_point = 'center'
# generate the path to follow
path_to_follow = mpat.Arc((5, 5), 2, 2)
ax.add_patch(path_to_follow)
def init():
patch.set(x=5, y=5)
ax.add_patch(patch)
return patch,
def animate(i, ax):
new_x = 5 + np.sin(np.radians(i)) - 0.5 # parametric form for the circle
new_y = 5 + np.cos(np.radians(i)) - 2
patch.set(x=new_x, y=new_y, angle=90-i)
return patch,
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=10,
blit=True)
plt.show()
The rectangle follows a circle, but a parametric one. Would it be possible to make it follow any path?
In other words, I would like to know if there are other simpler methods to do this (make my patch follow my path, here a circle), and if that could be generalized to other path.
Thanks in advance !
I searched into the matplotlib doc for a methods which gives the parametric form for a given path (but apparently there is not), or for a methods which directly move a patch along a path (obviously, there was not).
Here is one way to use matplotlib.path.Path to generate a path, whose vertices can be obtained using the method cleaned, to move a patch along it.
I have tried to showcase how blue and red colored Rectangles can be moved along a (blue) linear path and a (red) circular path, respectively:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation, path
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(0, 10), ylim=(0, 10))
# generate a linear path
path1 = np.column_stack((np.arange(500)/50, np.arange(500)/50))
# generate a circular path
circle = path.Path.circle(center=(5, 5), radius=1)
path2 = circle.cleaned().vertices[:-3]
# create patches
patch1 = mpat.Rectangle((0, 0), 1, 3)
patch2 = mpat.Rectangle((0, 0), 1, 3, color='red', fill=None)
# plot path vertices
plt.scatter(x=path1[:, 0], y=path1[:, 1], s=2)
plt.scatter(x=path2[:, 0], y=path2[:, 1], color='red', s=2)
def init():
patch1.set(x=0, y=0)
patch2.set(x=5, y=6)
ax.add_patch(patch1)
ax.add_patch(patch2)
return [patch1, patch2]
def animate(i, ax):
j = i % 500 # path1 has shape (500, 2)
k = (i % 16) # path2 has shape (16, 2)
patch1.set(x=path1[j][0], y=path1[j][1], angle=-j)
patch2.set(x=path2[k][0], y=path2[k][1], angle=-k)
return [patch1, patch2]
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=100,
blit=True)
plt.show()
If your path is some collection of coordinates, you can not only translate the rectangle, but also compute the vector from one point to the next and update the rectangle angle accordingly. In the next example (mix of your code with mine), we generate from the beginning the path, but it could be instead live read from some external source.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
# create some path with pairs of X and Y coordinates
t = np.linspace(0, 360, 361)
X = 5. * np.sin(np.radians(t))
Y = t * (t-360*2) / 8000 + 7.5
# create x and y lists to store the position pairs as they are plotted
x = [X[0],]
y = [Y[0],]
# plotting
fig, ax = plt.subplots()
ax.set(xlim=(-10, 10), ylim=(-10, 10))
patch = mpat.Rectangle((x[0], y[0]), 1, 3)
def init():
patch.set(x=x[0], y=y[0])
ax.plot(X, Y)
ax.add_patch(patch)
return patch,
def animate(i, ax):
new_x = X[i] # we are getting from pre-generated data,
new_y = Y[i] # but it could be some function, or even live external source
vx = new_x - x[-1] # calculate the vectors, which are used for angle
vy = new_y - y[-1]
x.append(new_x) # store for next iteration, so that we can calculate the vectors
y.append(new_y)
new_alfa = np.degrees(np.arctan2(vy, vx))
patch.set(x=new_x, y=new_y, angle = new_alfa)
return patch,
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=20,
blit=True)
plt.show()
Thanks a lot for your answers, here is the code I made (mixing the two answers) and which does exactly what I wanted, if it helps anyone :
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(-6, 6), ylim=(-6, 6))
# generate a circular path
circle = mpat.Arc((0, 0), 10, 10, theta1=20, theta2=220, color='green')
path = circle.get_transform().transform_path(circle.get_path()).cleaned().vertices[:-3] # get_path is not enough because of the transformation, so we apply to the path the same transformation as the circle has got from the identity circle
ax.add_patch(circle)
# create patch
patch = mpat.Rectangle((0, 0), 1, 4, color='red', fill=None)
patch.rotation_point = 'center'
# plot path vertices
plt.scatter(x=path[:, 0], y=path[:, 1], color='red', s=2)
shape = len(path)
def init():
patch.set(x=5-0.5, y=6-2) # we substract to get to the center
ax.add_patch(patch)
return [patch]
def animate(i, ax):
k = i % shape
new_x = path[k][0]
new_y = path[k][1]
vx = new_x - path[k-1][0]
vy = new_y - path[k-1][1]
patch.set(x=new_x-0.5, y=new_y-2, angle=np.degrees(np.arctan2(vy, vx) + 90))
return [patch]
anim = animation.FuncAnimation(fig, animate,
init_func=init,
fargs=[ax],
frames=360,
interval=200,
blit=True,
repeat=False)
plt.show()
To improve this, is anyone know how to increase the number of points given? In other words, increase the len of path to be more precise in moving the rectangle.
Thanks in advance !
Related
Context: I am trying to create a teaching demo tool to show how the iteration guesses through a set of points to ultimately arrive at the root of an equation
Problem: I want to animate using matplotlib to show the iterations viusally. Specifically, given a curve (see along side) and an initial guess (say 1.5 in this particular case), I want to compose an animation of 3 scenes:
draw a vertical line at x = guess (=1.5), to meet the curve at y= 9 (aka value).
Draw a line through (guess, value) with a slope 'm' (generated by my code). Delete the vertical line at this stage and keep the second line
Delete the second line after a pause
For illustration, here is the image
Additionally here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
xdata2, ydata2 = [], []
ln, = plt.plot([], [])
def init():
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
return [ln]
def update(frame):
xdata.append(frame)
ydata.append(frame ** 3 + 4 * frame ** 2 + frame - 6)
ln.set_data(xdata, ydata)
return [ln]
def update2(frame):
xdata2.append(1.5)
ydata2.append(frame)
ln.set_data(xdata2,ydata2)
return[ln]
ani = FuncAnimation(fig, update, frames=np.linspace(-3, 3, 100),
init_func=init, blit=True)
ani2 = FuncAnimation(fig, update2, frames=np.linspace(0, 3, 100),blit=True)
plt.show()
This is a simplified version of the problem that I am trying to solve and is not part of the code that involves the calculation of the iterations etc. For now I am just trying to draw the curve in Update and post that, draw a vertical line at x=1.5.
Results: At my end, the entire animation is a set of flickering where it is apparent that matplotlib switches "thread context" very rapidly between the two FuncAnimation calls
The desired animation in your next question can be achieved in the form of drawing a curve as a base graph, adding line graphs frame by frame, and deleting that graph object when necessary.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import time
fig, ax = plt.subplots()
x = np.linspace(-3, 3, 100)
y = x ** 3 + 4 * x **2 + x -6
xx = x[74]
yy = y[74]
#print(xx,yy)
xx2 = x[65]
yy2 = y[65]
#print(xx2,yy2)
ln, = ax.plot(x, y)
ln2, = ax.plot([], [])
ln3, = ax.plot([],[])
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
# Move axes center and spines off
ax.spines[['top', 'right']].set_visible(False)
ax.spines[['left', 'bottom']].set_position('center')
# Show ticks axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
def update(i):
ln2.set_data((xx, xx), (0, yy))
ln2.set_color('k')
if i == 2:
ln3.set_data((xx2, xx), (yy2, yy))
ln3.set_color('red')
ln3.set_width=3
if i >=3:
ln2.set_data([],[])
ln3.set_data([],[])
return ln2,ln3
ani = FuncAnimation(fig, update, frames=[0,1,2,3], interval=500, blit=True, repeat=True)
plt.show()
I want to animate the trajectory of a circle (ball) defined by y = -t^2 + 11t - 18. Basically it would just be bouncing up and down (i.e. no change in x). Its intercepts are (2,0) and (9,0) so the animation should start at time t = 2 as it leaves the ground and end at time t = 9 as it returns to the ground. I am also hoping that a running display of the time could also be included in the animation. So basically between times t=0 and t=2, the ball would just be on the ground. This is the code I have so far but it doesn't seem to make sense. I'm not sure whether the animation is just going too fast.
%matplotlib notebook
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
fig.set_dpi(100)
fig.set_size_inches(3, 3)
ax = plt.axes(xlim=(0, 10), ylim=(0, 15))
patch = plt.Circle((5, 0), 0.2, fc='r')
def init():
patch.center = (5, 0)
ax.add_patch(patch)
return patch,
def animate(i):
x, y = patch.center
x = 0 * i+5
y = - i**2 + 11 * i - 18
patch.center = (x, y)
return patch,
anim = animation.FuncAnimation(fig, animate,
init_func=init,
frames=3600,
interval=1,
blit=True)
plt.show()
Note this is a follow-up question of How to make an animation of a Lissajous curve;
My first idea was to edit my original question and ask for the animation, but I understand and respect SO way of operating. So the best is making another question.
I want to make an animation of the curve (where you incrementally draw it) with parametrization: x(t) = sin(3t) and y(y) = sin(4t) where t[0, 2pi].
For doing so I would add the code:
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'b')
def init():
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
x.append(np.sin(4*frame))
y.append(np.sin(3*frame))
ln.set_data(x, y)
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
The problem is that with this code it doesn't draw the whole curve from scratch. What does is overdrawing it, getting overlapping.
How can I draw it from scratch (i.e. starting with white background)? I've been thinking about an if else but got nothing.
Thanks
EDIT
Let me show you the whole code:
%matplotlib notebook
import matplotlib.pyplot as plt
import math
import numpy as np
from matplotlib.animation import FuncAnimation
# set the minimum potential
rm = math.pow(2, 1 / 6)
t = np.linspace(-10, 10, 1000, endpoint = False)
x = []
y = []
for i in t: #TypeError 'int' object is not iterable
x_i = np.sin( 3 * i )
y_i = np.sin( 4 * i )
x.append(x_i)
y.append(y_i)
# set the title
plt.title('Plot sin(4t) Vs sin(3t)')
# set the labels of the graph
plt.xlabel('sin(3t)')
plt.ylabel('sin(4t)')
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'b')
def init():
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
x.append(np.sin(4*frame))
y.append(np.sin(3*frame))
ln.set_data(x, y)
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
# display the graph
plt.show()
This is the image I get at the beginning (screenshot taken after approximately 1s after started running; that's why you see that funny line): https://imgur.com/a/bNoViDA. As you can see it doesn't start from scratch (i.e not from white background)
This is the plot I get at the end: https://imgur.com/a/WQHHUk9
I am seeking getting that ending point but drawing everything from scratch, without starting with the shown plot.
I'm trying to animate a 2d path, and I would like it to have a sort of "Disappearing Tail", where at any given time, it shows only the last 5 (for example) particles.
What I currently have is quite far from this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
sample_path = np.random.uniform(size=(100,2))
fig, ax = plt.subplots()
x = np.arange(-1, 1, 0.01) # x-array
line, = ax.plot(sample_path[0,0], sample_path[0,1])
def connect(i):
(x0,y0) = sample_path[i-1,:]
(x1,y1) = sample_path[i,:]
plt.plot([x0,x1],[y0,y1],'ro-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), init_func=init,
interval=200, blit=True)
HTML(ani.to_html5_video())
This retains a 'full tail', i.e. after k steps, it shows all of the first k locations.
Is there a way to adapt what I've got so that the animation only shows the most recent history of the particle?
You would probably want to update the line instead of adding a lot of new points to the plot. Selecting the 5 most recent points can be done via indexing, e.g.
sample_path[i-5:i, 0]
Complete example, where we take care not to have a negative index and also don't use blit (which does not make sense if saving the animation).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
r = np.sin(np.linspace(0,3.14,100))
t = np.linspace(0, 10, 100)
sample_path = np.c_[r*(np.sin(t)+np.cos(t)), r*(np.cos(t)-np.sin(t))]/1.5
fig, ax = plt.subplots()
line, = ax.plot(sample_path[0,0], sample_path[0,1], "ro-")
def connect(i):
start=max((i-5,0))
line.set_data(sample_path[start:i,0],sample_path[start:i,1])
return line,
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
ani = animation.FuncAnimation(fig, connect, np.arange(1, 100), interval=200)
plt.show()
Not as good as ImportanceOfBeingErnest's answer technically, but it still does the job and looks pretty cool, just plot the latest points and clear the old ones. I added a few more and sped it up because I thought it looked better with a longer trail.
def connect(i):
#clear current points
plt.clf()
#prevent axis auto-resizing
plt.plot(0,0)
plt.plot(1,1)
#generate points to plot
(x0,y0) = sample_path[i-8,:]
(x1,y1) = sample_path[i-7,:]
(x2,y2) = sample_path[i-6,:]
(x3,y3) = sample_path[i-5,:]
(x4,y4) = sample_path[i-4,:]
(x5,y5) = sample_path[i-3,:]
(x6,y6) = sample_path[i-2,:]
(x7,y7) = sample_path[i-1,:]
(x8,y8) = sample_path[i,:]
#plot old points
plt.plot([x0,x1,x2,x3,x4,x5,x6,x7],[y0,y1,y2,y3,y4,y5,y6,y7],'ro-')
#plot new point in blue
plt.plot([x7,x8],[y7,y8],'bo-')
return line,
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, connect, frames=np.arange(1, 100),
init_func=init,
interval=50, blit=True)
HTML(ani.to_html5_video())
I am using an artist animation method with 5 subplots. There is one static plot on the left, with 3 smaller animated imshow plots to the right (the colorbar is the 5th). I have successfully used ConnectionPatch to connect subplots to show where the data is coming from, but only on static plots. No matter what I try, I can't seem to get the patches to show up on the animation. I've tried to include the patch in the image artist list, tried to update the figure with the artist instead of the axis (which I guess doesn't make much sense), among other things. It will be very difficult to extract a working example due to the complexity of the plot, but maybe someone has a tip.
Could setting the facecolor to 'white' with the animation savefig_kwargs be covering up the connector lines? If so, how do I change the z order of the patch/facecolor?
Without a minimal working example, I can only tell you that it is possible to use a ConnectionPatch in an animation. However, as seen below, one has to recreate it for every frame.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)
import matplotlib.gridspec as gridspec
from matplotlib.patches import ConnectionPatch
import matplotlib.animation
plt.rcParams["figure.figsize"] = np.array([6,3.6])*0.7
x = np.linspace(-3,3)
X,Y = np.meshgrid(x,x)
f = lambda x,y: (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)+1.5
Z = f(X,Y)
bins=np.linspace(Z.min(), Z.max(), 16)
cols = plt.cm.PuOr((bins[:-1]-Z.min())/(Z.max()-Z.min()))
gs = gridspec.GridSpec(2, 2, height_ratios=[34,53], width_ratios=[102,53])
fig = plt.figure()
ax=fig.add_subplot(gs[:,0])
ax2=fig.add_subplot(gs[0,1])
ax3=fig.add_subplot(gs[1,1])
ax.imshow(Z, cmap="PuOr")
rec = plt.Rectangle([-.5,-.5], width=9, height=9, edgecolor="crimson", fill=False, lw=2)
conp = ConnectionPatch(xyA=[-0.5,0.5], xyB=[9.5,4], coordsA="data", coordsB="data",
axesA=ax3, axesB=ax, arrowstyle="-|>", zorder=25, shrinkA=0, shrinkB=1,
mutation_scale=20, fc="w", ec="crimson", lw=2)
ax3.add_artist(conp)
ax.add_artist(rec)
im = ax3.imshow(Z[:9,:9], cmap="PuOr", vmin=Z.min(), vmax=Z.max())
ticks = np.array([0,4,8])
ax3.set_yticks(ticks); ax3.set_xticks(ticks)
ax2.hist(Z[:9,:9].flatten(), bins=bins)
def ins(px,py):
global rec, conp, histpatches
ll = [px-.5,py-.5]
rec.set_xy(ll)
conp.remove()
conp = ConnectionPatch(xyA=[-0.5,0.5], xyB=[px+9.5,py+4], coordsA="data", coordsB="data",
axesA=ax3, axesB=ax, arrowstyle="-|>", zorder=25, shrinkA=0, shrinkB=1,
mutation_scale=20, fc="w", ec="crimson", lw=2)
ax3.add_patch(conp)
data = Z[px:px+9,py:py+9]
im.set_data(data)
ax3.set_xticklabels(ticks+px)
ax3.set_yticklabels(ticks+py)
ax2.clear()
ax2.set_ylim(0,60)
h, b_, patches = ax2.hist(data.flatten(), bins=bins, ec="k", fc="#f1a142")
[pat.set_color(cols[i]) for i, pat in enumerate(patches)]
def func(p):
px,py = p
ins(px, py)
phi = np.linspace(0.,2*np.pi)
r = np.sin(2*phi)*20+np.pi/2
xr = (r*np.cos(phi)).astype(np.int8)
yr = (r*np.sin(phi)).astype(np.int8)
plt.subplots_adjust(top=0.93,bottom=0.11,left=0.04,right=0.96,hspace=0.26,wspace=0.15)
frames = np.c_[xr+20, yr+20]
ani = matplotlib.animation.FuncAnimation(fig, func, frames=frames, interval=300, repeat=True)
plt.show()