随着python学习的深入,我们不可避免进入画图模块matplotlib,也不可避免会遇到制作动画的需求。
【1】官网教程
如何学习python制作动画,最简单的就是直奔官网:
https://matplotlib.org/stable/users/explain/animations/animations.html#animations
它给出很长的代码,下面是除引入numpy、matplotlib模块之外的部分:
图1
但是官网版本并不具备中文注释,为方便大家快速学习,本期就直接对官网教程自带的代码进行一次添加注释工作,期待能对大家的学习带来便利。
【2】程序模块引入
打开【1】中官网教程会发现,动画制作的前置条件包括:
【a】用数学计算模块numpy生成函数;
【b】用函数绘图模块matplotlib为函数绘图;
【c】用animation模块将matplotlib为函数绘的图形变成动态输出。
官网程序如下:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
增加注释后变成:
import matplotlib.pyplot as plt #引入 matplotlib模块,用于绘图
import numpy as np #引入 numpy模块,用于生成函数import matplotlib.animation as animation #引入 animation模块,用于生成动画
【3】函数定义代码解读
先查看第一段代码:
fig, ax = plt.subplots()
t = np.linspace(0, 3, 40)
g = -9.81
v0 = 12
z = g * t**2 / 2 + v0 * t
v02 = 5
z2 = g * t**2 / 2 + v02 * t
在这里,需要先说明fig,ax=plt.subplots()的含义:
追溯至官网的入门教程,我们将会看到:
Quick start guide — Matplotlib 3.9.2 documentation
这里的fig,ax=plt.subplots()就是指画一个图,有一个坐标轴。
官网教程还给出了一些测试案例,我们尝试把这段代码直接输出,可参考下输代码:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure() # an empty figure with no Axes
fig, ax = plt.subplots() # a figure with a single Axes
plt.show() #输出图片
运行后:
图2
此时可将初始部分代码解读如下:
fig, ax = plt.subplots() #定义一个带坐标轴的空图形
t = np.linspace(0, 3, 40) #定义自变量t的范围是(0,3),中间去40个数,但不包含3
g = -9.81 #定义常数g=-9.81
v0 = 12 #定义常数v0=12
z = g * t**2 / 2 + v0 * t #定义函数z = g * t**2 / 2 + v0 * t ,这是匀加速运动位移时间公式v02 = 5 #定义常数v02 = 5
z2 = g * t**2 / 2 + v02 * t #定义函数z2 = g * t**2 / 2 + v02 * t ,这是匀加速运动位移时间公式
【4】函数图形输出代码解读
继续解读:
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0]
ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]')
ax.legend()
式中,第一行scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')的意思是绘制散点图,t[0]代表t的第0个取值0,z[0]代表z的第0个取值0,c指颜色(具体的,“b”代表blue即蓝色),s指散点的大小,label就是指图例。
看不懂的最快解决办法,就是尝试把代码输出。我们先改写为下述代码:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots() # a figure with a single Axes
t = np.linspace(0, 3, 40) #定义自变量t的范围是(0,3),中间去40个数,但不包含3
g = -9.81 #定义常数g=-9.81
v0 = 12 #定义常数v0=12
z = g * t**2 / 2 + v0 * t #定义函数z = g * t**2 / 2 + v0 * t ,这是匀加速运动位移时间公式
v02 = 5 #定义常数v02 = 5
z2 = g * t**2 / 2 + v02 * t #定义函数z2 = g * t**2 / 2 + v02 * t ,这是匀加速运动位移时间公式
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')
ax.legend() #输出图例
plt.show() #输出图形
运行后的输出结果为:
图3
可见输出点(0,0)对应(t[0],z[0])。
类似的,在上述代码中间补充一行
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0] #输出(t[0],z2[0])
变成:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots() # a figure with a single Axes
t = np.linspace(0, 3, 40) #定义自变量t的范围是(0,3),中间去40个数,但不包含3
g = -9.81 #定义常数g=-9.81
v0 = 12 #定义常数v0=12
z = g * t**2 / 2 + v0 * t #定义函数z = g * t**2 / 2 + v0 * t ,这是匀加速运动位移时间公式
v02 = 5 #定义常数v02 = 5
z2 = g * t**2 / 2 + v02 * t #定义函数z2 = g * t**2 / 2 + v02 * t ,这是匀加速运动位移时间公式
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s') #输出(t[0],z[0])
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0] #输出(t[0],z2[0])
ax.legend() #输出图例
plt.show() #输出图形
运行后的输出结果为:
图4
虽然只看见一个点,但实际上的输出点(0,0),同时对应(t[0],z[0])和(t[0],z2[0])。
剩下部分的as.set部分为对坐标轴进行属性设置,对此不再单独输出,将整个函数图形输出代码增加注释如下:
fig, ax = plt.subplots() # a figure with a single Axes t = np.linspace(0, 3, 40) #定义自变量t的范围是(0,3),中间去40个数,但不包含3 g = -9.81 #定义常数g=-9.81 v0 = 12 #定义常数v0=12 z = g * t**2 / 2 + v0 * t #定义函数z = g * t**2 / 2 + v0 * t ,这是匀加速运动位移时间公式 v02 = 5 #定义常数v02 = 5 z2 = g * t**2 / 2 + v02 * t #定义函数z2 = g * t**2 / 2 + v02 * t ,这是匀加速运动位移时间公式 scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s') #输出(t[0],z[0]) line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0] #输出(t[0],z2[0]) ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]') #设置坐标轴大小和名称 ax.legend() #输出图例 plt.show() #输出图形
【5】update函数代码解读
继续解读:
def update(frame):
# for each frame, update the data stored on each artist.
x = t[:frame]
y = z[:frame]
# update the scatter plot:
data = np.stack([x, y]).T
scat.set_offsets(data)
# update the line plot:
line2.set_xdata(t[:frame])
line2.set_ydata(z2[:frame])
return (scat, line2)
这里的自定义函数update,引入了一个参数frame,因为这个参数此时第一次出现。它的赋值将在外部调用update函数的时候完成。
此外定义了矩阵x,它是一个由t组成的矩阵;定义了矩阵y,是z矩阵。
然后定义了data矩阵,它调用了numpy中的stack函数,让矩阵x和y先叠加在一起,然后再通过.T做了一次转置。为了形象理解np.stack([x,y]).T,在编辑器尝试输入以下代码:
import matplotlib.pyplot as plt #引入matplotlib模块
import numpy as np #引入numpy模块
x=np.linspace(0,3,3,dtype=int) #定义x矩阵
y=np.linspace(5,8,3,dtype=int) #定义y矩阵
data=np.stack([x,y]) #将x矩阵和y矩阵叠放在一起
dataT=np.stack([x,y]).T #将x矩阵和y矩阵叠放在一起,然后转置
print('x=',x) #输出x矩阵
print('y=',y) #输出y矩阵
print('data=',data) #输出data矩阵
print('dataT=',dataT) #输出dataT矩阵
运行后输出结果为:
图5
然后散点量scat,它通过调用scat.set_offsets(data)将data里面的数据按照顺序输出。
此后的line2.set_xdata(t[:frame])和line2.set_ydata(z2[:frame])相对简单,将t矩阵作为x输出,z2矩阵最为y输出。
综上所述有:
def update(frame): #定义update函数
# for each frame, update the data stored on each artist.
x = t[:frame] #定义矩阵x,由t构成的一维数组
y = z[:frame] #定义矩阵y,由z构成的一维数组
# update the scatter plot:
data = np.stack([x, y]).T #定义矩阵data,将矩阵x,y叠加后转置
scat.set_offsets(data) #将矩阵data输出为散点图
# update the line plot:
line2.set_xdata(t[:frame]) #将矩阵t设置为x
line2.set_ydata(z2[:frame]) #将矩阵z2设置为y
return (scat, line2) #返回scat和line2
【6】animation.FuncAnimation函数代码解读
继续解读,此时只剩最后一行代码:
ani = animation.FuncAnimation(fig=fig, func=update, frames=40, interval=30)
plt.show()
这里就是使用animation.FuncAnimation模块,反复调用update函数,给frames赋值40,但是我们从官网的animation.FuncAnimation模块中,并没有发现interval应该是哪一个参数。
matplotlib.animation.FuncAnimation — Matplotlib 3.9.2 documentation
为进一步理解interval的意义,最简单的办法就是改变interval的值,看输出结果如何变化。
首先,直接按照interval=30输出,然后,设置interval=0.0030,现实测试发现不会输出结果,只不过是输出变快了。
图6
至此,最后一行代码增加注释后为:
ani = animation.FuncAnimation(fig=fig, func=update, frames=40, interval=30) #调用undape函数输出动画
plt.show() #画图
【7】完整版注释
综上所述,完整版代码注释为:
import matplotlib.pyplot as plt #引入 matplotlib模块,用于绘图
import numpy as np #引入 numpy模块,用于生成函数
import matplotlib.animation as animation #引入 animation模块,用于生成动画
fig, ax = plt.subplots() #定义一个带坐标轴的空图形
t = np.linspace(0, 3, 40) #定义自变量t的范围是(0,3),中间去40个数,但不包含3
g = -9.81 #定义常数g=-9.81
v0 = 12 #定义常数v0=12
z = g * t**2 / 2 + v0 * t #定义函数z = g * t**2 / 2 + v0 * t ,这是匀加速运动位移时间公式
v02 = 5 #定义常数v02 = 5
z2 = g * t**2 / 2 + v02 * t#定义函数z2 = g * t**2 / 2 + v02 * t ,这是匀加速运动位移时间公式
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')#输出(t[0],z[0])
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0] #输出(t[0],z2[0])
ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]') #设置坐标轴大小和名称
ax.legend() #输出图例
def update(frame): #定义update函数
# for each frame, update the data stored on each artist.
x = t[:frame] #定义矩阵x,由t构成的一维数组
y = z[:frame] #定义矩阵y,由z构成的一维数组
# update the scatter plot:
data = np.stack([x, y]).T #定义矩阵data,将矩阵x,y叠加后转置
scat.set_offsets(data) #将矩阵data输出为散点图
# update the line plot:
line2.set_xdata(t[:frame]) #将矩阵t设置为x
line2.set_ydata(z2[:frame]) #将矩阵z2设置为y
return (scat, line2) #返回scat和line2
ani = animation.FuncAnimation(fig=fig, func=update, frames=40, interval=300) #调用undape函数输出动画
plt.show() #画图
【8】总结
本文对matplotlib官网中的animation动画制作模块进行了示例程序解读,解读过程中对不理解的部分进行了展开探索,非常适用于动画制作初学者学习,对新手小白友好。