在〈NumPy 的 Universal 函式〉中,在文字模式中顯示了謝爾賓斯基三角形,有沒有辦法在 Matplotlib 中也畫個謝爾賓斯基三角形呢?可以的!
依〈NumPy 的 Universal 函式〉的演算法,在文字模式中必須有黑與白對應的字元,不過使用 Matplotlib 來畫的話,因為預設背景為白色,只要在黑的部份佈點就可以了,這可以使用散佈圖來達到,在 Matplotlib 中,可透過 pyplot
的 scatter
函式,指定 x 軸與 y 軸的座標清單就可以了。
暫且不使用 NumPy 的話,想在黑的部份佈點,就是收集黑的部份座標,用以呼叫 scatter
函式:
import matplotlib.pyplot as plt
n = 32
xs = []
ys = []
for y in range(n):
for x in range(n):
if x & y == 0:
xs.append(x)
ys.append(y)
plt.title('Sierpinski triangle')
plt.xlabel('x')
plt.ylabel('y')
plt.gca().set_aspect(1)
plt.scatter(xs, ys)
plt.show()
不過,這只會畫出以下的圖案:
Matplotlib 預設的標示(marker)是圓形,想調整標示圖案,可以透過 scatter
的 marker
參數,預設值是 'o'
,可指定的其他值可在 matplotlib.markers 查詢,我想要使用方塊,就要指定 's'
。
另外,我想要作為點的方塊彼此銜接,這必須設定標示的大小,可以透過 scatter
的 s
參數來設定,這個參數有點特別,scatter 的文件談到,s
要指定 points**2,point 是啥?
Matplotlib 在指定一些尺寸參數時,並不是使用像素之類的單位,而是跟印刷方面有關的單位,Matplotlib 中的 point,是印刷上使用的長度單位,一個點是 1/72 英吋(inch),而 s
是指定標示尺寸的平方(可以想成大致上是標示佔有的方形區域的面積)。
在標示指定為方塊時,為了讓他們彼此能銜接,必須知道軸的座標範圍與長度,範圍可以透過 pyplot
的 xlim
、ylim
得知,不過預設情況下,軸的範圍是 Matplotlib 自動運算得到的,無論運算後的範圍為何,這時呼叫 xlim
、ylim
,只會傳回 (0.0, 1.0)
。
既然如此,那就自行設定軸的範圍,這可以透過 xlim
、ylim
來設定,那麼軸的長度呢?可以透過 pyplot
的 gcf
取得目前的圖,然後呼叫 set_size_inches
來設定圖表大小,不過這設定的是整個圖表大小,包含了那些標示文字,如果設定比例為 1:1 的話,那麼座標軸佔的長度,大致上會是圖長度的 0.775。
因此,為了能計算標示的尺寸,使用 set_size_inches
設定圖表為 (plotwidth, plotwidth)
,x、y 軸範圍都是 (-0.5, n - 0.5)
,那麼標示大小就可以如下計算:
PTS_PER_INCH = 72 # 一英吋有 72 個點
plotwidth_pts = PTS_PER_INCH * plotwidth # 圖表的長度有幾個點?
marksize = 0.775 * plotwidth_pts / n # 座標軸切為 n 分,每一份的長度有幾個點?
整理一下程式碼,如下就可以畫出方才第一張圖的結果:
import matplotlib.pyplot as plt
def scatter_plot(title, plotsize, axislim, markersize, xs, ys):
plt.title(title)
plt.gcf().set_size_inches(plotsize)
plt.xlim(axislim)
plt.ylim(axislim)
plt.xlabel('x')
plt.ylabel('y')
plt.scatter(xs, ys, marker = 's', s = markersize ** 2)
plt.show()
n = 128
xs = []
ys = []
for y in range(n):
for x in range(n):
if x & y == 0:
xs.append(x)
ys.append(y)
plotwidth = 6
axislim = (-0.5, n - 0.5)
plotsize = (plotwidth, plotwidth)
PTS_PER_INCH = 72
plotwidth_pts = PTS_PER_INCH * plotwidth
marksize = 0.775 * plotwidth_pts / n
scatter_plot(
'Sierpinski triangle',
plotsize,
axislim,
marksize,
xs,
ys
)