在这一章,我们首先学习matplotlib中的patches,用它可以绘制各种几何图形,如圆、三角形和多边形。然后,我们将学习matplotlib的动画支持,并比那些一个程序动态演示抛物轨迹。在最后一节中,我们将需学习如何绘制分形图(一种重复应用简单的几何变换得到复杂的几何图形)。开始吧!

6.1 使用matplotlib的patches绘制几何图形

在matplotlib中,patches可以用来绘制几何图形,图形的每一部分都可视为一个块(patches).。例如,为了把一个圆添加到绘图中,你可以指定圆的半径和圆心。这与我们之前使用matplotlib的方式非常不同,之前我们只需要提供点的x坐标和y坐标即可,在使用patches的特点编写程序之前,我们需要先了解一下matplotlib是如何绘图的。考虑以下程序,其使用matplotlib绘制了三个点(1,1),(2,2),(3,3):


>>> import matplotlib.pyplot as plt >>> x = [1,2,3] >>> y = [1,2,3] >>> plt.plot(x,y) [<matplotlib.lines.Line2D object at 0x000001605C4B7A58>] >>> plt.show()


这个程序创建一个matplotlib窗口,显示一条穿过给定的三个点的直线。在程序中,调用plt.plot()函数会创建一个Figure对象,在该对象中创建了坐标系,最后在坐标系中绘制数据点(见图6-1)。

python画螺旋方阵(Python数学编程第六章)(1)

图6-1 三点直线

下面的程序重新绘制了这个图,只是这次我们明确创建了Figure对象并添加了坐标轴,而不只是调用plot()函数并依赖它创建上述对象:


>>> import matplotlib.pyplot as plt >>> x = [1,2,3] >>> y = [1,2,3] >>> fig = plt.figure() # ① >>> ax = plt.axes() # ② >>> plt.plot(x,y) [<matplotlib.lines.Line2D object at 0x000001605CF712E8>] >>> plt.show()


这里,我们在①处使用figure()函数创建Figure对象(fig),然后在②处使用axes()函数创建坐标轴。axes()函数同时将坐标轴添加到Figure对象中。最后两行代码与之前的程序相同。这一次,当我们调用plot()函数时,它会看到一个带有坐标系的Figure对象已经存在,因此直接根据提供的数据绘制图形。

除了手动创建Figure和Axes对象之外,还可以使用pyplot模块中的两个不同函数(gcf()函数和gca()函数)来获取对当前对象Figure和Axes对象的引用。当调用gcf()函数时,它返回当前Figure对象的引用;当调用gca()函数时,它返回当前Axes对象的引用。这两个该函数都有一个有趣的特性:如果对象不存在,它们将分别别创建相应的对象。我们在本章中使用这些函数时,它们的工作原理将变得更加清晰。

6.1.1 绘制一个圆

为了绘制一个圆,你可以添加一个Circle块到Axes对象中,如下例所示:


''' Example of using matplotlib's Circle patch ''' import matplotlib.pyplot as plt def create_circle(): circle = plt.Circle((0, 0), radius = 0.5) # ① return circle def show_shape(patch): ax = plt.gca() # ② ax.add_patch(patch) plt.axis('scaled') plt.show() if __name__ == '__main__': c= create_circle() # ③ show_shape(c)


在这个程序中,我们将Circle块对象的创建和图中的块的添加分成两个函数:create_circle()和show_shape()。在create_circle()函数中,我们创建一个圆心在(0,0)且半径为0.5的圆,方法是在①处创建一Circle对象,将圆心坐标(0,0)作为一个元组传递,并将半径0.5使用同名的关键字(radius)进行传递。create_circle()函数将返回创建的Circle对象。

为了使show_shape()函数可以作用于任何matplotlib块,首先在②处用gca()函数获取当前Axes对象的引用,接着使用add_patch()函数添加传递给它的块,最后调用show()函数显示图形。我们这里使用scaled函数调用axis()函数,这个参数能够告诉matplotlib自动调节数轴的取值范围,我们需要在所有用块自动调节数轴范围的程序中使用这个语句。当然,你也可以指定固定的取值界限,正如第二章看到的那样。比如,axis(xmin=x,xmax=y,ymin=m,ymax=n)。

