1. 程式人生 > >matplotlib高階教程之形狀與路徑——patches和path

matplotlib高階教程之形狀與路徑——patches和path

 

歡迎關注“勇敢AI”公眾號,更多python學習、資料分析、機器學習、深度學習原創文章與大家分享,還有更多電子資源、教程、資料集下載。勇敢AI,一個專注於人工智慧AI的公眾號。

==================================================================================

一、什麼是形狀和路徑

在一般的使用matplotlib進行繪圖的時候,線形圖、條形圖、折線圖、扇形圖等等都是我們常見的一些繪圖函式,但是有時候我們需要繪製一些特殊的形狀和路徑,比如我們要繪製一個橢圓,我們當然可以通過橢圓的函式表示式,然後選取一系列的(x,y)的座標值進行依次相連,但是這樣效率低下,而且不太好看。

1、形狀

指的是matplotlib.patches包裡面的一些列物件,比如我們常見的箭頭,正方形,橢圓等等,也稱之為“塊”

2、路徑

什麼是路徑?

表示一系列可能斷開的、可能已關閉的線和曲線段。

指的是matplotlib.path裡面所實現的功能,最簡單的路徑就是比如一條任意的曲線都可以看成是路徑。比如我要繪製一個心形,就需要通過路徑去完成。

但是畫圖我們說最重要的是畫圖的步驟,下面以橢圓為例詳細講解繪製基本形狀的一般步驟。

二、形狀的畫圖步驟:

1、第一步:建立畫圖物件以及子圖

這一步和前面的一般畫圖方式沒有什麼區別,主要實現以下兩句話

fig = plt.figure()
ax = fig.add_subplot(211, aspect='auto')

2、第二步:建立相對應的形狀——建立橢圓

e1 = patches.Ellipse((xcenter, ycenter), width, height,angle=angle, linewidth=2, fill=False, zorder=2)

等價於:

e2 = patches.Arc((xcenter, ycenter), width, height,angle=angle, linewidth=2, fill=False, zorder=2)

因為Arc是繼承自Ellipse類的,故而等價。

注意:上面的橢圓是通過patches包裡面的類完成的,如果是比較常用的,比如Circle,也可以通過plt去實現。即

c1=plt.Circle(相關引數)

plt只實現了常用的幾個,如Rectangle、Circle、Polygon這三個,它們可以通過plt.xxxx()的形式加以建立,如果要建立更多型別更復雜的圖形,則使用patches模組。

補充:建立一個圖形實際上就是呼叫它的建構函式即可,但是建構函式有許多的引數可選,這裡不一一說明,僅僅以橢圓Arc為例加以說明,其他圖形可以檢視定義或者是相關文件:

有效的引數如下:

