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
這兩個模組裡面,關於這兩個模組的操作,這理由不討論了,有興趣可以查閱官方文件。