在③处,我们调用create_circle()函数,并使用标签c指代其返回的Circle对象。然后,调用show_shape()函数,将c作为参数输入。当运行程序时,你将看到一个matplotlib窗口显示的圆。

正如你看到的,这个圆看上去并不圆。这是由于自动宽高比决定了x轴和y轴的长度比例。如果在②后插入语句ax.set_aspect('equal'),你会发现这个圆将变得更加符合实际。set_aspect()函数用来设置图形的宽高比。使用equals参数可以使matplotlib将x轴和y轴的长度比设置为1:1。

可以使用ec和fc关键字参数修改块中的边缘颜色和表面颜色(填充颜色)。例如fc='g'和ec = 'r'将创建一个边缘颜色为红色和内部填充为绿色的圆。

python画螺旋方阵(Python数学编程第六章)(2)

圆心在(0,0)半径为0.5的圆

matplotlib支持其他各种块,如椭圆(Ellipse)、多边形(POlygon)和矩形(Rectangle)。

6.1.2 创建动画图形

有时我们可能想要创建具有动态形状的图形,matplotlib的动画支持可以帮我们做到这一点。后面我们将创建一个电话版本的抛物轨迹绘制程序。

首先来看一个简单的例子,我们将绘制一个matplotlib图形,该图形从一个小的圆开始,无限的增长到某个半径(除非matplotlib窗口关闭)。


''' A growing circle ''' from matplotlib import pyplot as plt from matplotlib import animation def create_circle(): circle = plt.Circle((0,0), radius=0.05) return circle def update_radius(i, circle): circle.radius = i*0.5 return circle def create_animation(): fig = plt.gcf() # ① ax = plt.axes(xlim=(-10,10),ylim=(-10,10)) ax.set_aspect('equal') circle = create_circle() ax.add_patch(circle) # ② anim = animation.FuncAnimation( # ③ fig, update_radius, fargs = (circle,), frames=30, interval=50) plt.title('Simple Circle Animation') plt.show() if __name__ == '__main__': create_animation()


首先我们从matplotlib包中导入animation模块。create_animation()函数在这里执行了核心功能,在①处它使用gcf()函数获得当前Figure对象的引用,然后创建一个坐标系(x轴和y轴的区间均为-10到10)。在这之后,创建一个半径为0.05、圆心在(0,0)处的Circle对象,并在②处将这个对象添加到坐标系中。然后,在③处我们创建了FuncAnimation对象,它传递了将要创建的动画的数据。

(1)fig。它是当前Figure对象。

(2)update_radius。这个函数负责绘制每一帧。它需要两个参数——一个在调用时传递给它的帧编号,以及更新每一帧时的块对象。这个函数也返回一个块对象。

(3)fargs。这个元组包含了所有(除了帧编号)要传递给update_radius()函数的参数。如果没有要传递的此类参数,则不需要指定该关键字。

(4)frames。frames指动画中的帧数,也是update_radius()函数被调用的次数。这里我们任意取了30帧。

(5)interval。interval值两帧之间的时间间隔(毫秒)。如果共话显示速度太慢,则减小这个值,否则增大这个值。

接下来我们使用title()函数设置标题,最后用show()显示图形。

如前所述,update_radius()函数负责更新在每一帧都会发生变化的圆的属性。这里我们设置半径为i*0.5,其中i是帧编号。你将会看到每一帧逐渐增大的圆(共30帧),一次最大的圆的半径为15。因为坐标轴设置在-10到10之间,这就出现了圆形超出图形范围之外的效果。运行程序时,你将看到第一个动画图形。如下所示。

你会注意到动画会一直持续,直到关闭matplotlib窗口,这是默认情况,你可以在创建FuncAnimation对象时通过设置关键字参数repeat=False来改变此行为。

FunAnimation对象和持久化

你可能会注意到在圆的动画的程序中,我们用标签anim来指代创建的FuncAnimation对象,即使我们不再其他地方使用它。这是因为matplotlib当前的行为存在问题,他不存储任何对FuncAnimation对象额的引用,使该对象容易受到Python中垃圾回收机制的影响,这意味着动画将不会被创建。我们可以通过创建一个标签指代这个对象来阻止这一问题的发生。

