matplotlib是一個基於Python的繪圖庫,具有對2D的完全支持和對3D圖形的有限支持,在Python科學計算社區中廣泛使用。
本文對matplotblib的基本繪圖邏輯進行了比較詳細的梳理。寫作過程中參考了很多資料,由於筆記是斷續的,有些可能忘記引用,在此表達感謝。
限於篇幅,本文並不會對一些封裝比較高級的繪圖接口進行介紹。此外,由於繪圖中涉及到的類與接口實在是太多,不可能面面俱到。
有必要提及,Matplotlib的主要開發者John D. Hunter是一名神經生物學家,但2012年不幸因癌症去世,感謝他開發出了一個這麼優秀的庫。
如果你覺得看完本文不夠,下面是一些我認為比較好的文章:
開發團隊的隨感:http://www.aosabook.org/en/matplotlib.html
公眾號《王的機器》推文:https://mp.weixin.qq.com/s/vUaIfjM7R2Ac3ICPBh39UQ
以及最重要的API文檔:
http://www.matplotlib.org/
下面是本文的索引。
1.三種繪圖模式在matplotlib的網上教程中,經常可以看到很多種作圖模式,繪製出一樣的圖。
有時候容易凌亂,因此做一個簡單的梳理。
根據繪圖理念的不同,分成了3種。
不同的繪圖模式,本質上是利用了Matplotlib的不同層提供的接口,在下一節會具體介紹。
本文的重點會放在面向對象上。
1.pylab
函數式作圖,最接近matlab,但是不推薦。
from pylab import *
2.pyplot交互式
(適合IPython這種交互式編輯器,本質上是利用pyplot腳本層對底層的封裝,使得繪圖非常簡單)
非常簡單的交互式作圖,直接根據命令作圖,但個性化能力有限。
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1,11)
y = np.random.random(10)
plt.plot(x,y)
plt.title('pyplot')
plt.show()
3.面向對象式
最接近底層的作圖模式,貫徹了萬物皆對象的理念,可以定製很多高難度的圖。
流程可以表述為:
FigureCanvas 實例實例化 Figure實例(添加到畫板上)
使用 figure 實例創建一個或多個 Axes 或 Subplot 實例(放置畫紙)
使用 Axes實例方法創建 具體構圖零件。(畫畫)
import numpy as np
from matplotlib.backends.backend_agg import
FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
fig = Figure()
canvas = FigureCanvas(fig)
x = np.random.randn(10000)
ax = fig.add_subplot(111)
ax.hist(x, 100)
fig.savefig('matplotlib_histogram.png')
但有時候,我們也會將交互式與面向對象式進行結合,比如:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1,11)
y = np.random.random(10)
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
plt.plot(x,y)
ax1.set_title('object oriented')
對於面向對象式+交互式結合的方法,一個基本的繪圖流程可以表述為:
創建一個 figure 實例;使用 figure 實例創建一個或多個 Axes 或 Subplot 實例;使用 Axes實例方法創建具體構圖零件。
2.面向對象繪圖基礎參考資料:
http://www.aosabook.org/en/matplotlib.html
matplotlib有三層結構。
三層結構可以看作一個棧結構。上一層的layer懂得如何調用下一層layer,但下一層的layer無法調用上層的layer.
棧結構如圖:
自頂向下依次是scripting artist backend
2.1 matplotlib三層結構1.backend參考資料:
https://matplotlib.org/3.1.0/api/backend_bases_api.html
後端層
後端層有三個內置的抽象接口類:
FigureCanvas(類似於畫板):即matplotlib.backend_bases.FigureCanvas,它定義並包含繪製圖形的區域。對於一些UI工具(例如:Qt),FigureCanvas內部有具體的實現,它知道如何把自己(畫板)插入到一個UI界面中,也知道如何調用Renderer繪製到Canvas上,同時,一些UI操作事件也可以被自動轉化為matplotlib Event。
Renderer(類似於畫筆):即matplotlib.backend_bases.Renderer,Renderer類的一個實例用於在圖形畫布上繪製。它是一個基於像素的核心渲染器,調用了C++的:Anti-Grain Geometry(Agg)。Agg是一個C ++高性能圖像模板庫,基於像素的核心渲染器。這是一個高性能庫,用於渲染消除鋸齒的2D圖形,從而生成有吸引力的圖像。matplotlib提供了將像素緩衝區插入到交互界面的支持,這些像素由agg後端渲染產生。因此可以跨UI和作業系統獲得像素精確的圖形。
Event(事件,由用戶定義):即matplotlib.backend_bases.Event,處理用戶輸入,如鍵盤敲擊和滑鼠點擊。Event框架把鍵盤/滑鼠事件映射到了2個類KeyEvent 和 MouseEvent上,用戶通過觸發事件,框架內部就會調用相關函數。
2.artistartist層包含了一個最主要的對象Artist。Artist對象知道如何獲取Renderer實例,並使用它在Canvas實例上進行畫畫。
我們在Matplotlib圖上看到的所有內容都是Artist實例。
包括 title, line ,tick labels,images等等。
matplotlib.artist.Artist是所有Artist類的基類,它包含了所有Artist類共有的屬性:將artist對象的坐標系統轉化成canvas對象的坐標系統。
artist對象如何與backend發生耦合?
artist對象的類必須實現draw方法,該方法能夠從後端傳入render實例,由於render實例有一個指向想要繪製的canvas類型(可以是PDF,SVG等)的指針,因此,它會調用合適的方法在上面進行繪製。
class SomeArtist(Artist):
'An example Artist that implements the draw method'
def draw(self, renderer):
"""Call the appropriate renderer methods to paint self onto canvas"""
if not self.get_visible(): return
renderer.draw_path(graphics_context, path, transform)
Artist對象有兩種類型
第一種類型是原始類型(primitive type):例如Line2D, Rectangle, Circle, Text類 的實例。
第二種類型是複合類型(composite type),例如Figure,Axes,Tick,Axis類的實例。
幾個重要的點:
1.figure Artist是一個圖中所有元素的頂層對象,包含並管理這些元素。
2.composite type中最重要的對象是axes,因為它是matplotlib API幾乎所有方法發揮作用的地方,包括創建並操縱刻度線(ticks),軸線(axis lines),grid或background。
3.一個composite type可以包含其他的composite type以及primitive type。比如:一個figure(畫布)可以包含多個坐標軸(axes)以及text等元素,它的背景是Rectangle實例。
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
fig = Figure()
canvas = FigureCanvas(fig)
import numpy as np
x = np.random.randn(10000)
ax = fig.add_subplot(111)
ax.hist(x, 100)
ax.set_title('Normal distribution with $\mu=0, \sigma=1$')
fig.savefig('matplotlib_histogram.png')
下圖模擬了人類繪畫與matplotlib繪畫的過程:
為非專業程式設計師的科學家開發,腳本層本質上是Matplotlib.pyplot接口,它將定義畫布(canvas)和定義figure artist實例的過程自動化,並連接它們,因此,腳本層本質上是前兩層的wrapper。
pyplot接口是圍繞核心Artist API的一個相當薄的包裝器,它試圖通過使用最少量樣板代碼暴露腳本接口中的API函數。
通過matplotlib提供的狀態腳本接口,可快速輕鬆以MATLAB繪圖風格繪製圖形。
來看一個通過腳本層繪圖的實例。(本質上是前面提到的交互式方式)
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(10000)
plt.hist(x, 100)
plt.title(r'Normal distribution with $\mu=0, \sigma=1$')
plt.savefig('matplotlib_histogram.png')
plt.show()
而事實上,我們絕大多數用戶使用的都是腳本層的接口。僅僅使用一個plt函數(import matplotlib.pyplot as plt),就能夠完成絕大多數繪圖。
為什麼腳本層無需要指定fig就可以實現繪圖?
Pyplot 為底層繪圖庫對象提供了有限狀態機接口,從而引入了當前軸(current axes) 的概念。
state-machine 會自動和以用戶無感的方式創建 Figures(圖)對象 和 axes (軸域),以實現所需的繪圖操作。
具體一點: plt.plot() 的第一個調用將自動創建 Figure 和 Axes 對象,以實現所需的繪圖。對 plt.plot() 後續的調用會重複使用當前 Axes 對象,並每次添加一行。設置 title 標題、legend 圖例等,都會使用當前 Axes 對象,設置相應的 Artist。
利用一個簡版的例子來看一下腳本層是如何對artist和canvas進行包裝的。
@autogen_docstring(Axes.plot)
def plot(*args, **kwargs):
ax = gca()
ret = ax.plot(*args, **kwargs)
draw_if_interactive()
return ret
Canvas是位於最底層的系統層,在繪圖的過程中充當畫板的角色,即放置畫布的工具。
通常情況下,我們並不需要對Canvas特別的聲明,但是當我需要在其他模塊如PyQt中調用Matplotlib模塊繪圖時,就需要首先聲明Canvas,這就相當於我們在自家畫室畫畫不用強調要用畫板,出去寫生時要特意帶一塊畫板。
Figure是Canvas上方的第一層,也是需要用戶來操作的應用層的第一層,在繪圖的過程中充當畫布的角色。
當我們對Figure大小、背景色彩等進行設置的時候,就相當於是選擇畫布大小、材質的過程。因此,每當我們繪圖的時候,寫的第一行就是創建Figure的代碼。
Axes是應用層的第二層,在繪圖的過程中相當於畫布上的繪圖區的角色。 一個Figure對象可以包含多個Axes對象,每個Axes都是一個獨立的坐標系,繪圖過程中的所有圖像都是基於坐標系繪製的。
總結:對於普通用戶,只需要記住:Figure是畫布,axes是繪畫區,axes可以控制每一個繪圖區的屬性。
本節會做一個基本介紹。
創建figure創建一個畫布,可以指定很多參數
import matplotlib.pyplot as plt
fig = plt.figure(
num = None,
figsize=None,
dpi=None,
facecolor=None,
edgecolor=None, frameon=True,
FigureClass=<class 'matplotlib.figure.Figure'>,
clear=False,
**kwargs)
e.g.
fig = plt.figure(num=1,figsize = [10,10],dpi=400,
facecolor = 'grey',
edgecolor = 'blue')
這樣,我們創建出來的畫布就是灰色的了
創建多個畫布
當然,順著面向對象的概念,也可以創建多個畫布,即:創建多個畫布對象即可。
fig1 = plt.figure()
ax11 = fig1.add_subplot(1,1,1)
ax11.plot([1,2],[1,2])
fig2 = plt.figure()
ax21 = fig2.add_subplot(1,1,1)
ax21.plot([1,2],[1,2])
參考
https://matplotlib.org/api/asgen/matplotlib.figure.Figure.html
figure來自matplotlib.figure.Figure類,Figure繼承了Artist類,是所有繪圖元素的頂層容器。
通過pyplot腳本層進行包裝,我們可以直接通過plt.figure()調用該類。
Figure的屬性包含了 Patch,在笛卡爾坐標中,patch 是 Rectangle,在極坐標中是 Circle。這個 patch 確定了繪圖區的 shape,background 和 border。
笛卡爾坐標系統的fig(默認)通過屬性Rectangle實例對其background patch進行填充,見下面的實現:
self.patch = Rectangle(
xy=(0, 0), width=1, height=1,
facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth,
visible=frameon)
fig一些常見的屬性與方法
import matplotlib.pyplot as plt
fig = plt.figure()
print(fig)
fig.suptitle('this is a test')
figure.patch
figure.axes
例子:
for ax in fig.axes:
ax.axis('off')
for ax in fig.axes:
ax.grid(True)
例子1:
例子2:
for ax in fig.axes:
rect = ax.patch
rect.set_facecolor('g')
rect.set_alpha(0.1)
效果:
下表是 figure 包含的一些Artist
僅僅舉幾個例子,後面會經常看到
setter:
fig.set_edgecolor()
fig.set_facecolor()
getter:
##返回一個axes列表,可以對坐標軸進行控制
fig.get_axes()
matplotlib.axes.Axes 是 matplotlib 的核心。它包含了 figure 中使用的 大部分Artist ,而且包含了許多創建和添加 Artist 的方法。
當然這些函數也可以獲取和自定義 Artists。
axes擁有很多屬性。
這些屬性本質上是一個對象,都屬於artist基類。
關係如下:
一個簡單的例子:
• spines: 指的是axes邊框部分,分為上下左右。
• patch:指的是spin圍成的2D區域
• line:設置線條(顏色、線型、寬度等)和標記
• ticks:指的是刻度線
axes與subplot的區別我們常常在一些教程中看到一幅圖內嵌套了多個圖,或者一個圖內分成了好多子圖。
一幅圖 (Figure) 中可以有多個坐標系 繪圖區(Axes),
一幅圖中可以有多幅子圖繪圖區 (Subplot),
那麼坐標系和子圖是不是同樣的概念?
在絕大多數情況下是的,兩者有一點細微差別:
坐標系在母圖中的網格結構可以是不規則的,子圖subplot是坐標系axes的一個特例,子圖在母圖中的網格結構必須是規則的,而坐標系在母圖中的網格結構可以是不規則的。由此可見,子圖是坐標系的一個特例。
(subplot就是自動整齊的排列好子圖了,axes就是自己手動排,就更靈活和麻煩。)
創建axes關於axes 子圖的排版,詳細的參考:
https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py
axes的創建方式
坐標系繪圖法比子圖更通用,更靈活,當然也更難。
常見的有3種生成方式
• 用 gridspec 包加上 subplot()
• fig.add_axes()
• 用 plt.axes()
plt.axes([l,b,w,h])
其中 [l, b, w, h] 可以定義坐標系:
l 代表坐標系左邊到 Figure 左邊的水平距離
b 代表坐標系底邊到 Figure 底邊的垂直距離
w 代表坐標系的寬度
h 代表坐標系的高度
1.用 gridspec 包加上 subplot()
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig2 = plt.figure(constrained_layout=True)
spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=fig2)
f2_ax1 = fig2.add_subplot(spec2[0, 0])
f2_ax2 = fig2.add_subplot(spec2[0, 1])
f2_ax3 = fig2.add_subplot(spec2[1, 0])
f2_ax4 = fig2.add_subplot(spec2[1, 1])
利用切片形式進行更加個性化的定製:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig3 = plt.figure(constrained_layout=True)
G = gridspec.GridSpec(ncols=3, nrows=2, figure=fig3)
ax1 = fig3.add_subplot(G[0,:])
ax2 = fig3.add_subplot(G[1,:2])
ax3 = fig3.add_subplot(G[1,2])
ax1.set_xticklabels('')
for i in ax1.xaxis.get_ticklines():
i.set_markersize(0)
i.set_markeredgewidth(0)
ax1.set_yticklabels('')
for i in ax1.yaxis.get_ticklines():
i.set_markersize(0)
i.set_markeredgewidth(0)
繪製一個重疊圖:
plt.axes([0,0,1,1],facecolor = 'g')
plt.xticks([])
plt.yticks([])
plt.axes([0.2,0.2,1,1],facecolor = 'r')
plt.xticks([])
plt.yticks([])
plt.axes([0.4,0.4,1,1],facecolor = 'b')
plt.xticks([])
plt.yticks([])
2.fig.add_axes()
fig = plt.figure()
ax1 = fig. add_axes()
ax1.plot([1,2,3],[1,2,3])
ax2 = fig.add_axes([0.7,0.1,0.3,0.3])
ax2.plot([1,2,3],[1,2,3])
3.plt.axes() ##腳本層的封裝
###坐標系1
plt.axes([0.1,0.1,1,1])
plt.plot([1,2,3],[1,2,3])
###坐標系2
plt.axes([[0.7,0.1,0.3,0.3])
plt.plot([1,2,3],[1,2,3])
兩者效果是一樣的:
axes能不能做到和subplot效果相似?可以
只需要按照對應位置調整好坐標即可
###坐標系1
plt.axes([0.1,0.1,1,1])
plt.plot([1,2,3],[1,2,3])
###坐標系2
plt.axes([0.1,1.2,1,1])
plt.plot([1,2,3],[1,2,3])
setter與getter是設計邏輯,幾乎所有的類對象都有setter與getter方法。通過getter與setter,能夠獲取/改變該實例的屬性。
通過setter getter可以獲取很多artist子類的屬性。
matplotlib文檔:https://matplotlib.org/api/axes_api.html#the-axes-class
有非常詳細的介紹。
一些常見的屬性如下:
ax.set_*
ax.get_*
*包括:
alpha
title
tick
xticks
yticks
label
xlabel
ylabel
xlim
ylim
#
ax.get_xaxis()
ax.get_yaxis()
#標籤+刻度構成了axis
#
ylabel
xlabel
#
xticklabels
yticklabels
get_legend_handles_label
...
也可以通過:
fig.axes[0].title()找到這些對象
#
ax.get_ylim()
ax1.set_ylim(0,5)
#
ax.set_ylabel('y_label',rotation =45) ##旋轉45度
#一個batch setter
axes.set()
#e.g
ax2.set(xlim=[0,2], zorder=2)
謹記:獲取對象的目的是為了調用屬於對象實例的方法。
比如:
通過ax.patch獲取了ax的patch,接下來通過調用patch的set_color函數進行設定顏色。
ax1.patch.set_color('g')
ax1.patch.set_alpha(0.1)
ax.plot():折線圖
##當調用 ax.plot 時,會創建 Line2D 實例,並將實例添加到 Axes.lines 列表。
ax.plot([1,100],[1,100])
ax.plot([3,2],[3,2])
ax.lines
##lines列表記錄了plot生產的Line2D對象
[<matplotlib.lines.Line2D at 0x13a3d2f4f60>,
<matplotlib.lines.Line2D at 0x13a3b01c198>]
ax.hist():頻率分布圖
ax.hist()會返回3個對象:
#n:頻率
#bins:箱子的橫坐標
#patches:每一個箱子patch構成的列表,可以通過patches的索引追蹤到每一個patch,並進行人為操作
n, bins, patches = ax.hist(np.random.randn(1000), 50,
facecolor='yellow', edgecolor='yellow')
要注意,對patch進行操作也有一系列setter和getter,參考patch一節。
3.subplot:子圖繪圖區子圖subplot是坐標系axes的一個子類,即:一個特例。
子圖在母圖中的網格結構必須是規則的,而坐標系在母圖中的網格結構可以是不規則的。
創建subplot有2種方式(仍舊是第一節提到的三種作圖模式種的後兩種):交互式和面向對象式。
其中交互式和面向對象式又分別有兩種語法。
1.交互式(可以感覺到在繪製複雜圖形時,控制能力有限)
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1,11)
y = np.random.random(10)
plt.subplot(121)
plt.plot(x,y)
plt.subplot(122)
plt.plot(x,y)
plt.show()
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1,11)
y = np.random.random(10)
ax1 = plt.subplot(1,2,1)
ax2 = plt.subplot(1,2,2)
ax1.plot(x,y)
ax2.plot(x,y)
2.面向對象式(1):
利用add_subplot
x = np.arange(1,11)
y = np.random.random(10)
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
ax1.plot(x,y)
ax2.plot(x,y)
上面語句的等價於:
x = np.arange(1,11)
y = np.random.random(10)
fig, axes = plt.subplots(1,2)
axes[0].plot(x,y)
axes[1].plot(x,y)
• 創建子圖的形式總結:
1.(不推薦)交互式:直接用plt.subplot按順序操縱
2.面向對象:
ax1 = fig.add_subplot() ##最容易理解
plt.subplots() axes[0],axes[1] ##也不錯
https://matplotlib.org/api/_as_gen/matplotlib.lines.Line2D.html
Patch:形狀參考:
https://matplotlib.org/api/_as_gen/matplotlib.patches.Patch.html#matplotlib.patches.Patch
patch類繼承了artist,一個patch是一個2d的artist實例,包含了填充顏色+邊框顏色。如果沒有對其進行設置,會根據rc params的默認參數進行設置。
這點可以從Patch類的實現中看出:
def __init__(self,
edgecolor=None,
facecolor=None,
color=None,
linewidth=None,
linestyle=None,
antialiased=None,
hatch=None,
fill=True,
capstyle=None,
joinstyle=None,
**kwargs):
"""
The following kwarg properties are supported
%(Patch)s
"""
artist.Artist.__init__(self)
if linewidth is None:
linewidth = mpl.rcParams['patch.linewidth']
if linestyle is None:
linestyle = "solid"
...
patch對象由matplotlib.patches.Patch生成。
使用時,我們可以通過如下代碼進行調用:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='The red data')
plt.legend(handles=[red_patch])
plt.show()
• 創建常見的patch子類
很多常見的形狀繼承了patch類,包括:Rectangle、Circle、Polygon等,(matplotlib.patches.Rectangle類等)
創建一個具體的形狀本質上是在調用它的構造函數,構造函數有許多的參數可選,需要查閱具體文檔。
使用時,我們可以通過如下代碼進行調用:
import matplotlib.patches as mpatches
grid = np.mgrid[0.2:0.8:3j, 0.2:0.8:3j].reshape(2, -1).T
rect = mpatches.Rectangle(grid[1] - [0.025, 0.05], 0.05, 0.1, ec="none")
polygon = mpatches.RegularPolygon(grid[3], 5, 0.1)
arrow = mpatches.Arrow(grid[5, 0] - 0.05, grid[5, 1] - 0.05, 0.1, 0.1,
width=0.1)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
rect = plt.Rectangle([0.1,0.1],0.5,0.3,facecolor= 'g')
cir = plt.Circle([0.5,0.5],0.1)
ax.add_patch(rect)
ax.add_patch(cir)
plt.show()
效果:
• 將Patch添加到axes中
ax.add_patch()
patches=[]
patches.append(e1)
patches.append(e2)
collection=PatchCollection(patches)
ax.add_collection(collection)
• 通過setter/getter更改/查詢patch的屬性。
rectangle、circle等patch子類中內置了大量getter/setter,可以方便獲取/改變對應屬性。看一個例子:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
rect = plt.Rectangle([0.1,0.1],0.5,0.3,facecolor= 'g')
cir = plt.Circle([0.5,0.5],0.1)
ax.add_patch(rect)
ax.add_patch(cir)
plt.axis('off')
plt.show()
rect.get_fill()
rect.get_path()
rect.set_x(0.5)
rect.set_y(0.5)
圖像中的矩形發生了位移:
最後,要強調一點,所有封裝好的形狀本質上都是通過Path來進行實現的,換句話說,通過path類,我們能夠創建一些更複雜的自定義形狀。
Path:自定義形狀參考:
https://matplotlib.org/api/path_api.html
所有封裝好的形狀本質上都是通過Path來進行實現的,換句話說,通過path類,我們能夠創建一些更複雜的自定義形狀。
要想通過path類繪製patch,
首先,需要定義好頂點(vertice)的位置以及對應的code(表明vertex的類)。
code類型包括:
1.STOP
2.MOVETO:移動到當前頂點
3.LINETO:畫一條線到當前頂點
4.CURVE3:繪製2次貝塞爾曲線到當前頂點
5.CURVE4:繪製3次貝塞爾曲線到當前頂點
6.CLOSEPOLY:Draw a line segment to the start point of the current polyline.(封閉點)
通過一個例子來看看path類如何產生變量:
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
verts = [
(0., 0.),
(0., 1.),
(1., 1.),
(1., 0.),
(0., 0.)
]
codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]
path = Path(verts,codes)
fig = plt.figure()
ax = fig.add_subplot(111)
patch = patches.PathPatch(path, facecolor='orange', lw=2)
ax.add_patch(patch)
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
plt.show()
事實上,python中的histogram barplot等基礎的圖像也是通過path實現的,比如一個例子:
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)
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
barpath = path.Path(verts, codes)
patch = patches.PathPatch(barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
plt.show()
可以得到:
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
class QualityBoxplot(object):
def __init__(self,seq_len = 200):
self.__seq_len = seq_len
self.__fig = plt.figure()
self.__ax = self.__fig.add_subplot(111)
self.__ax.set_ylim(0,40)
self.__ax.set_xlim(0,self.__seq_len)
self.__fig.set_size_inches([15,12])
self.__low_quality_path()
self.__mid_quality_path()
self.__high_quality_path()
def __low_quality_path(self):
for i in range(self.__seq_len):
verts = [
(i, 0),
(i, 20),
(i+1, 20),
(i+1, 0.),
(i, 0.)
]
codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]
path = Path(verts,codes)
if (i % 2) == 0:
patch = patches.PathPatch(path, facecolor='r', lw=0.1,alpha = 0.1)
else:
patch = patches.PathPatch(path, facecolor='r', lw=0.1,alpha = 0.3)
self.__ax.add_patch(patch)
def __mid_quality_path(self):
for i in range(self.__seq_len):
verts = [
(i, 20),
(i, 30),
(i+1, 30),
(i+1, 20.),
(i, 20)
]
codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]
path = Path(verts,codes)
if (i % 2) == 0:
patch = patches.PathPatch(path, facecolor='y', lw=0.1,alpha = 0.1)
else:
patch = patches.PathPatch(path, facecolor='y', lw=0.1,alpha = 0.3)
self.__ax.add_patch(patch)
def __high_quality_path(self):
for i in range(self.__seq_len):
verts = [
(i, 30),
(i, 40),
(i+1, 40),
(i+1, 30.),
(i, 30)
]
codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]
path = Path(verts,codes)
if (i % 2) == 0:
patch = patches.PathPatch(path, facecolor='g', lw=0.1,alpha = 0.1)
else:
patch = patches.PathPatch(path, facecolor='g', lw=0.1,alpha = 0.3)
self.__ax.add_patch(patch)
if __name__ == '__main__':
qc = QualityBoxplot()
效果:
figure
已在前面有了介紹。
axes
已在前面有了介紹。
下圖中可以看到Axis(軸)由標籤(label)+刻度tick構成。
舉個例子
ax.get_xticks()
array([-4., -3., -2., -1., 0., 1., 2., 3., 4., 5.])
axis = ax.xaxis
axis.get_ticklocs()
array([-4., -3., -2., -1., 0., 1., 2., 3., 4., 5.])
ax.get_xlabel()
'time (s)'
ax.get_xticklabels()
for i in ax1.get_xticklabels():
print(i)
ax.set_xticks([0,1,2,3,5,6])
ax.set_xlabel('This is a x label')
效果:
以上講的幾乎都是ax的方法,下面看一些屬於axis的方法和屬性
axis = ax.xaxis
xis.get_ticklocs()
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
axis.get_ticklabels()
<a list of 10 Text major ticklabel objects>
axis.get_ticklines()
<a list of 20 Line2D ticklines objects>
axis.get_ticklines()
<a list of 20 Line2D ticklines objects>
axis.get_ticklines(minor=True)
<a list of 0 Line2D ticklines objects>
下面是 axis 的一些非常有用的獲取方法,都由相對應的設置方法:
一些例子
for i in ax1.xaxis.get_ticklabels():
print(i)
i.set_rotation(45)
for i in ax1.xaxis.get_ticklines():
print(i)
i.set_markersize(10)
i.set_markeredgewidth(3)
j = 0
for i in ax1.xaxis.get_ticklines():
j+=1
if (j % 3) ==0:
print(i)
i.set_markersize(0)
i.set_markeredgewidth(0)
如果想要針對性的刪除某幾個刻度,可以直接根據索引取到相應的位置,把寬度和長度設為0即可。
##刪除第3個刻度
ax1.xaxis.get_ticklines()[6].set_markersize(0)
ax1.xaxis.get_ticklines()[6].set_markeredgewidth(0)
tick也是一個組成類型,它由具體的tick label與grid line構成,這些都可以直接作為 Tick 的屬性而被獲取。還有一些布爾值變量用來控制是否設置 x 軸上方 ticks 和 labels,y 軸右側 ticks 和 labels。
通過一副圖來看一下什麼是tick label,什麼是grid line
在前面axis一節,我們會發現ticklabel的對象個數是顯示出來個數的2倍,另一半被隱藏起來了,這一節我們試圖顯示另一半。
for i in ax1.xaxis.get_major_ticks():
i.label1On = True
i.label2On = True
class matplotlib.ticker.Locator是一個單獨的類,用於決定tick的在spine上的排布特點。
在實際使用的時候,並不需要過多關注locator這個類的具體內容,只需要清楚它可以作為ax.xaxis.set_major_locator()的一個參數傳遞即可。
有很多種locator類型,不同的 locator() 可以生成不同的刻度對象,我們來研究以下 8 種:
NullLocator(): 空刻度
MultipleLocator(a): 刻度間隔 = 標量 a
FixedLocator(a): 刻度位置由數組 a 決定
LinearLocator(a): 刻度數目 = a, a 是標量
IndexLocator(b, o): 刻度間隔 = 標量 b,偏移量 = 標量 o
AutoLocator(): 根據默認設置決定
MaxNLocator(a): 最大刻度數目 = 標量 a
LogLocator(b, n): 基數 = 標量 b,刻度數目 = 標量 n
下面是一些例子
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize = (10,24))
fig.tight_layout()
def init(ax):
ax.spines['top'].set_color(None)
ax.spines['right'].set_color(None)
ax.spines['left'].set_color(None)
for i in ax.yaxis.get_ticklabels():
i.set_fontsize(0)
for i in ax.yaxis.get_ticklines():
i.set_markersize(0)
i.set_markeredgewidth(0)
for i in ax.xaxis.get_ticklines():
i.set_markersize(8)
i.set_markeredgewidth(2)
for i in ax.xaxis.get_ticklabels():
i.set_fontsize(20)
i.set_fontfamily('serif')
ax1 = fig.add_subplot(611)
init(ax1)
ax1.xaxis.set_major_locator(ticker.NullLocator())
ax1.text(0,0.1,'ax1 Nulllocator',fontsize = 20)
ax2 = fig.add_subplot(612)
init(ax2)
ax2.xaxis.set_major_locator(ticker.MultipleLocator(0.5))
ax2.xaxis.set_minor_locator(ticker.MultipleLocator(0.1))
for i in ax2.xaxis.get_minorticklines():
i.set_markersize(5)
i.set_markeredgewidth(2)
ax2.text(0,0.1,'ax2 Multiplelocator',fontsize = 20)
ax3 = fig.add_subplot(613)
init(ax3)
ax3.xaxis.set_major_locator(ticker.FixedLocator([0,0.1,0.5,1]))
ax3.xaxis.set_minor_locator(ticker.FixedLocator([0.02,0.04,0.06,0.08]))
for i in ax3.xaxis.get_minorticklines():
i.set_markersize(5)
i.set_markeredgewidth(2)
ax3.text(0,0.1,'ax3 Fixedlocator',fontsize = 20)
ax4 = fig.add_subplot(614)
init(ax4)
ax4.xaxis.set_major_locator(ticker.LinearLocator(3))
ax4.xaxis.set_minor_locator(ticker.LinearLocator(10))
for i in ax4.xaxis.get_minorticklines():
i.set_markersize(5)
i.set_markeredgewidth(2)
ax4.text(0,0.1,'ax4 Linear locator',fontsize = 20)
plt.subplots_adjust(hspace =0.2)
def format_func(value, tick_number):
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif N == 1:
return r"$\pi/2$"
elif N == 2:
return r"$\pi$"
elif N % 2 > 0:
return r"${0}\pi/2$".format(N)
else:
return r"${0}\pi$".format(N // 2)
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
fig
也可以通過set_yticklabels()函數來自定義tick label內容。
ax1.set_yticklabels(['a','b','c','d'])
通過幾個實例,來全面鞏固一下坐標軸的DIY畫法
for spine in ["left", "top", "right"]:
ax.spines[spine].set_visible(False)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig = plt.figure(figsize = (10,24))
fig.tight_layout()
def init(ax):
for spine in [ "top", "right"]:
ax.spines[spine].set_visible(False)
ax1 = fig.add_subplot(611)
init(ax1)
ax1.patch.set_color('orange')
ax1.patch.set_alpha(0.5)
for i in ax1.yaxis.get_ticklabels():
i.set_fontsize(0)
for i in ax1.yaxis.get_ticklines():
i.set_markersize(0)
i.set_markeredgewidth(0)
for i in ax1.xaxis.get_ticklabels():
i.set_fontsize(20)
i.set_fontfamily('serif')
i.set_color('orange')
i.set_rotation(45)
for i in ax1.xaxis.get_ticklines():
i.set_markersize(8)
i.set_markeredgewidth(2)
i.set_color('orange')
ax1.spines['left'].set_linewidth(2)
ax1.spines['left'].set_color('orange')
ax1.set_ylabel('ax1 ylabel',fontsize = 20,color = 'orange')
ax1.set_xlabel('ax1 xlabel',fontsize = 20,color = 'orange')
ax1.spines['bottom'].set_linewidth(2)
ax1.spines['bottom'].set_color('orange')
ax1.set_title('ax1 title',fontsize = 20,color = 'orange')
ax2 = fig.add_subplot(612)
init(ax2)
cur_labels = [item.get_text() for item in ax2.get_xticklabels()]
new_labels = ['tick1','tick2','','','tick5']
ax2.set_xticklabels(new_labels)
ax2.tick_params('x',direction = 'in')
for i in ax2.xaxis.get_ticklines():
i.set_markersize(8)
i.set_markeredgewidth(2)
ax3 = fig.add_subplot(613)
init(ax3)
ax3.spines['top'].set_visible(True)
ax3.twiny()
plt.subplots_adjust(hspace =2)
在matplotlib中,Legend本質是一個composite type,也叫一個容器類,由handle和text對象組成。
通常不需要用戶顯示的創建一個Legend實例,而是直接通過調用ax.legend()函數返回一個legend對象,從而創建圖例。
一個legend由一個或多個entry組成。
Legend = entry1+(entry2)+…
entry=key+label
key:圖標
label:text描述
handle(句柄):用於產生entry的原始對象。(比如散點,曲線等;)
.legend()Ax 或figure調用legend()函數會返回一個legend對象。
例子1:自動添加legend
一個簡單的例子來說明什麼是handle,什麼是label:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
fig.subplots_adjust(top=0.8)
ax1 = fig.add_subplot(211)
ax1.set_ylabel('volts')
ax1.set_title('a sine wave')
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2*np.pi*t)
line, = ax1.plot(t, s, color='blue',label='sin plot', lw=2)
ax1.legend()
handles, labels = ax1.get_legend_handles_labels()
通過axes.get_legend_handles_labels()可以獲取到handles對象以及labels,不難發現,在本例,handles是一個Line2D對象,labels是一個Text文本列表
axes.get_legend_handles_labels()
該函數會返回一個handles(或者說能夠轉化成handles的artist對象)列表。
例子2:手動添加legend
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(211)
scatters= ax1.scatter([1,3,4],[2,3,5])
ax1.legend()
例子2可以看出,如果在創建一個散點對象(handle)時,沒有指定label,那麼即使調用了legend()函數,也不會把handle和label放進legend對象中。
解決方案:
要麼在創建散點時,指定Label:
scatters= ax1.scatter([1,3,4],[2,3,5],label='scatter')
要麼手動添加handles 與對應的labels
一些常見的用法
##由於已經有了label,因此調用legend不需要再指定label
line_up, = plt.plot([1,2,3], label='Line 2')
line_down, = plt.plot([3,2,1], label='Line 1')
plt.legend(handles=[line_up, line_down])
##該圖顯示的label是'Line Up', 'Line Down'
line_up, = plt.plot([1,2,3], label='Line 2')
line_down, = plt.plot([3,2,1], label='Line 1')
plt.legend([line_up, line_down], ['Line Up', 'Line Down'])
例子3:自定義legend
要注意,並不是所有artist對象都能添加到legend。
也並不是handles必須存在於axes中才能添加到legend上,可以人為添加。
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='The red data')
plt.legend(handles=[red_patch])
plt.show()
Legend位置設置
位置設置有3種方法:
1.調用legend()函數的loc參數
Axes.legend()中的參數loc可以指定位置。
e.g
ax1.legend(handles=[scatters],labels=['add a scatter'],loc=1)
2.調用legend()函數的bbox_to_anchor參數。(2個或4個浮點構成的tuple)
4元組:(x, y, width, height)
2元組:(x, y)
3.loc參數與bbox_to_anchor參數相結合。
ax1.legend(loc='lower left',bbox_to_anchor=(0.5, 0.5)
ax1.legend(loc='upper left',bbox_to_anchor=(0.5, 0.5)
設置多個legend
如果多次調用ax.legend(),只能不斷地更新,並只顯示1個legend.
如果想要添加多個legend,可以參考下面的例子:
交換legend順序
import numpy as np
import matplotlib.pyplot as plt
generate random data for plotting
x = np.linspace(0.0,100,50)
y2 = x*2
y3 = x*3
y4 = x*4
y5 = x*5
y6 = x*6
y7 = x*7
plot multiple lines
plt.plot(x,y2,label='y=2x')
plt.plot(x,y3,label='y=3x')
plt.plot(x,y4,label='y=4x')
plt.plot(x,y5,label='y=5x')
plt.plot(x,y6,label='y=6x')
plt.plot(x,y7,label='y=7x')
get current handles and labels
this must be done AFTER plotting
current_handles, current_labels = plt.gca().get_legend_handles_labels()
sort or reorder the labels and handles
reversed_handles = list(reversed(current_handles))
reversed_labels = list(reversed(current_labels))
call plt.legend() with the new values
plt.legend(reversed_handles,reversed_labels)
plt.show()
本質上,我們看到的plt.*()的形式,都是腳本層進行封裝後的函數。
plt.axis('equal')
plt.axis('off')
plt.xlabel('test')
plt.ylabel('')
腳本層提供了類似於MATLAB的編程語法,可以很輕鬆地畫出想要的圖形。由於實在太過簡單,這裡不詳細介紹。
補充:jupyter的魔法函數利用jupyter notebook非常方便,下面是一些常用的魔法函數。
%matplotlib inline
優點:將inline作為後端傳遞,可強制在瀏覽器中呈現圖形。
局限:是無法在render後修改圖形。(換句話說,一旦圖片繪製完畢,無法進一步添加標題等零件。)
%matplotlib notebook
notebook後端能夠克服創建圖片後無法修改的問題。在notebook後端到位的情況下,如果調用了plt函數,它會檢查是否存在激活的figure對象,如果有,調用的任何函數都將作用於該figure對象。
如果激活的figure對象不存在,它會創建一個新figure對象。
例子: