Issue
I'm plotting some points moving in a 2D space and I want to add circles around them. I have all the x,y coordinates for the points around which the circles are supposed to be plotted, but I am unsure how to add this into the animate function.
Any help would be appreciated!
import matplotlib.pyplot as plt
from launcher import sol, T, dt
from matplotlib.animation import FuncAnimation, PillowWriter
from create import p, N, walls_x, walls_y
# convert sol.y to plottable array('s) using smart slicing
def sol_convert(sol):
#T = len(sol.t) # amount of time steps
x = sol.y[0::4,:] # there are 4 values p.p. (x,y,vx,vy)
y = sol.y[1::4,:]
vx = sol.y[2::4,:]
vy = sol.y[3::4,:]
return x, y, vx ,vy
r_x, r_y, v_x, v_y = sol_convert(sol)
fig, ax = plt.subplots()
xlimit, ylimit = [-1,26], [-1,16]
axis = plt.axes(xlim =(xlimit),
ylim =(ylimit))
for i in range(len(walls_x)):
plt.plot(walls_x[i],walls_y[i],'k',linestyle="solid")
ped, = axis.plot([], [], 'o')
# what will our line dataset contain?
def init():
ax.set_xlim(xlimit)
ax.set_ylim(ylimit)
ped.set_data([], [])
return ped,
# initializing empty values for x and y co-ordinates
xdata, ydata = [], []
# animation function
def animate(frame):
# t is a parameter which varies
# with the frame number
t = dt * frame
xdata = r_x[:,frame]
ydata = r_y[:,frame]
ped.set_data(xdata, ydata)
circles = plt.Circle((r_x[:,frame],r_y[:,frame]),2,color='r', fill=False)
ax.add_patch(circles)
return ped,
anim = FuncAnimation(fig, animate,
init_func = init,
frames = T,
interval = 20,
blit = True)
# saves the animation in our desktop
writergif = PillowWriter(fps=24)
anim.save('pedestrians.gif',writer=writergif)
I've tried adding code within the animate function, but I am very new to animation plotting and do not fully grasp what type of data can be given through this function.
Here is a minimal reproducible example:
# Minimal reproducable example
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
# simulation variables
sim_time = 4 # total simulation time in [?] [is it in steps or in s]
dt = 0.5 # time step of the simulation in s
t = np.arange(0,sim_time,dt) # live running time of simulation
t_span = [t[0],t[-1]]
T = len(t) # amount of time steps for frames
fig, ax = plt.subplots()
xlimit, ylimit = [-1,26], [-1,16]
axis = plt.axes(xlim =(xlimit),
ylim =(ylimit))
points, = axis.plot([], [], 'o')
# what will our line dataset contain?
def init():
ax.set_xlim(xlimit)
ax.set_ylim(ylimit)
points.set_data([], [])
return points,
# initializing empty values for x and y co-ordinates
xdata, ydata = [], []
# animation function
def animate(frame):
# t is a parameter which varies
# with the frame number
t = dt * frame
xdata = np.random.rand(3)*10
ydata = np.random.rand(3)*10
points.set_data(xdata, ydata)
circles = plt.Circle((xdata,ydata),2,color='r', fill=False)
ax.add_patch(circles)
return points,
anim = FuncAnimation(fig, animate,
init_func = init,
frames = T,
interval = 20,
blit = True)
# saves the animation in our desktop
writergif = PillowWriter(fps=24)
anim.save('pedestrians.gif',writer=writergif)
Solution
Instead of plt.circle
(which seems to me is not supported in Matplotlib v3.5.1), please use matplotlib.patches.Circle
to draw circles.
plt.subplots
draws a figure and returns an array of Axes objects. By default, the function call returns one Axes object, which is referred here as ax
.
At the beginning of every frame, we have to make sure previous circles are erased from the ax
. Thus, we use plt.cla()
which erases the entire ax
but we quickly plot the new data using ax.plot
and set its axes limits using ax.set_xlim
and ax.set_ylim
.
Circles need to be added to the ax
one by one since using ax.add_patch
since matplotlib.patches.Circle
creates only one circle at a time. Using a loop, we draw circles around centres (using xdata and ydata).
However, given the length of x- and y-axes are not equal (27 units vs. 17 units), the circles drawn will be ellipses as x-axis is stretched as compared to y-axis: you can confirm this by commenting out ax.set_aspect("equal")
in the code below. However, scaling the axes properly gives us appropriate circles.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Circle
t = np.arange(0, 4, 0.5)
T = len(t)
xdata, ydata = [], []
xlimit, ylimit = [-1,26], [-1,16]
fig, ax = plt.subplots(layout='tight')
points, = ax.plot(xdata, ydata, 'o')
ax.set_xlim(xlimit)
ax.set_ylim(ylimit)
def init():
points, = ax.plot(xdata, ydata, 'o')
return points,
def animate(frame):
xdata = np.random.rand(3)*10
ydata = np.random.rand(3)*10
# Clear the current Axes to make sure circles drawn in the latest animation are erased
plt.cla()
ax.plot(xdata, ydata, 'o')
ax.set_xlim(xlimit)
ax.set_ylim(ylimit)
# To make sure circles are actually circles, and not ellipses
ax.set_aspect("equal")
# Draw circles one by one on the current Axes
for i in range(3):
circle = Circle((xdata[i], ydata[i]), 2, color='r', fill=False)
ax.add_patch(circle)
return points,
anim = FuncAnimation(fig, animate, frames=T, interval=100, blit=True)
plt.show()
An alternative method would store circles drawn at every frame and make them invisible before a new frame is drawn, along with using fig.canvas.draw()
:
circles = {}
ax.set_aspect("equal")
def animate(frame):
global circles
xdata = np.random.rand(3)*10
ydata = np.random.rand(3)*10
for i in circles.keys():
circles[i].set_visible(False)
for i in range(3):
circles[i] = Circle((xdata[i], ydata[i]), 2, color='r', fill=False)
ax.add_patch(circles[i])
points, = ax.plot(xdata, ydata, 'o', color='b')
fig.canvas.draw()
return points,
Answered By - medium-dimensional
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.