动画线和散点图时如何避免 'list index out of range' 错误

How do I avoid the 'list index out of range' error when animating line as well as scatter plot

我只是在玩 matplotlib 的动画模块。我想创建一个弹丸运动的动画图。我设法使用以下代码创建它:

import matplotlib.animation as ani
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['figure.dpi'] = 150

## Inputs
angle = 30  # deg
angleRad = angle*np.pi/180
vel = 2     # m/s
deltat = 0.005    # s (timestep)
acc = -9.81     # m/s^2 (gravitational acc)


## Calculations
# Initialisations
ux0 = vel*np.cos(angleRad)
uy0 = vel*np.sin(angleRad)
U = [ux0]
V = [uy0]

# Acceleration
ax = 0
ay = acc

# Velocity
def calcVel(time):
    Uvel = ux0 + ax*time
    Vvel = uy0 + ay*time
    return (Uvel, Vvel)

# Location
x0 = 0
y0 = 0
x = [x0]
y = [y0]
   
def location(time):
    locx = U[0]*time + 0.5*ax*time**2
    locy = V[0]*time + 0.5*ay*time**2
    return (locx, locy)
   
yn = y0
t0 = 0
n = 1
count = [0]
while yn>=0:
    t = t0 + n*deltat
    xVel, yVel = calcVel(t)
    posx, posy = location(t)
    yn = posy
    if yn>=0:
        U.append(xVel)
        V.append(yVel)
        x.append(posx)
        y.append(posy)
        count.append(t)
        n +=1


def buildframes(i=int):
    line.set_data(x[:i], y[:i])
    points.set_offsets([x[i-1], y[i-1]])
    index.set_text('time = %.3f s' % count[i])
    plt.pause(0.001)


## Plotting
fig = plt.figure()
axes = fig.add_subplot(1,1,1)
line, = axes.plot([], []) # , 'bo', lw=2, scalex = True, scaley = True
points = plt.scatter([], [], c='red', s=15)
axes.set_ylim(min(y), np.true_divide(np.ceil(max(y) * 10**2), 10**2))
axes.set_xlim(min(x), np.true_divide(np.ceil(max(x) * 10**2), 10**2))
plt.ylabel('Height')
plt.xlabel('Distance')
index = axes.annotate('', xy=(0.75, 0.95), xycoords='axes fraction')

animator = ani.FuncAnimation(fig, buildframes, interval = 500)
#animator.save("filename")
plt.show()
animator.save('animation.gif')

标题问题很可能是笼统的“我如何改进这段代码”,但这里不鼓励这样做。虽然代码生成动画,但此代码存在几个更细微的问题。我按重要性递减顺序列出了它们:

  1. “列表索引超出范围”错误:这是由于线条和散点的绘制方式所致。对于线应该已经有两个点。我如何避免这种情况并拥有更清晰的动画师功能? (它仍然在绘制 - 只是这个错误仍然存​​在,这让我很烦。)
  2. 线尖始终在点上方(注意红球上方的微小蓝色线段)。我曾尝试更改顺序,但这无助于让红点始终在线上方。我该怎么做?
  3. 当在 Spyder 中 运行 时,它似乎在绘图 window 中动画了两次(速度不同)。我不确定为什么。创建的 gif 始终具有较晚的速度(较慢)。保存时可能会覆盖速度较慢的帧,从而覆盖观察结果。这意味着动画函数被调用了两次?我该如何解决?如果我在 Linux python 环境中 运行 它不会发生这种情况。
  4. 通用:有没有更聪明的方法来制作相同的动画?例如感觉代码太长了,请问有什么办法可以让它更紧凑更高效?
  1. 将动画中的帧数设置为等于要绘制的变量的长度。例如:ani.FuncAnimation(fig, buildframes, frames=len(x), interval=500)
  2. 您可以使用 zorder 强制以适当的顺序显示内容。在这里,我使用 ``zorder=1for the line andzorder=2` 作为散点,因此散点将在行的顶部可视化。
  3. 我不能对此发表评论,因为我不使用 Spyder。
  4. 使用动画时,不要将 plt.pause 放在动画函数中。调用 ani.FuncAnimation 时设置适当的 interval。动画功能很好。在下面的代码中,我使用 Numpy 的“矢量化”进行了相同的计算,因此无需使用循环(这样可以节省一些代码行)。
import matplotlib.animation as ani
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['figure.dpi'] = 150

## Inputs
angle = 30  # deg
angleRad = angle*np.pi/180
vel = 2     # m/s
deltat = 0.005    # s (timestep)
acc = -9.81     # m/s^2 (gravitational acc)

# Initialisations
x0 = 0
y0 = 0
ux0 = vel*np.cos(angleRad)
uy0 = vel*np.sin(angleRad)
ax = 0
ay = acc

# 
def linspace(start, stop, step=1.):
    """
    Like np.linspace but uses step instead of num
    This is inclusive to stop, so if start=1, stop=3, step=0.5
    Output is: array([1., 1.5, 2., 2.5, 3.])
    """
    return np.linspace(start, stop, int((stop - start) / step + 1))

t0 = 0
tf = 1 # arbitrary value
# discretize the time with a given deltat
time = linspace(t0, tf, deltat)
# compute x-y locations
x = ux0*time + 0.5*ax*time**2
y = uy0*time + 0.5*ay*time**2
# now there is a change that y < 0 for some x.
# We need to only keep the locations and times where y >= 0.
idx = y >= 0
x = x[idx]
y = y[idx]
time = time[idx]
# compute velocities
U = ux0 + ax*time
V = uy0 + ay*time

def buildframes(i):
    line.set_data(x[:i+1], y[:i+1])
    points.set_offsets([x[i], y[i]])
    index.set_text('time = %.3f s' % time[i])


## Plotting
fig = plt.figure()
axes = fig.add_subplot(1,1,1)
line, = axes.plot([], [], zorder=1) # , 'bo', lw=2, scalex = True, scaley = True
points = plt.scatter([], [], c='red', s=15, zorder=2)
axes.set_ylim(min(y), np.true_divide(np.ceil(max(y) * 10**2), 10**2))
axes.set_xlim(min(x), np.true_divide(np.ceil(max(x) * 10**2), 10**2))
plt.ylabel('Height')
plt.xlabel('Distance')
index = axes.annotate('', xy=(0.75, 0.95), xycoords='axes fraction')

animator = ani.FuncAnimation(fig, buildframes, frames=len(x), interval=500)
# plt.show()
animator.save('animation.gif')