轉(zhuǎn)載:matplotlib高級教程之形狀與路徑——patches和path
1 什么是形狀和路徑
在使用 matplotlib 進(jìn)行繪圖的時(shí)候,線形圖、條形圖、折線圖、扇形圖等等都是我們常見的一些繪圖函數(shù),但是有時(shí)候我們需要繪制一些特殊的形狀和路徑,比如我們要繪制一個橢圓,我們當(dāng)然可以通過橢圓的函數(shù)表達(dá)式,然后選取一系列的 的坐標(biāo)值進(jìn)行依次相連,但是這樣效率低下,而且不太好看。
- 形狀:指的是
matplotlib.patches包里面的一些對象,比如我們常見的箭頭,正方形,橢圓等等,也稱之為“塊”。 - 路徑:表示一系列可能斷開的、可能已關(guān)閉的線和曲線段。指的是
matplotlib.path里面所實(shí)現(xiàn)的功能,最簡單的路徑就是比如一條任意的曲線都可以看成是路徑。比如我要繪制一個心形,就需要通過路徑去完成。
下面我們將逐步展開這些內(nèi)容。
2 形狀的畫圖步驟
第一步:創(chuàng)建畫圖對象以及子圖
這一步和前面的一般畫圖方式?jīng)]有什么區(qū)別,主要實(shí)現(xiàn)以下兩句話:
fig = plt.figure()
ax = fig.add_subplot(211, aspect='auto')
第二步:創(chuàng)建相對應(yīng)的形狀——創(chuàng)建橢圓
e1 = patches.Ellipse((xcenter, ycenter), width, height,angle=angle, linewidth=2, fill=False, zorder=2)
等價(jià)于:
e2 = patches.Arc((xcenter, ycenter), width, height,angle=angle, linewidth=2, fill=False, zorder=2)
因?yàn)?Arc 是繼承自 Ellipse 類的,故而等價(jià)。
注意:上面的橢圓是通過 patches 包里面的類完成的,如果是比較常用的,比如 Circle,也可以通過 plt 去實(shí)現(xiàn)。即:
c1=plt.Circle(相關(guān)參數(shù))
plt 只實(shí)現(xiàn)了常用的幾個,如 Rectangle、Circle、Polygon 這三個,它們可以通過 plt.xxxx() 的形式加以創(chuàng)建,如果要創(chuàng)建更多類型更復(fù)雜的圖形,則使用 patches 模塊。
補(bǔ)充:創(chuàng)建一個圖形實(shí)際上就是調(diào)用它的構(gòu)造函數(shù)即可,但是構(gòu)造函數(shù)有許多的參數(shù)可選,這里不一一說明。
第三步:將圖形添加到圖中——這是非常核心的一步
光創(chuàng)建一個圖形對象還不夠,還需要添加進(jìn)“Axes”對象里面去,即我們所創(chuàng)建的 ax 對象,使用它的 add_patch() 方法:
ax.add_patch(e1)
ax.add_patch(e2)
除此之外,還可以將每一個形狀先添加到一個集合里面,然后再將容納了多個 patch 對象的集合添加進(jìn) ax 對象里面,等價(jià)如下:
patches=[] #創(chuàng)建容納對象的集合
patches.append(e1) #將創(chuàng)建的形狀全部放進(jìn)去
patches.append(e2)
collection=PatchCollection(patches) #構(gòu)造一個Patch的集合
ax.add_collection(collection) #將集合添加進(jìn)axes對象里面去
plt.show() #最后顯示圖片即可
上述案例的完整代碼如下:
import numpy as np
from matplotlib import patches
import matplotlib.pyplot as plt
#繪制一個橢圓需要制定橢圓的中心,橢圓的長和高
xcenter, ycenter = 1,1
width, height = 0.8,0.5
angle = -30 #橢圓的旋轉(zhuǎn)角度
#第一步:創(chuàng)建繪圖對象
fig = plt.figure()
ax = fig.add_subplot(211, aspect='auto')
ax.set_xbound(-1,3)
ax.set_ybound(-1,3)
#第二步
e1 = patches.Ellipse((xcenter, ycenter), width, height,
angle=angle, linewidth=2, fill=False, zorder=2)
#第三步
ax.add_patch(e1)
#第一步
ax = fig.add_subplot(212, aspect='equal')
ax.set_xbound(-1,3)
ax.set_ybound(-1,3)
#第二步
e2 = patches.Arc((xcenter, ycenter), width, height,
angle=angle, linewidth=2, fill=False, zorder=2)
#第三步
ax.add_patch(e2)
plt.show()
使用集合的源代碼如下:
import numpy as np
from matplotlib import patches
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
#繪制一個橢圓需要制定橢圓的中心,橢圓的長和高
xcenter, ycenter = 1,1
width, height = 0.8,0.5
angle = -30 #橢圓的旋轉(zhuǎn)角度
fig = plt.figure()
ax = fig.add_subplot(211, aspect='auto')
ax.set_xbound(-1,3)
ax.set_ybound(-1,3)
e1 = patches.Ellipse((0, 0), width, height,
angle=angle, linewidth=2, fill=False, zorder=2)
e2 = patches.Arc((2, 2), width=3, height=2,
angle=angle, linewidth=2, fill=False, zorder=2)
patches=[]
patches.append(e1)
patches.append(e2)
collection=PatchCollection(patches)
ax.add_collection(collection)
更多 pathes 參考matplotlib.patches?.
3 畫出不同形狀
matplotlib 官方提供了許多例子,下面直接看一個畫形狀的例子:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.lines as mlines
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
def label(xy, text):
'''定義函數(shù),給每一個patch都設(shè)置標(biāo)簽說明'''
# 標(biāo)簽放置在patch下方的0.15位置處
y = xy[1] - 0.15 # shift y-value for label so that it's below the artist
plt.text(xy[0], y, text, ha="center", family='sans-serif', size=14)
fig, ax = plt.subplots()
# create 3x3 grid to plot the artists(創(chuàng)建一個3x3的網(wǎng)格)
grid = np.mgrid[0.2:0.8:3j, 0.2:0.8:3j].reshape(2, -1).T
patches = [] # 創(chuàng)建容納 patch 的集合
# add a circle
circle = mpatches.Circle(grid[0], 0.1, ec="none")
patches.append(circle)
label(grid[0], "Circle")
# add a rectangle
rect = mpatches.Rectangle(grid[1] - [0.025, 0.05], 0.05, 0.1, ec="none")
patches.append(rect)
label(grid[1], "Rectangle")
# add a wedge(添加一個楔形,即圓的一部分)
wedge = mpatches.Wedge(grid[2], 0.1, 30, 270, ec="none")
patches.append(wedge)
label(grid[2], "Wedge")
# add a Polygon
polygon = mpatches.RegularPolygon(grid[3], 5, 0.1)
patches.append(polygon)
label(grid[3], "Polygon")
# add an ellipse
ellipse = mpatches.Ellipse(grid[4], 0.2, 0.1)
patches.append(ellipse)
label(grid[4], "Ellipse")
# add an arrow
arrow = mpatches.Arrow(grid[5, 0] - 0.05, grid[5, 1] - 0.05, 0.1, 0.1,
width=0.1)
patches.append(arrow)
label(grid[5], "Arrow")
# add a path patch
Path = mpath.Path
path_data = [
(Path.MOVETO, [0.018, -0.11]),
(Path.CURVE4, [-0.031, -0.051]),
(Path.CURVE4, [-0.115, 0.073]),
(Path.CURVE4, [-0.03, 0.073]),
(Path.LINETO, [-0.011, 0.039]),
(Path.CURVE4, [0.043, 0.121]),
(Path.CURVE4, [0.075, -0.005]),
(Path.CURVE4, [0.035, -0.027]),
(Path.CLOSEPOLY, [0.018, -0.11])]
codes, verts = zip(*path_data)
path = mpath.Path(verts + grid[6], codes)
patch = mpatches.PathPatch(path)
patches.append(patch)
label(grid[6], "PathPatch")
# add a fancy box
fancybox = mpatches.FancyBboxPatch(
grid[7] - [0.025, 0.05], 0.05, 0.1,
boxstyle=mpatches.BoxStyle("Round", pad=0.02))
patches.append(fancybox)
label(grid[7], "FancyBboxPatch")
# add a line
x, y = np.array([[-0.06, 0.0, 0.1], [0.05, -0.05, 0.05]])
line = mlines.Line2D(x + grid[8, 0], y + grid[8, 1], lw=5., alpha=0.3)
label(grid[8], "Line2D")
colors = np.linspace(0, 1, len(patches))
# 將 patch 集合包裝成 PatchCollection
collection = PatchCollection(patches, cmap=plt.cm.hsv, alpha=0.3)
collection.set_array(np.array(colors))
#將 PatchCollection 添加給 axes 對象
ax.add_collection(collection)
ax.add_line(line)
plt.axis('equal')
plt.axis('off')
plt.tight_layout()
plt.show()
效果圖:

