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, 15))
patch = mpat.Rectangle((5, 5), 1, 3)
def init():
patch.set(x=5, y=5)
ax.add_patch(patch)
return patch,
def animate(i, ax):
new_x = 5 3 * np.sin(np.radians(i))
new_y = 5 3 * np.cos(np.radians(i))
patch.set(x=new_x, y=new_y, angle=-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).
CodePudding user response:
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()
CodePudding user response:
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()