Property Description
agg_filter a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array
alpha float or None
animated bool
antialiased unknown
capstyle {'butt', 'round', 'projecting'}
clip_box Bbox
clip_on bool
clip_path [(Path, Transform) | Patch | None]
color color
contains callable
edgecolor color or None or 'auto'
facecolor color or None
figure Figure
fill bool
gid str
hatch {'/', '\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
in_layout bool
joinstyle {'miter', 'round', 'bevel'}
label object
linestyle {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
linewidth float or None for default
path_effects AbstractPathEffect
picker None or bool or float or callable
rasterized bool or None
sketch_params (scale: float, length: float, randomness: float)
snap bool or None
transform Transform
url str
visible bool
zorder float

3、第三步:將圖形新增到圖中——這是非常核心的一步

光建立一個圖形物件還不夠,還需要新增進“ Axes ”物件裡面去,即我們所建立的ax物件,使用它的add_patch()方法

ax.add_patch(e1)

ax.add_patch(e2)

除此之外,還可以將每一個形狀先新增到一個集合裡面,然後再將容納了多個patch物件的集合新增進ax物件裡面,等價如下

patches=[]      #建立容納物件的集合

patches.append(e1)   #將建立的形狀全部放進去

patches.append(e2)

collection=PatchCollection(patches)  #構造一個Patch的集合

ax.add_collection(collection)    #將集合新增進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  #橢圓的旋轉角度

#第一步:建立繪圖物件
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  #橢圓的旋轉角度

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)

plt.show()

執行結果如下:

三、patches模組中型別大全

patches所有的型別都在matplotlib.patches包中,因此需要事先匯入

Classes

Arc(xy, width, height[, angle, theta1, theta2]) An elliptical arc.
Arrow(x, y, dx, dy[, width]) An arrow patch.
ArrowStyle ArrowStyle is a container class which defines several arrowstyle classes, which is used to create an arrow path along a given path.
BoxStyle BoxStyle is a container class which defines several boxstyle classes, which are used for FancyBboxPatch.
Circle(xy[, radius]) A circle patch.
CirclePolygon(xy[, radius, resolution]) A polygon-approximation of a circle patch.
ConnectionPatch(xyA, xyB, coordsA[, ...]) A ConnectionPatch class is to make connecting lines between two points (possibly in different axes).
ConnectionStyle ConnectionStyle is a container class which defines several connectionstyle classes, which is used to create a path between two points.
Ellipse(xy, width, height[, angle]) A scale-free ellipse.
FancyArrow(x, y, dx, dy[, width, ...]) Like Arrow, but lets you set head width and head height independently.
FancyArrowPatch([posA, posB, path, ...]) A fancy arrow patch.
FancyBboxPatch(xy, width, height[, ...]) Draw a fancy box around a rectangle with lower left at xy*=(*x, y) with specified width and height.
Patch([edgecolor, facecolor, color, ...]) A patch is a 2D artist with a face color and an edge color.
PathPatch(path, **kwargs) A general polycurve path patch.
Polygon(xy[, closed]) A general polygon patch.
Rectangle(xy, width, height[, angle]) Draw a rectangle with lower left at xy = (x, y) with specified width, height and rotation angle.
RegularPolygon(xy, numVertices[, radius, ...]) A regular polygon patch.
Shadow(patch, ox, oy[, props]) Create a shadow of the given patch offset by ox, oy.
Wedge(center, r, theta1, theta2[, width]) Wedge shaped patch.

四、圖形的綜合應用案例


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

#定義函式,給每一個patch都設定標籤說明
def label(xy, text):
    y = xy[1] - 0.15  # 標籤放置在patch下方的0.15位置處
    plt.text(xy[0], y, text, ha="center", family='sans-serif', size=14)


fig, ax = plt.subplots()
# 建立一個3x3的網格
grid = np.mgrid[0.2:0.8:3j, 0.2:0.8:3j].reshape(2, -1).T

#建立容納patch的集合
patches = []

# 新增一個圓Circle
circle = mpatches.Circle(grid[0], 0.1, ec="none")
patches.append(circle) 
label(grid[0], "Circle")

# 新增一個Rectangle
rect = mpatches.Rectangle(grid[1] - [0.025, 0.05], 0.05, 0.1, ec="none")
patches.append(rect)
label(grid[1], "Rectangle")

# 新增一個楔形,即圓的一部分
wedge = mpatches.Wedge(grid[2], 0.1, 30, 270, ec="none")
patches.append(wedge)
label(grid[2], "Wedge")

# 新增一多邊形,這裡新增一個五邊形
polygon = mpatches.RegularPolygon(grid[3], 5, 0.1)
patches.append(polygon)
label(grid[3], "Polygon")

# 新增一個橢圓,也可以使用Arc
ellipse = mpatches.Ellipse(grid[4], 0.2, 0.1)
patches.append(ellipse)
label(grid[4], "Ellipse")

# 新增一個箭頭
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")

# 新增一個路徑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")

# 新增一個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")

# 新增一條折線——注意這裡的折線和前面所畫的這顯示不一樣的,這裡的折線是一個形狀
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) 
#將折線新增到axes物件
ax.add_line(line)

plt.axis('equal')
plt.axis('off')
plt.tight_layout()

plt.show()

上面程式碼的執行結果如下:

五、路徑path

1、例項

路徑裡面所涉及到的類容相對較多,這裡只介紹簡單的應用。首先通過一個例子加以說明。這個例子是要繪製一個簡單的矩形路徑。

import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches

#import matplotlib.patheffects
#import matplotlib.transforms