4 路徑 path
路徑里面所涉及到的類容相對較多,這里只介紹簡單的應(yīng)用。首先通過一個例子加以說明。這個例子是要繪制一個簡單的矩形路徑。
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
#import matplotlib.patheffects
#import matplotlib.transforms
verts = [
(0., 0.), # 矩形左下角的坐標(biāo)(left,bottom)
(0., 1.), # 矩形左上角的坐標(biāo)(left,top)
(1., 1.), # 矩形右上角的坐標(biāo)(right,top)
(1., 0.), # 矩形右下角的坐標(biāo)(right, bottom)
(0., 0.)] # 封閉到起點(diǎn)
codes = [Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY]
path = Path(verts, codes) #創(chuàng)建一個路徑path對象
#依然是三步走
#第一步:創(chuàng)建畫圖對象以及創(chuàng)建子圖對象
fig = plt.figure()
ax = fig.add_subplot(111)
#第二步:創(chuàng)建一個patch,路徑依然也是通過patch實(shí)現(xiàn)的,只不過叫做pathpatch
patch = patches.PathPatch(path, facecolor='orange', lw=2)
#第三步:將創(chuàng)建的patch添加到axes對象中
ax.add_patch(patch)
#顯示
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
plt.show()
效果:

總結(jié):通過上面的例子顯示,繪制 “路徑” 的過程和繪制普通的 “patch” 是大致一樣的,依然是遵循一個 “三步走”的步驟,核心在于第二步,也是要創(chuàng)建一個 PathPatch 對象,它也是來自于 patches 包,和普通的 rectangle,circle 是等價(jià)的概念:
patch = patches.PathPatch(path, facecolor="orange", lw=2)
但是這里的 path 對象是要事先自己創(chuàng)建的。
總結(jié):實(shí)際上,matplotlib 中的 rectangle、circle、polygon 等所有簡單的簡單圖形都采用簡單的路徑 path 去實(shí)現(xiàn)的,只不過用類的形式進(jìn)行了更高級的封裝。像直方圖 hist 和 條形圖 bar 這樣的繪圖函數(shù)創(chuàng)建了許多基元圖像,它們的本質(zhì)也是通過路徑去實(shí)現(xiàn)的:
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
Path = mpath.Path
path_data = [
(Path.MOVETO, (1.58, -2.57)),
(Path.CURVE4, (0.35, -1.1)),
(Path.CURVE4, (-1.75, 2.0)),
(Path.CURVE4, (0.375, 2.0)),
(Path.LINETO, (0.85, 1.15)),
(Path.CURVE4, (2.2, 3.2)),
(Path.CURVE4, (3, 0.05)),
(Path.CURVE4, (2.0, -0.5)),
(Path.CLOSEPOLY, (1.58, -2.57)),
]
codes, verts = zip(*path_data)
path = mpath.Path(verts, codes)
patch = mpatches.PathPatch(path, facecolor='r', alpha=0.5)
ax.add_patch(patch)
# plot control points and connecting lines
x, y = zip(*path.vertices)
line, = ax.plot(x, y, 'go-')
ax.grid()
ax.axis('equal')
plt.show()

