How to move a patch along a path?

Question:

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

Asked By: Adrials

||

Answers:

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()
Answered By: medium-dimensional

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()
Answered By: fdireito

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 !

Update
Here is the link to increase vertices in order to be more precise: How to increase the number of vertices?

Answered By: Adrials