matplotlib 之形狀與路徑:patches和path

轉(zhuǎn)載:matplotlib高級教程之形狀與路徑——patches和path

1 什么是形狀和路徑

在使用 matplotlib 進(jìn)行繪圖的時(shí)候,線形圖、條形圖、折線圖、扇形圖等等都是我們常見的一些繪圖函數(shù),但是有時(shí)候我們需要繪制一些特殊的形狀和路徑,比如我們要繪制一個橢圓,我們當(dāng)然可以通過橢圓的函數(shù)表達(dá)式,然后選取一系列的 (x, y) 的坐標(biāo)值進(jìn)行依次相連,但是這樣效率低下,而且不太好看。

  1. 形狀:指的是 matplotlib.patches 包里面的一些對象,比如我們常見的箭頭,正方形,橢圓等等,也稱之為“”。
  2. 路徑:表示一系列可能斷開的、可能已關(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)

那么 verticescodes 到底是什么意思呢?

  • vertices 是指的是路徑 path 所經(jīng)過的關(guān)鍵點(diǎn)的一系列坐標(biāo) (x,y)
  • 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)建 verticescodes 的時(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 TutorialPath effects guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容