下面使用路徑去繪制一個條形統(tǒng)計(jì)圖:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
fig = plt.figure()
ax = fig.add_subplot(111)
# 固定隨機(jī)數(shù)種子
np.random.seed(19680801)
# 產(chǎn)生1000組隨機(jī)數(shù),并進(jìn)行組織
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
print(data.shape,n.shape,bins.shape,sep=' ')
# 得到每一個條形圖的四個角落的位置
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
nverts = nrects*(1+3+1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5,0] = left
verts[0::5,1] = bottom
verts[1::5,0] = left
verts[1::5,1] = top
verts[2::5,0] = right
verts[2::5,1] = top
verts[3::5,0] = right
verts[3::5,1] = bottom
#第二步:構(gòu)造patches對象
barpath = path.Path(verts, codes)
patch = patches.PathPatch(barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
#添加patch到axes對象
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
plt.show()

總結(jié):從上面可以得知,我們的繪圖,包括條形圖,扇形圖等都是通過基本的簡單的路徑 path 去實(shí)現(xiàn)的,但是這樣做起來很麻煩,喊不簡單,因而使用 hist、bar 等高層函數(shù)進(jìn)一步封裝,簡化繪圖操作。
5 path 對象
首先需要導(dǎo)入 matplotlib.path 模塊。在使用路徑的時(shí)候一般需要兩個重要的參數(shù),Path 類的定義如下:
class Path(vertices, codes=None, _interpolation_steps=1, closed=False, readonly=False)
故而需要傳遞兩個必要的參數(shù):
rectpath = path.Path(vertices, codes)
那么 vertices 和 codes 到底是什么意思呢?
-
vertices是指的是路徑 path 所經(jīng)過的關(guān)鍵點(diǎn)的一系列坐標(biāo) -
codes指的是點(diǎn)與點(diǎn)之間到底是怎么連接的,是直線連接?曲線連接?還是 。。。
vertices = [
(0., 0.), # left, bottom
(0., 1.), # left, top
(1., 1.), # right, top
(1., 0.), # right, bottom
(0., 0.), # ignored
]
codes = [
Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY,
]
path = Path(verts, codes) # 創(chuàng)建 path 對象
vertices 好理解,那么 codes 到底什么意思?
-
MOVETO: 拿起鋼筆, 移動到給定的頂點(diǎn)。一般指的是 “起始點(diǎn)” -
LINETO: 從當(dāng)前位置繪制直線到給定頂點(diǎn)。 -
CURVE3: 從當(dāng)前位置 (用給定控制點(diǎn)) 繪制一個二次貝塞爾曲線到給定端點(diǎn)。 -
CURVE4: 從當(dāng)前位置 (與給定控制點(diǎn)) 繪制三次貝塞爾曲線到給定端點(diǎn)。 -
CLOSEPOLY: 將線段繪制到當(dāng)前折線的起始點(diǎn)。 -
STOP: 整個路徑末尾的標(biāo)記 (當(dāng)前不需要和忽略)
總結(jié):在創(chuàng)建 vertices 和 codes 的時(shí)候,每個點(diǎn)和每一個 codes 是對應(yīng)著的,如上面所示,一定要注意這樣的對應(yīng)關(guān)系。
6 path 對象的另一種實(shí)現(xiàn)
path_data = [
(Path.MOVETO, [0.018, -0.11]), # 起點(diǎn)
(Path.CURVE4, [-0.031, -0.051]),
(Path.CURVE4, [-0.115, 0.073]),
(Path.CURVE4, [-0.03, 0.073]),
(Path.LINETO, [-0.011, 0.039]),
(Path.CURVE4, [0.043, 0.121]),
(Path.CURVE4, [0.075, -0.005]),
(Path.CURVE4, [0.035, -0.027]),
(Path.CLOSEPOLY, [0.018, -0.11])] # 閉合到起
codes, verts = zip(*path_data) # 使用內(nèi)置的Zip函
heartpath = Path(verts, codes) # 創(chuàng)建Path對
patch = mpatches.PathPatch(heartpath) # 將path包裝成一個patch對
ax.add_patch(patch)
3、補(bǔ)充
上面知識介紹了一些最基本的路徑path的操作,路徑的各種操作很復(fù)雜,還有各種各樣的路徑操作函數(shù),還有路徑效果和相關(guān)的一些操作,在
import matplotlib.patheffects
import matplotlib.transforms
這兩個模塊里面,關(guān)于這兩個模塊的操作,這里就不討論了,有興趣可以查閱官方文檔。(Transformations Tutorial 和 Path effects guide)