python画螺旋方阵(Python数学编程第六章)(3)

动画圆

6.1.3 抛物运动动画演示

在第二章中,我们绘制了一个球在抛物运动中的轨迹。这里,基于这个图象,使用matplotlib动画来支持制作动画轨迹,从而使得这一演示更接近于实际生活中看到的球的运动情况。


''' Animation thr trajectory of an object in projectile mmotion ''' from matplotlib import pyplot as plt from matplotlib import animation import math g = 9.8 def get_intervals(u, theta): t_flight = 2*u*math.sin(theta)/g intervals = [] starts = 0 interval = 0.005 while starts < t_flight: intervals.append(starts) starts = interval return intervals def update_position(i, circle, intervals, u, theta): t = intervals[i] x = u*math.cos(theta)*t y = u*math.sin(theta)*t - 0.5*g*t**2 circle.center = x, y return circle def create_animation(u, theta): intervals = get_intervals(u, theta) xmin = 0 xmax = u*math.cos(theta)*intervals[-1] ymin = 0 tmax = u*math.sin(theta)/g ymax = u*math.sin(theta)*tmax - 0.5*g*tmax**2 # ① fig = plt.gcf() ax = plt.axes(xlim=(xmin,xmax), ylim=(ymin,ymax)) circle = plt.Circle((xmin, ymin), 1.0) ax.add_patch(circle) anim = animation.FuncAnimation( fig, update_position, fargs=(circle, intervals, u, theta), frames=len(intervals), interval=1, repeat=False) plt.title('Projectile Motion') plt.xlabel('X') plt.ylabel('Y') plt.show() if __name__ == '__main__': try: u = float(input('Enter the initial velocity (m/s): ')) theta = float(input('Enter the angle of projection (degree): ')) except ValueError: print('You entered an invalid input') else: thete =math.radians(theta) create_animation(u, theta)


create_animation()函数有两个参数:u和theta,这两个参数对应于初始速度和抛射角度,他们是程序的初始输入。get_intervals()函数用来获取世纪那间隔,据此可以计算抛物运动的x和y坐标,这个函数是通过使用在第二章中用到的相同逻辑来实现的,那时我们单独定义了一个frange()函数。

为了给动画设置坐标界限,我们需要找到x和y的最大值和最小值。最小值都为0,及每一个坐标的初始值。x的最大值为球飞行结束时的坐标值,即列表intervals的最后一个时间间隔。y坐标的最大值为球飞行处于最高点的值,即在①处,我们可以使用如下公式进行计算:

得到这些值后,我们在②处创建坐标系,将合适的坐标界限作为参数输入。接下来的两个语句中,我们在(xmin,ymin)处创建一个半径为1.0的原来表示求(xmin, ymin分别表示x轴和y轴的最小坐标值),并将其添加到图形的Axes对象中。

接下来在③处创建FuncAnimation对象,以当前的图形对象和以下参数作为输入。

(1)update_position。这个函数将更改每一帧中圆的圆心。这里的想法是为每个时间间隔创建一个新的帧,因此我们将帧的数量设置为时间间隔的大小。我们在低i个时间间隔计算球在这一瞬间的x和y坐标,并将圆心设置为这些值。

(2)fargs。update_position()函数需要使用的时间间隔列表、间隔、初始速度和theta,他们都需要通过此关键字参数指定。

(3)frames。因为我们将在每一个时间间隔绘制一帧,所以将帧的数目设置为intervals列表的大小。

(4)repeat。如同我们在第一个动画例子中所讨论的,默认情况下动画将无限重复。这们并不希望看到这一点,所以将这个关键字设置为False。

运行程序,它会询问初始输入然后创建动画。

python画螺旋方阵(Python数学编程第六章)(4)

初始时刻球的位置

python画螺旋方阵(Python数学编程第六章)(5)

过程时刻球的位置

python画螺旋方阵(Python数学编程第六章)(6)

过程时刻球的位置

python画螺旋方阵(Python数学编程第六章)(7)

最终时刻球的位置

,