verts = [
    (0., 0.), # 矩形左下角的座標(left,bottom)
    (0., 1.), # 矩形左上角的座標(left,top)
    (1., 1.), # 矩形右上角的座標(right,top)
    (1., 0.), # 矩形右下角的座標(right, bottom)
    (0., 0.), # 封閉到起點    ]

codes = [Path.MOVETO,
         Path.LINETO,
         Path.LINETO,
         Path.LINETO,
         Path.CLOSEPOLY,
         ]

path = Path(verts, codes) #建立一個路徑path物件

#依然是三步走
#第一步:建立畫圖物件以及建立子圖物件
fig = plt.figure()
ax = fig.add_subplot(111)

#第二步:建立一個patch,路徑依然也是通過patch實現的,只不過叫做pathpatch
patch = patches.PathPatch(path, facecolor='orange', lw=2)

#第三步:將建立的patch新增到axes物件中
ax.add_patch(patch)

#顯示
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
plt.show()

執行結果如下所示:

總結:通過上面的例子顯示,繪製 “路徑” 的過程和繪製普通的 “patch” 是大致一樣的,依然是遵循一個 “三步走”的步驟,核心在於第二步,也是要建立一個PathPatch物件,它也是來自於patches包,和普通的rectangle,circle是等價的概念,

patch = patches.PathPatch(path, facecolor='orange', lw=2)

但是這裡的path物件是要事先自己建立的。

總結:實際上,我們

matplotlib中的rectangle、circle、polygon等所有簡單的簡單圖形都採用簡單的路徑path去實現的,只不過用類的形式進行了更高階的封裝。像直方圖hist () 條形圖bar ()這樣的繪圖函式建立了許多基元影象,它們的本質也是通過路徑去實現的, 下面將使用路徑去繪製一個條形統計圖。

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)

# 固定隨機數種子
np.random.seed(19680801)

# 產生1000組隨機數,並進行組織
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

#第二步:構造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()

執行結果如下:

總結:從上面可以得知,我們的繪圖,包括條形圖,扇形圖等都是通過基本的簡單的路徑path去實現的,但是這樣做起來很麻煩,喊不簡單,因而使用hist、bar等高層函式進一步封裝,簡化繪圖操作。

2、path物件

首先需要匯入matplotlib.path模組。

在使用路徑的時候一般需要兩個重要的引數,Path類的定義如下:

class Path(vertices, codes=None, _interpolation_steps=1, closed=False, readonly=False)

故而需要傳遞兩個必要的引數:

rectpath = path.Path(vertices, codes)

那麼vertices和codes到底是什麼意思呢?

vertices是指的是路徑path所經過的關鍵點的一系列座標(x,y)

codes指的是點與點之間到底是怎麼連線的,是直線連線?曲線連線?還是。。。

(1)vertices

vertices = [

(0., 0.), # left, bottom

(0., 1.), # left, top

(1., 1.), # right, top

(1., 0.), # right, bottom

(0., 0.), # ignored

]

(2)codes

codes = [

Path.MOVETO,

Path.LINETO,

Path.LINETO,

Path.LINETO,

Path.CLOSEPOLY,

]

path = Path(verts, codes)  #建立path物件。vertices好理解,那麼codes到底什麼意思?

 

  • MOVETO :拿起鋼筆, 移動到給定的頂點。一般指的是 “起始點” 

  • LINETO :從當前位置繪製直線到給定頂點。

  • CURVE3 :從當前位置 (用給定控制點) 繪製一個二次貝塞爾曲線到給定端點。

  • CURVE4 :從當前位置 (與給定控制點) 繪製三次貝塞爾曲線到給定端點。

  • CLOSEPOLY :將線段繪製到當前折線的起始點。

  • STOP :整個路徑末尾的標記 (當前不需要和忽略)

總結:在建立vertices和codes的時候,每個點和每一個codes是對應著的,如上面所示,一定要注意這樣的對應關係。

(3)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)   #使用內建的Zip函式

heartpath = path.Path(verts, codes)   #建立Path物件

patch = mpatches.PathPatch(path)    #將path包裝成一個patch物件

patches.append(patch)

3、補充

上面知識介紹了一些最基本的路徑path的操作,路徑的各種操作很複雜,還有各種各樣的路徑操作函式,還有路徑效果和相關的一些操作,在

import matplotlib.patheffects

import matplotlib.transforms

這兩個模組裡面,關於這兩個模組的操作,這理由不討論了,有興趣可以查閱官方文件。