前面提到过mtaplotlib支持创建其他几何图形。Polygon块特别有趣,因为我们可以用它绘制具有不同边数的多边形。以下是我们绘制一个正方形(每条边的长度为4的)方式:
'''
Draw a square
'''
from Matplotlib import pyplot as plt
def draw_square():
ax = plt.axes(xlim=(0, 6), ylim=(0, 6))
square = plt.Polygon([(1,1), (5,1), (5,5), (1, 5), closed=True])
ax.add_patch(square)
ax.axis('equal')
plt.show()
if __name__ == '__main__':
draw_square()
Polygon对象是通过将顶点左边编排作为第一个输入参数来传入的,因为我们要绘制一个正方形,所以修要传入四个点的坐标:(1,1), (5,1), (5,5), (1, 5),令closed=True,告诉matplotlib我们要绘制一个封闭的正方形,即起点和终点是相同的。
在这个挑战中,你将尝试一个“在正方形中填充圆形”问题的简化版本。在上述代码生成的正方形中,可以放入多少个半径为0.5的圆形?画出来看一看!下图展示了最终的输出结果。
在5*5的正方形中填充半径为0.5的圆
这里的技巧是从正方形的左下角开始,即(1,1)点,然后持续添加圆形到正方形被填满。以下代码片段显示了如何创建一个圆并将其添加到图形中:
y = 1.5
while y < 5:
x = 1.5
while x < 1.5:
c = draw_circle(x, y)
ax.add_patch(c)
# ax.set_gc('r')
x = 1.0
y = 1.0
这并不是在正方形中填充圆形的唯一办法,大家可以自行探讨。
代码实现:
'''
circle_in_square.py
Circles in a square
'''
from matplotlib import pyplot as plt
def draw_square():
square = plt.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)], closed=True)
return square
def draw_circle(x, y):
circle = plt.Circle((x, y), radius=0.5, fc='y')
return circle
if __name__ == '__main__':
ax = plt.gca()
s = draw_square()
ax.add_patch(s)
y = 1.5
while y < 5:
x = 1.5
while x < 5:
c = draw_circle(x, y)
ax.add_patch(c)
x = 1.0
y = 1.0
plt.axis('scaled')
plt.show()
绘制Siperpinski三角(名字来源于波兰数学家Waclaw Siperpinski)是一个有内嵌其中的较小等边三角形组成的等边三角形分形。下图展示了由10000个点构成的Siperpinski三角。
由10000个点构成的Siperpinski三角
有趣的是,我们使用绘制蕨类植物的程序在这里可以同样绘制绘制Siperpinski三角,只需要改变变换规则及其概率。以下程序可以用来绘制绘制Siperpinski三角:从点(0,0)开始,并应用以下变换之一。
(1)变换1:
(2)变换2:
(3)变换3:
每个变换被选中地概率相同,均为1/3,这里的挑战是编写一个程序绘制由输入的指定点数构成的绘制Siperpinski三角。
代码实现:
'''
Draw a Siperpinski
'''
from matplotlib import pyplot as plt
import random
def transformations_1(p):
x = p[0]
y = p[1]
x1 = 0.5*x
y1 = 0.5*y
return x1, y1
def transformations_2(p):
x = p[0]
y = p[1]
x1 = 0.5*x 0.5
y1 = 0.5*y 0.5
return x1, y1
def transformations_3(p):
x = p[0]
y = p[1]
x1 = 0.5*x 1
y1 = 0.5*y
return x1, y1
def get_x_y(n,p):
transformations = [transformations_1, transformations_2, transformations_3]
x = [0]
y = [0]
x1, y1 = 0, 0
for i in range(n):
t = random.choice(transformations)
x1, y1 = t((x1, y1))
x.append(x1)
y.append(y1)
return x, y
if __name__ == '__main__':
p = (0, 0)
n = int(input('Enter the number of Siperpinski\'s dots: ' ))
x, y = get_x_y(n, p)
plt.plot(x, y,'o')
plt.title('Siperpinski with {0} dots'.format(n))
plt.show()
1976年,Michel Henon提出了Henon函数,该函数描述了一个点P(x,y)的变换规则:
无论初始点在哪里(假设它离原点不远),你将看到随着点的增多,它们开始沿着曲线分布,如下图:
Henon函数
这里的挑战是编写一个程序,创建一个以(1,1)为初始点,经20000次该变换规则迭代后的图形。
这是一个关于动力系统的例子,被所有点所吸引的曲线称为吸引子。更多关于此函数、动力系统和分形的基本信息,可以参考Kenneth Falconer的著作Fractals:A Very Short Introduction(牛津大学出版社,2013)。
代码实现(Henon 分形):
import matplotlib.pyplot as plt
def transformation(p):
x = p[0]
y = p[1]
x1 = y 1 - 1.4 * x**2
y1 = 0.3*x
return x1, y1
def draw_Henon(n, p):
x = [1]
y = [1]
x1, y1 = 1, 1
for i in range(n):
x1, y1 = transformation((x1, y1))
x.append(x1)
y.append(y1)
return x, y
if __name__ == '__main__':
n = int(input('Enter the numbers of Henon points: '))
p = (1, 1)
x, y = draw_Henon(n, p)
plt.plot(x, y, 'o')
plt.title('Henon with {0} points'.format(n))
plt.show()
这里的挑战是编写一个程序绘制Mandelbrot集,这是引用加单规则导出复杂形状的另一案例(如下图)。在讨论实现它的具体步骤之前,我们先来了解matplotlib中的imshow()函数。
Mandelbrot集
imshow()函数
imshow()函数通常用来显示外部图像,如JPEFG或PNG图像。可以参考网址Image tutorial — Matplotlib 2.0.2 documentation中的例子。这里,我们使用这个函数通过matplotlib来绘制我们新创建的图像。
考虑笛卡尔平面的一部分,其中x和y’的坐标都位于0到5之间。现在,考虑沿每个轴的6个等距离点:x坐标和y坐标的取值均为0,1,2,3,4,5.如果我们取这些点的笛卡尔积,将得到x-y平面上的36个等距点。坐标分别为(0,0),(0,1), ... ,(0,5),(1,0),(1,1),... ,(1,5),...,(5,5)。现在假设我们要用灰色阴影给每个点上色,也就是说,随机选择呢一些点为黑色、一些点为白色以及一些点为黑白之间的颜色。下图就展示了这种情形。
为创建这个图形,我们必须定义一个包含6个列表的列表,6个列表中的每一个都依次包含着0-10之间的6个整数,每个数字对应于每个点的颜色:0表示黑色,10表示白色。然后我们将这个列表和其他必要的参数一起传递给inshow()函数。
创建一个包含多个列表的列表
一个列表可以包含其他列表作为其元素:
>>> l1 = [1,2,3]
>>> l2 = [4,5,6]
>>> l = [l1, l2]
6*6的等间距点阴影上色
这里我们创建一个列表l,它包含了列表l1和列表l2。列表1的第一个元素l[0]与列表l1相同,第二个元素l[1]与列表l2相同。
>>> l[0]
[1, 2, 3]
>>> l[1]
[4, 5, 6]
为引用这两个列表中的单个元素,我们必须指定两个索引,l[0][1]指代第一个列表的第二个元素,l[1][2]指代第二个列表的第三个元素,以此类推。
既然我们已经知道如何处理包含多个列表的列表,我们可以摆写一个程序创建一个类似于上图的图形。
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import random
def initialize_image(x_p, y_p): # ①
image = []
for i in range(y_p):
x_colors = []
for i in range(x_p):
x_colors.append(0)
image.append(x_colors)
return image
def color_points():
x_p = 6
y_p = 6
image = initialize_image(x_p, y_p)
for i in range(y_p):
for j in range(x_p):
image[i][j] = random.randint(0,10) # ②
plt.imshow(image, origin='lower', extent=(0, 5, 0, 5),
cmap=cm.Greys_r, interpolation='nearest') # ③
plt.colorbar()
plt.show()
if __name__ == '__main__':
color_points()
①处的initialize_image()函数创建了一个包含多个列表的列表,其中每一个元素都初始化为0,该函数有两个输入参数x_p和y_p,分别对应于x轴和y轴上的点的个数,这实际上意味着初始列表图像包含x_p个列表,其中每个列表包含y_p个0。
在color_points()函数中,一旦从initialize_image()函数返回一个图像列表,在②处就给元素image[i][j]分配一个0-10之间的随机整数。当给元素分配随机整数时,我们也给笛卡尔平面上的点(从原点出发沿y轴第i步,沿x轴第j步的点)指定了颜色。这里很重要的一点就是imshow()函数从image列表中的位置推导点的颜色,而不是依赖于具体的x坐标和y坐标。
然后,在③处调用imshow(函数,并将image作为第一个参数输入,关键字参数origin='lower指定image[0][0]的数字对应于点(0, 0)的颜色,关键字参数extent=(0,5, 0, 5)分别将图像的左下角和右上角的坐标设置为(0, 0)和(5, 5),关键字参数cmap=cm.Greys_ r说明我们要创建一个灰度图像。
最后一个关键字参数interpolation='nearest'设定matplotlib 应该给那些没有指定颜色的点用与其最近的点的颜色上色。这是什么意思呢?注意我们仅考虑并指定了坐标区域(0, 0)和(5,5)内36个点的颜色,因为该区域内有无穷多个点,所以我们应该告诉matplotlib对那些没有设定颜色的点如何上色,这就是你在图中的 每个点周围看到颜色"框”的原因。
调用colorbar()函数将在图中显示一个颜色条, 显示哪个整 数对应哪个颜色。最后调用show()函数展示图像。需要注意的是由于使用了random.randint()函数,你的图像可能会与图6-15展示的有所不同。
如果你通过在color_points()函数中将x_ p和y_ p设置为20来增加每个轴上的点数,你将会看到一个与图6-16所示类似的图像,注意颜色框的尺寸变小了。如果你增加更多的点数,你将看到颜色框的尺寸进一步缩小, 给人的错觉是每个点都有不同的颜色。
Mandelbrot集的绘制
我们考虑x-y平面上位于点(-2.5,-1.0)和(1.0,1.0)之间的区域,并把每个轴划分为400个等间距的点,这些点的笛卡尔积将给出该区域内的1600个等间距点,我们把这些点记为。
20*20的等间距点阴影上色
通过调用之前用到的initialize_ image(函数创建一个 列表image,并将函数中的x_ p和y_ p都设置为400。 然后,为每个生成的点(x, y)执行下述步骤:
(1) 首先,创建两个复数,和。(我们用j表示 )
(2)创建一个迭代标签,并将其设置为0,即iteration=0。
(3)创建一个复数
(4)以1为单位增加iteration的值,即iteration= iteration 1。
(5)若abs(z1) < 2 且iteration < max_ iteration, 则返回第(3)步;否则进入第(6)步。max iteration 的值越大,绘制的图像越详细,当然花费的时间也就越长。这里设置max iteration=1000。 (6)将点的颜色设置为iteration 的值,即image[k][i] = iteration。一旦有了完整的image列表,调用imshow()函数,并将extent关键字参数设置为(-2.5, -1.0)和(1.0,1.0) 之间的区域。
这个算法通常称为时间逃逸算法。当一个点达到最大迭代次数时仍在区域内(即复数的模小于2),则该点属于Mandelbrot 集,将其涂成白色。那些在未达到最大迭代次数就超出区域的点称为“逃逸”,它们不属于Mandelbrot集,将其涂成黑色。你可以通过减少和增加每一个轴上点的个数来进行实验,减少点的个数会导 致颗粒图像,而增加点的个数则会产生更加细致的图像。
代码实现:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import random
x0, x1 = -2.5, 1
y0, y1 = -1.0, 1
def initialize_image(x_p, y_p):
image = []
for i in range(y_p):
x_colors = []
for i in range(x_p):
x_colors.append(0)
image.append(x_colors)
return image
def color_points():
n = 400
max_iteration = 1000
z1 = complex(0, 0)
image = initialize_image(n, n)
dx = (x1-x0)/(n-1)
dy = (y1-y0)/(n-1)
x_coords = [x0 i * dx for i in range(n)]
y_coords = [y0 i * dy for i in range(n)]
for i,x in enumerate(x_coords):
for k, y in enumerate(y_coords):
z1 = complex(0, 0)
iteration = 0
c = complex(x,y)
while abs(z1) < 2 and iteration < max_iteration:
z1 = z1 ** 2 c
iteration = 1
image[k][i] = iteration
print(image)
plt.imshow(image, origin='lower', extent=(-2.5, -1.0, -1.0, 1.0),
cmap=cm.Greys_r, interpolation='nearest')
plt.colorbar()
plt.show()
if __name__ == '__main__':
color_points()