Matplotlib 提供了 Matlab 與物件導向 (OOP) 兩種風格的介面, 在前面的 Matplotlib 測試中都是使用 Matlab 風格, 此乃模仿自 Matlab, 以狀態為基礎 (stateful) 的簡易繪圖介面, 它會追蹤目前的作用中畫布物件與繪圖區物件, 只要呼叫 pyplot.plot() 函數即可在目前作用中的繪圖區物件上畫圖, 這種寫法優點是快速簡易, 但是無法做細部微調. 較進階的設定 (例如版面配置等) 必須使用物件導向方式.
更多 Python 筆記參考 :
本篇測試參考書籍如下 :
# Hands-on Matplotlib (Ashwin Pajankar, 2022, Appress)
# Python 資料科學學習手冊 (何敏煌譯, 2017, 碁峰)
# 王者歸來-Python 在大數據科學計算上的最佳實作 (佳魁, 張若愚)
# Python 程式設計學習經典 (碁峰 2018)
# 新手村逃脫! 初心者的 Python 機器學習攻略 (博碩 2020, 郭耀仁)
一. Matplotlib 的物件結構 :
Matplotlib 本質上是一個物件導向繪圖函式庫, 由底層的 FigureCanvas, Renderer, 以及高層的 Artist 物件構成, FigureCanvas 負責建立繪圖用的畫布; Renderer 負責在 FigureCanvas 畫布上渲染繪圖, 而 Artist 物件則負責所有高層結構.
Artist 物件分成簡單型與容器型兩種, 簡單型 Artist 物件用來建構基本繪圖元素, 例如線條, 文字, 圖像等; 而容器型 Artist 物件則是用來盛裝簡單型 Artist 物件, 透過繼承容器型 Artist 物件衍生出 Axis, Axes, 與 Figure 等實際繪圖使用之物件, 其用途如下 :
- Figure 物件 : 可以理解為一塊畫布, 是最上層的 Artist 物件, 用來盛裝所有圖表元素, 其下一層容器為 Axes 物件, 一個 Figure 裡面可以包含多個 Axes 物件 (子圖).
- Axes 物件 : 可以理解為畫布中的一塊具有獨立坐標系的繪圖區域, 也是 Matplotlib 繪圖的核心物件, 它包含了組成圖表的各種 Artist 物件, 例如 Line2D 物件 (圖形), Text (文字), AxesImage (圖像) 等.
- Axis 物件 : 此容器用來盛裝刻度線, 刻度標籤, 座標網格, 以及座標標題.
三者之層次如下圖所示 :
Matplotlib 將畫布與繪圖區中的全部元素都視為物件, Figure 物件代表整塊畫布 , 裡面可包含一個以上具有獨立坐標系的繪圖區, 稱為 Axes 物件 (亦即子圖), 繪圖區也是由多個物件組成, 例如核心的 Line2D (圖形), AxesImage (圖片), Text (標註文字), 以及 Axis 物件所裝的 X, Y 軸刻度等.
事實上, 在 Matplotlib 的物件導向繪圖實務中, 會直接操作的只有 Figure 與 Axes 物件而已, Artist 物件則是在呼叫 Figure 與 Axes 物件的方法時自動建立. 一個 Python 程式中可以建立多個 Figure 物件 (會產生多個繪圖視窗); 每個 Figure 物件裡面可以建立多個 Axes 物件 (會在畫布中產生多個子圖).
二. Figure 與 Axes 物件 :
Matplotlib 的物件導向模式繪圖程序如下 :
- 建立 Figure 物件 (畫布)
- 在 Figure 物件中建立一或多個 Axes 物件 (繪圖區)
- 呼叫 Axes 物件的方法建立簡單型 Artist 物件 (Line2D 與 Text 等圖表元素物件)
可見 Artist 物件是 Matplotlib 物件導向繪圖模式的基礎, 但在實務上 Axes 物件才是主角.
首先來看 Figure 物件, 之前在進階篇中曾呼叫 pyplot.figure() 函式並傳入參數來更改預設畫布的設定 (尺寸, 解析度, 背景色, 邊框顏色與寬度等), 事實上呼叫此函式會建立一個 Figure 物件, 只是沒將傳回來的物件存起來而已, 參考下面這篇 :
1. 建立 Figure 物件 :
在 mtplotlib.pyplot 模組中有一個 Figure 類別, 可呼叫其建構式 Figure() 來建立 Figure 物件, 但通常不這麼做, 而是呼叫 mtplotlib.pyplot 模組下的輔助函式 figure() 來建立, 因為它會根據傳入參數或預設值對 Figure 進行初始化.
呼叫 pyplot.figure() 時不傳入參數會建立一個空白畫布並傳回一個 Figure 物件, 例如 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.figure() # 以預設值建立空白畫布
>>> type(fig)
<class 'matplotlib.figure.Figure'> # plt.figure() 會傳回 Figure 物件
>>> plt.show()
上面程式碼執行後會跳出一個名為 Figure 1 的空白畫布視窗 :
plt.figure() 函式可傳入參數以設定畫布, 語法如下 :
plt.figure([num=None, figsize=(8, 6), dpi=80, facecolor=None, edgecolor=None, frameon=True])
常用參數說明如下 :
pyplot.figure() 參數 | 說明 |
num | 畫布編號 (整數) 或名稱 (字串), 不指定會自動產生 |
figsize | 畫布尺寸 (元組), 單位為英吋, 預設為 (寬, 高)=(6.4, 4.8) |
dpi | 解析度 (整數, 每英吋之畫素個數), 預設為 80 點 |
facecolor | 背景色 (預設白色), 可用色彩名 'yellow' 或色碼 '#ffff00' |
edgecolor | 邊框顏色 (預設白色), 可用色彩名 'yellow' 或色碼 '#ffff00' |
frameon | 是否顯示邊框, 預設 True |
constrained_layout | 是否限制版面配置 (布林值), 預設 False |
其中較常用的參數是用來設定畫布大小的 facecolor 與 figsize, facecolor 用來設定畫布背景色; 繪製多個子圖時通常需要用 figsize 將畫布尺寸設定為比預設值要大, 這時就需要指定適當的 figsize 來容納這些子圖. 另外 constrained_layout 參數用來限制版面, 主要配合 GridSpec 物件來彈性調整版面 (此時須設為 True).
參考 :
Figure 物件的預設值可呼叫 matplotlib.rc_params() 查詢, 例如 :
>>> import matplotlib as mpl
>>> mpl.rc_params()
RcParams({'_internal.classic_mode': False,
'agg.path.chunksize': 0,
'animation.avconv_args': [],
'animation.avconv_path': 'avconv',
'animation.bitrate': -1,
'animation.codec': 'h264',
'animation.convert_args': [],
'animation.convert_path': 'convert',
'animation.embed_limit': 20.0,
'animation.ffmpeg_args': [],
'animation.ffmpeg_path': 'ffmpeg',
'animation.frame_format': 'png',
'animation.html': 'none',
'animation.html_args': [],
'animation.writer': 'ffmpeg',
'axes.autolimit_mode': 'data',
'axes.axisbelow': 'line',
'axes.edgecolor': 'black',
'axes.facecolor': 'white',
'axes.formatter.limits': [-5, 6],
'axes.formatter.min_exponent': 0,
'axes.formatter.offset_threshold': 4,
'axes.formatter.use_locale': False,
'axes.formatter.use_mathtext': False,
'axes.formatter.useoffset': True,
'axes.grid': False,
'axes.grid.axis': 'both',
'axes.grid.which': 'major',
'axes.labelcolor': 'black',
'axes.labelpad': 4.0,
'axes.labelsize': 'medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
'axes.prop_cycle': cycler('color', ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']),
'axes.spines.bottom': True,
'axes.spines.left': True,
'axes.spines.right': True,
'axes.spines.top': True,
'axes.titlecolor': 'auto',
'axes.titlelocation': 'center',
'axes.titlepad': 6.0,
'axes.titlesize': 'large',
'axes.titleweight': 'normal',
'axes.unicode_minus': True,
'axes.xmargin': 0.05,
'axes.ymargin': 0.05,
'axes3d.grid': True,
'backend': 'TkAgg',
'backend_fallback': True,
'boxplot.bootstrap': None,
'boxplot.boxprops.color': 'black',
'boxplot.boxprops.linestyle': '-',
'boxplot.boxprops.linewidth': 1.0,
'boxplot.capprops.color': 'black',
'boxplot.capprops.linestyle': '-',
'boxplot.capprops.linewidth': 1.0,
'boxplot.flierprops.color': 'black',
'boxplot.flierprops.linestyle': 'none',
'boxplot.flierprops.linewidth': 1.0,
'boxplot.flierprops.marker': 'o',
'boxplot.flierprops.markeredgecolor': 'black',
'boxplot.flierprops.markeredgewidth': 1.0,
'boxplot.flierprops.markerfacecolor': 'none',
'boxplot.flierprops.markersize': 6.0,
'boxplot.meanline': False,
'boxplot.meanprops.color': 'C2',
'boxplot.meanprops.linestyle': '--',
'boxplot.meanprops.linewidth': 1.0,
'boxplot.meanprops.marker': '^',
'boxplot.meanprops.markeredgecolor': 'C2',
'boxplot.meanprops.markerfacecolor': 'C2',
'boxplot.meanprops.markersize': 6.0,
'boxplot.medianprops.color': 'C1',
'boxplot.medianprops.linestyle': '-',
'boxplot.medianprops.linewidth': 1.0,
'boxplot.notch': False,
'boxplot.patchartist': False,
'boxplot.showbox': True,
'boxplot.showcaps': True,
'boxplot.showfliers': True,
'boxplot.showmeans': False,
'boxplot.vertical': True,
'boxplot.whiskerprops.color': 'black',
'boxplot.whiskerprops.linestyle': '-',
'boxplot.whiskerprops.linewidth': 1.0,
'boxplot.whiskers': 1.5,
'contour.corner_mask': True,
'contour.negative_linestyle': 'dashed',
'datapath': 'C:\\Python37\\lib\\site-packages\\matplotlib\\mpl-data',
'date.autoformatter.day': '%Y-%m-%d',
'date.autoformatter.hour': '%m-%d %H',
'date.autoformatter.microsecond': '%M:%S.%f',
'date.autoformatter.minute': '%d %H:%M',
'date.autoformatter.month': '%Y-%m',
'date.autoformatter.second': '%H:%M:%S',
'date.autoformatter.year': '%Y',
'docstring.hardcopy': False,
'errorbar.capsize': 0.0,
'figure.autolayout': False,
'figure.constrained_layout.h_pad': 0.04167,
'figure.constrained_layout.hspace': 0.02,
'figure.constrained_layout.use': False,
'figure.constrained_layout.w_pad': 0.04167,
'figure.constrained_layout.wspace': 0.02,
'figure.dpi': 100.0,
'figure.edgecolor': 'white',
'figure.facecolor': 'white',
'figure.figsize': [6.4, 4.8],
'figure.frameon': True,
'figure.max_open_warning': 20,
'figure.subplot.bottom': 0.11,
'figure.subplot.hspace': 0.2,
'figure.subplot.left': 0.125,
'figure.subplot.right': 0.9,
'figure.subplot.top': 0.88,
'figure.subplot.wsp…
其中藍色字部分就是 Figure 物件的預設值. 也可以呼叫 pyplot.rcParams 類別的靜態方法 items() 查詢 :
>>> dir(plt.rcParams)
['_MutableMapping__marker', '__abstractmethods__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', 'clear', 'copy', 'find_all', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'validate', 'values']
因為 items() 會傳回全部配置參數與其預設值鍵值對, 其長度很長, 可以用 stratswith() 方法來過濾 以 'figure' 開頭的鍵, 例如 :
>>> items=plt.rcParams.items()
>>> for k, v in items:
if (k.startswith('figure')): # 只顯示以 'figure' 開頭的鍵
print(k, ':', v)
figure.autolayout : False
figure.constrained_layout.h_pad : 0.04167
figure.constrained_layout.hspace : 0.02
figure.constrained_layout.use : False
figure.constrained_layout.w_pad : 0.04167
figure.constrained_layout.wspace : 0.02
figure.dpi : 100.0
figure.edgecolor : white
figure.facecolor : white
figure.figsize : [6.4, 4.8]
figure.frameon : True
figure.max_open_warning : 20
figure.subplot.bottom : 0.11
figure.subplot.hspace : 0.2
figure.subplot.left : 0.125
figure.subplot.right : 0.9
figure.subplot.top : 0.88
figure.subplot.wspace : 0.2
figure.titlesize : large
figure.titleweight : normal
呼叫 plt.figure() 時傳入參數可更改這些預設之畫布屬性, 下面的範例會建立兩個 Figure 物件, 一個是不傳入參數建立預設 Figure 物件, 另一個則是傳入參數建立自訂的 Figure 物件, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig1=plt.figure() # 建立預設的 Figure 物件
>>> x=np.linspace(0, 2*np.pi, 360)
>>> y=np.sin(x)
>>> plt.plot(x, y, 'b-', lw=2) # 在預設的 Figure 物件內繪圖
[<matplotlib.lines.Line2D object at 0x000001F9F8BB65F8>]
>>> fig2=plt.figure('fig2', figsize=(8, 6), dpi=72, facecolor='#FFD700', edgecolor='blue')
>>> plt.plot(x, y, 'g-', lw=2) # 在自訂的 Figure 物件內繪圖
[<matplotlib.lines.Line2D object at 0x000001F9F8BFC2B0>]
>>> plt.grid()
>>> plt.show()
此例先呼叫 plt.figure() 建立一個預設的 Figure 物件 fig1 (畫布 1), 然後呼叫 plt.plot() 在此 Figure 物件上繪圖; 接著再次呼叫 plt.figure() 並傳入參數建立一個自訂的 Figure 物件 fig2 (畫布 2), 這時呼叫 plt.plot() 就會在自訂的畫布 fig2 上繪圖, 而不是在原先的 fig1 上. 執行後會出現兩個繪圖視窗, 分別代表畫布 1 與畫布 2 :
可見左邊的預設 Figure 物件預設的標題是自動編號的 Figure 1, 而右邊自訂的 Figure 物件之標題是傳入的 'fig2', 但可能會讓人覺得奇怪的是, 右邊 fig2 的尺寸被指定為 (8, 6) 吋, 為何會比左邊預設的預設的 (6.4, 4.8) 吋小? 原因是 fig2 的 dpi 比較小 (72), 而左邊 fig1 預設為 dpi=100, 若將 fig2 的 dpi 改為預設的 100 就會比 fig1 大了.
要注意的是, 此例呼叫兩次 plt.plot(), 第一次是在建立 fig1 物件後, 圖形會繪製在 fig1 上; 第二次是在建立 fig2 物件後, 圖形會繪製在 fig2 上, 呼叫 plt.plot() 時 Matplotlib 會依據內部狀態來控制畫在哪塊畫布上 (即 Matlab 風格的 stateful 機制). 但真正的物件導向用法是透過 Axes 物件的 plot() 繪圖, Axes 物件提供更多微調功能.
2. Figure 物件的成員 :
在進入純物件導向用法之前, 先來檢視 Figure 物件的內容有哪些公開成員, 這可以透過內建函式 dir() 與 eval() 來一窺堂奧 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.Figure()
>>> members=dir(fig)
>>> for mbr in members:
obj=eval('fig.' + mbr)
if not mbr.startswith('_'):
print(mbr, type(obj))
add_artist <class 'method'>
add_axes <class 'method'>
add_axobserver <class 'method'>
add_callback <class 'method'>
add_gridspec <class 'method'>
add_subplot <class 'method'>
align_labels <class 'method'>
align_xlabels <class 'method'>
align_ylabels <class 'method'>
matplotlib_table_5_5.py:1: MatplotlibDeprecationWarning:
The aname attribute was deprecated in Matplotlib 3.1 and will be removed in 3.3.
import matplotlib.pyplot as plt
aname <class 'str'>
artists <class 'list'>
autofmt_xdate <class 'method'>
axes <class 'list'>
bbox <class 'matplotlib.transforms.TransformedBbox'>
bbox_inches <class 'matplotlib.transforms.Bbox'>
callbacks <class 'matplotlib.cbook.CallbackRegistry'>
canvas <class 'matplotlib.backends.backend_tkagg.FigureCanvasTkAgg'>
clear <class 'method'>
clf <class 'method'>
clipbox <class 'NoneType'>
colorbar <class 'method'>
contains <class 'method'>
convert_xunits <class 'method'>
convert_yunits <class 'method'>
delaxes <class 'method'>
dpi <class 'float'>
dpi_scale_trans <class 'matplotlib.transforms.Affine2D'>
draw <class 'method'>
draw_artist <class 'method'>
eventson <class 'bool'>
execute_constrained_layout <class 'method'>
figimage <class 'method'>
figure <class 'NoneType'>
findobj <class 'method'>
format_cursor_data <class 'method'>
frameon <class 'bool'>
gca <class 'method'>
get_agg_filter <class 'method'>
get_alpha <class 'method'>
get_animated <class 'method'>
get_axes <class 'method'>
get_children <class 'method'>
get_clip_box <class 'method'>
get_clip_on <class 'method'>
get_clip_path <class 'method'>
get_constrained_layout <class 'method'>
get_constrained_layout_pads <class 'method'>
get_contains <class 'method'>
get_cursor_data <class 'method'>
get_default_bbox_extra_artists <class 'method'>
get_dpi <class 'method'>
get_edgecolor <class 'method'>
get_facecolor <class 'method'>
get_figheight <class 'method'>
get_figure <class 'method'>
get_figwidth <class 'method'>
get_frameon <class 'method'>
get_gid <class 'method'>
get_in_layout <class 'method'>
get_label <class 'method'>
get_path_effects <class 'method'>
get_picker <class 'method'>
get_rasterized <class 'method'>
get_size_inches <class 'method'>
get_sketch_params <class 'method'>
get_snap <class 'method'>
get_tight_layout <class 'method'>
get_tightbbox <class 'method'>
get_transform <class 'method'>
get_transformed_clip_path_and_affine <class 'method'>
get_url <class 'method'>
get_visible <class 'method'>
get_window_extent <class 'method'>
get_zorder <class 'method'>
ginput <class 'method'>
have_units <class 'method'>
images <class 'list'>
init_layoutbox <class 'method'>
is_transform_set <class 'method'>
legend <class 'method'>
legends <class 'list'>
lines <class 'list'>
mouseover <class 'bool'>
number <class 'int'>
patch <class 'matplotlib.patches.Rectangle'>
patches <class 'list'>
pchanged <class 'method'>
pick <class 'method'>
pickable <class 'method'>
properties <class 'method'>
remove <class 'method'>
remove_callback <class 'method'>
savefig <class 'method'>
sca <class 'method'>
set <class 'method'>
set_agg_filter <class 'method'>
set_alpha <class 'method'>
set_animated <class 'method'>
set_canvas <class 'method'>
set_clip_box <class 'method'>
set_clip_on <class 'method'>
set_clip_path <class 'method'>
set_constrained_layout <class 'method'>
set_constrained_layout_pads <class 'method'>
set_contains <class 'method'>
set_dpi <class 'method'>
set_edgecolor <class 'method'>
set_facecolor <class 'method'>
set_figheight <class 'method'>
set_figure <class 'method'>
set_figwidth <class 'method'>
set_frameon <class 'method'>
set_gid <class 'method'>
set_in_layout <class 'method'>
set_label <class 'method'>
set_path_effects <class 'method'>
set_picker <class 'method'>
set_rasterized <class 'method'>
set_size_inches <class 'method'>
set_sketch_params <class 'method'>
set_snap <class 'method'>
set_tight_layout <class 'method'>
set_transform <class 'method'>
set_url <class 'method'>
set_visible <class 'method'>
set_zorder <class 'method'>
show <class 'method'>
stale <class 'bool'>
stale_callback <class 'function'>
sticky_edges <class 'matplotlib.artist._XYPair'>
subplotpars <class 'matplotlib.figure.SubplotParams'>
subplots <class 'method'>
subplots_adjust <class 'method'>
suppressComposite <class 'NoneType'>
suptitle <class 'method'>
text <class 'method'>
texts <class 'list'>
tight_layout <class 'method'>
transFigure <class 'matplotlib.transforms.BboxTransformTo'>
update <class 'method'>
update_from <class 'method'>
waitforbuttonpress <class 'method'>
zorder <class 'int'>
可見 Figure 物件提供了非常豐富的屬性與方法, 常用屬性如下表 :
Figure 物件常用屬性 | 說明 |
axes | 儲存畫布中的 Axes 物件之串列 |
lines | 儲存畫布中的 Line2D 物件之串列 |
images | 儲存畫布中的 FigureImage 物件 (圖片) 之串列 |
legends | 儲存畫布中的 Legend 物件 (圖例) 之串列 |
texts | 儲存畫布中的 Text 物件 (註解文字) 之串列 |
patch | 儲存畫布中的 Rectangle 物件 (背景) |
patches | 儲存畫布中的 Patch 物件之串列 |
常用方法如下表 :
Figure 物件常用方法 | 說明 |
add_axes([x, y, w, h]) | 新增一個繪圖區, 傳回一個 Axes 物件 |
add_subplot(nrow, ncol, index) | 新增 nrow*ncol 網格中索引為 index 的 SubplotAxes 物件 |
delaxes([ax]) | 刪除指定之 Axes 物件 ax, 若未傳入 ax 則刪除目前之 Axes |
set_tight_layout(True) | 設定為緊湊版面與否 (預設 False), 緊湊版面可避免標題被裁切 |
tight_layout() | 設定為緊湊版面, 功能與 set_tight_layout(True) 相同 |
set_facecolor(color) | 設定畫布背景色為 color, 例如 'red', '#ff0000', (1, 0, 0) |
set_size_inches(width, height) | 設定畫布尺寸為寬 width, 高 height (均為英吋) |
gca() | 傳回目前的 Axes 物件 |
subplots_adjust(wspace, hspace) | 設定子圖間的水平 (wspace) 與垂直 (hspace), 相對於子圖寬與高 |
add_gridspec(nrows, ncols) | 傳回一個 GridSpec 物件來做彈性版面配置 |
其中最常用的方法是 add_axes() 與 add_subplots(), 它們都會在 Figure 畫布中建立一個擁有獨立坐標系的繪圖區 (或稱為子圖), 這兩個方法也是 Matplotlib 物件導向繪圖的核心, 利用 Figure 物件與這兩個方法傳回的 Axes 物件繪圖才算是真正物件導向模式用法.
3. 建立 Axes 物件 :
呼叫 Figure 物件的 add_axes() 方法會傳回一個 Axes 物件, 一個 Figure 物件內可以建立不限個數的 Axes 物件, 亦即一個畫布上可以擁有多個擁有各自獨立坐標系的繪圖區, Figure 物件有一個 axes 方法用來管理畫布內的 Axes 物件, 每建立一個 Axes 物件, 其參考會被放進 axes 屬性字串中, Figure 物件會記錄目前繪圖區狀態 (其中只有一個是 active). 使用 Matlab 模式繪圖時, 呼叫 plt.plot() 就是將圖形繪製到此作用中的 Axes 物件上, 呼叫 Figure 物件的 gca() 方法可取得目前作用中的 Axes 物件參考.
Figure 物件的 add_axes() 方法的語法如下 :
ax=fig.add_axes(rect [, projection=None, polar=False])
參數說明如下 :
- rect : 必要參數, 用來定位繪圖區左下角座標位置與尺寸的串列 [left, bottom, width, height] 或元組 (left, bottom, width, height), 其值均為 0~1 的浮點數 (相對於 Figure 的尺寸), 其中 left 與 bottom 為繪圖區左下角座標位置 (Figure 畫布的原點是在左下角), 而 width 與 height 則 表示繪圖區的寬度與高度. 例如 [0.1, 0.1, 0.8, 0.8] 表示此繪圖區左下角座標為畫布左下角向上向右的 10% 位置, 而其寬度與高度是畫布的 80%.
- projection : 備選參數 (字串), 用來指定坐標系投影模式.
- polar : 是否使用極座標, 預設 False 表示使用直角坐標系.
必要參數 rect 名稱可指定也可不指定 (即也是關鍵字參數), 傳入 [0.1, 0.1,0.8, 0.8] 或 rect=[0.1, 0.1,0.8, 0.8] 均可. 注意, 呼叫 add_axes() 不傳入參數雖然不會出現錯誤, 但不會建立 Axes 物件, 傳回 None 而非 Axes 物件, 且 Figure 物件的 axes 屬性串列為空, 故呼叫 ax.plot() 會出現 AttributeError 錯誤 (無 plot 方法), 總之, 呼叫 add_axes() 時一定要傳 rect 串列進去.
可用的坐標系投影模式可透過呼叫 Matplotlib 的 projections 模組下的 get_projection_names() 函式來查詢 :
>>> from matplotlib import projections
>>> projections.get_projection_names()
['3d', 'aitoff', 'hammer', 'lambert', 'mollweide', 'polar', 'rectilinear']
其中 'aitoff', 'hammer', 'lambert', 'mollweide' 屬於地圖投影模式; 'polar' 為極座標投影模式; 而 'rectilinear' 則為直線投影模式 (projection 預設值 None 表示使用此模式). 例如在繪製 3D 立體圖時就會使用 '3d' 模式, 使用極座標繪圖時則傳入 'polar', 且 polar 參數要設為 True.
參考 :
呼叫 fig.add_axes() 建立一個 Axes 物件後就可呼叫其 plot() 方法並傳入資料 (串列或 Numpy 陣列) 即可在 Axes 物件上繪圖, 語法與 plt.plot() 相同 :
其中 x1, y1, x2, y2, .... 是成對的 X, Y 軸資料, 如果只傳入一對表示只畫一個圖形, 兩對就是兩個圖形, .... X 軸資料可有可無的 , 未傳入時預設是 0, 1, 2, ....
參考 :
如果是在 Colab 或 Jupyter Notbook 下有使用神奇指令 %matplotlib inline, 則呼叫 ax.plot() 後就會在儲存格中繪製圖形; 但如果是在命令列環境下, 則必須再呼叫 plt.show() 才會顯示圖形, 例如 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.figure()
>>> ax=fig.add_axes([0, 0, 1, 1]) # 繪圖區占滿整個畫布
>>> type(ax)
<class 'matplotlib.axes._axes.Axes'> # add_axes() 傳回 Axes 物件
>>> fig.axes # Axes 物件串列
[<matplotlib.axes._axes.Axes object at 0x000001A077B4A048>]
>>> fig.gca()
<matplotlib.axes._axes.Axes object at 0x000001A077B4A048>
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1]) # 傳入串列繪製折線圖
[<matplotlib.lines.Line2D object at 0x000001A077B37940>]
>>> plt.show()
此例用 rect=[0, 0, 1, 1] 滿版配置傳入 add_axes() 建立繪圖區, 產生的 Axes 物件參考也會被記錄在 Figure 物件的 axes 屬性中, 可見 axes 屬性串列中目前只有一個 Axes 物件, 呼叫其 gca() 方法也顯示該物件就是作用中的 Axes 物件. 結果如下 :
可見滿版配置只會顯示圖形 (即 Line2D 物件), 坐標軸 (即 Axis 物件) 消失了.
完整原始碼參考 :
測試 : 呼叫 fig.add_axes() 建立 Axes 物件(1) [看原始碼]
若要完整顯示座標軸, 通常會使用較小的 rect 來放置 Axes 繪圖區, 例如 [0.1, 0.1, 0.8, 0.8] 以保留上下左右 10% 的餘裕空間來顯示座標軸, 例如 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.figure()
>>> ax=fig.add_axes([0.1, 0.1, 0.8, 0.8]) # 保留 10% 餘裕顯示座標軸與標題
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1]) # 傳入串列繪製折線圖
[<matplotlib.lines.Line2D object at 0x000001A074D59198>]
>>> ax.set_xlabel('X') # 設定繪圖區的 X 軸標籤
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y') # 設定繪圖區的 Y 軸標籤
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo') # 設定繪圖區的標題
Text(0.5, 1.0, 'Demo')
>>> plt.show()
此例呼叫了 Axes 物件的 set_xlabel(), set_ylabel(), 以及 set_title() 方法分別設定子圖的 X, Y 軸標籤與圖形標題, 結果如下 :
可見只要留足夠的餘裕就能顯示整個 Axes 物件的內容, 如果只繪製一個子圖, 則 rec=[0.1, 0.1, 0.8, 0.8] 的繪圖區尺寸就足夠容納標題與座標軸的顯示了.
透過版面安排可以在一個畫布上製作子母疊圖效果, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig=plt.figure(figsize=[10, 8]) # 建立 10*8 吋之畫布
>>> x=np.linspace(0, 2*np.pi, 360) # X 軸陣列 0~2pi
>>> ax1=fig.add_axes([0.1, 0.1, 0.8, 0.8]) # 建立 Axes 物件 ax1
>>> ax1.plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001A001939A20>]
>>> ax2=fig.add_axes([0.55, 0.55, 0.3, 0.3]) # 建立 Axes 物件 ax2
>>> ax2.plot(x, np.cos(x))
[<matplotlib.lines.Line2D object at 0x000001A00100A0F0>]
>>> ax1.set_title('Function of Sin(x)')
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax2.set_title('Function of Cos(x)')
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()
此例先建立指定為 10*8 吋的畫布物件 fig1, 然後呼叫 fig1.add_axes() 在畫布上建立兩個 Axes 物件 ax1 與 ax2, 前者原點座標 [0.1, 0.1], 長寬大小均為 0.8; 後者原點座標在 [0.55,0.55], 長寬為 0.3, 結果如下 :
由於 ax1 尺寸較大, ax2 較小, 所以疊加起來像是子母圖形.
其實只要版面設計得宜 (不要有覆蓋現象), fig.add_axes() 也可以繪製多子圖效果, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()
>>> ax1=fig.add_axes([0.1, 0.6, 0.8, 0.3]) # 上方的 Axes 物件
>>> ax2=fig.add_axes([0.1, 0.1, 0.8, 0.3]) # 下方的 Axes 物件
>>> ax1.plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001A001445390>]
>>> ax2.plot(x, np.cos(x))
[<matplotlib.lines.Line2D object at 0x000001A001445518>]
>>> ax1.set_title('Function of Sin(x)')
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax2.set_title('Function of Cos(x)')
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()
此例以畫布中央水平線 y=0.5 為界將版面分成上下兩塊, 上面用來放 ax1 物件; 下面用來放 ax2 物件, 分別在這兩個 Axes 物件上繪製 sin(x) 與 cos(x) 圖形, 結果如下 :
如果覺得要自行分割版面很麻煩, 那就改用 Figure 物件的 add_subplot() 方法.
呼叫 Figure 物件的 add_subplots() 方法並傳入子圖網格配置參數會傳回一個 SubplotAxes 物件, 此乃 Axes 的衍生物件, 因此其參考也會被記錄在 Figure 物件的 axes 屬性中, 與 Axes 物件一樣納入狀態管理, 且同樣有 plot() 方法可在所建立的子圖上繪圖, 其常用語法如下 :
ax=fig.add_subplot([nrows, ncols, index, projection=None, polar=False])
參數說明如下 :
- nrows 與 ncols 表示子圖網格的列數與欄數, 而 index 是則是子圖索引 (1 起始)
- projection : 坐標系投影模式 (字串), 見上面 add_axes() 說明
- ploar : 是否使用極座標系繪圖
此外還有另一種語法 :
ax=fig.add_subplot([pos, projection=None, polar=False])
參數 pos 其實就是將 nrows, ncols, 與 index 這三個參數黏合成的 3 位數的數值, 例如 add_subplot(3, 2, 2) 可以寫成 add_sunplot(322), 但只能在 nrows 與 ncols 都小於 10 時才能這樣用. 參考 :
另外, add_subplot() 還可以傳入一個 GridSpec 物件來進行彈性或複雜的版面配置, 參考 :
注意, add_subplot() 的參數都是可有可無的, 不傳入參數的話, 預設會建立一個 111 的子圖 (1 列 1 行的第 1 個子圖), 例如 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.figure()
>>> ax=fig.add_subplot() # 未傳入參數, 預設 rect=111 (只有一個子圖)
>>> type(ax)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1]) # 傳入串列繪製折線圖
[<matplotlib.lines.Line2D object at 0x000001A077871400>]
>>> fig.axes # AxesSubplot 物件的參考也會放進 axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A0777B7F28>]
>>> ax.set_xlabel('X') # 設定子圖的 X 軸標籤
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y') # 設定子圖的 Y 軸標籤
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo') # 設定子圖的標題
Text(0.5, 1.0, 'Demo')
>>> plt.show()
此例在呼叫 fig.add_subplot() 時未傳入參數, 預設是建立一個 1*1 的子圖網格, 亦即畫布中只有一個子圖, 結果與上面用 fig.add_axes() 所繪製的圖一樣 :
fig.add_subplot() 主要是用在繪製多子圖情況, 它會自動將各子圖放在網格中, 如果使用 fig.add_axes() 來繪製多子圖則需要為各子圖在畫布中進行人工排版. 多子圖範例如下 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()
>>> ax1=fig.add_subplot(211) # 在 2*1 網格的索引 1 位置新增一個子圖 ax1
>>> type(ax1) # 傳回之物件類型為 AxesSubplot 物件 (Axes 之子類別物件)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> fig.axes # ax1 物件參考被記錄在 fig.axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E57C88>]
>>> ax1.plot(x, np.sin(x)) # 在 ax1 子圖上繪製 sin 正弦函數
[<matplotlib.lines.Line2D object at 0x000001A078E9B080>]
>>> ax1.set_title('Sin function')
Text(0.5, 1.0, 'Sin function')
>>> ax2=fig.add_subplot(212) # 在 2*1 網格的索引 2 位置新增一個子圖 ax2
>>> ax2.plot(x, np.cos(x)) # 在 ax2 子圖上繪製 cos 餘弦函數
[<matplotlib.lines.Line2D object at 0x000001A078EC6668>]
>>> ax2.set_title('Cos function')
Text(0.5, 1.0, 'Cos function')
>>> type(ax2) # 傳回之物件類型為 AxesSubplot 物件 (Axes 之子類別物件)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> fig.axes # ax1, ax2 物件參考都被記錄在 fig.axes 屬性串列中
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E57C88>, <matplotlib.axes._subplots.AxesSubplot object at 0x000001A078E88FD0>]
>>> plt.show()
此例先用 Numpy 的 linsspace() 函式在 [0, 10] 區間建立 100 個等間隔點之陣列做為 X 軸資料點, 然後呼叫兩次 fig.add_subplot() 在畫布的 2*1 網格版面中依序建立 ax1 與 ax2 子圖, 然後在子圖內分別繪製 sin(x) 與 cos(x) 函式圖形, 結果如下 :
上例中, 下方子圖的標題會與上方子圖的 X 軸刻度標籤有部分重疊現象, 這只要在呼叫 plt.show() 之前先呼叫 plt.tight_layout() 令版面緊密即可解決 :
>>> plt.tight_layout()
>>> plt.show()
結果如下 :
另一個方式是使用 Figure 物件的 subplots_adjust(wspace, hspace) 方法, 此方法其實還有用來設定子圖位置的 left, right, top, bottom 等參數, 但較常用的是用來設定子圖間距的 hspace (垂直) 與 wspace (水平), 其值為 0~1, 是相對於畫布尺寸, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()
>>> fig.subplots_adjust(0.2, 0.2) # 設定子圖間距為畫布尺寸的 0.2
>>> ax1=fig.add_subplot(211) # 在 2*1 網格的索引 1 位置新增一個子圖 ax1
>>> ax1.plot(x, np.sin(x)) # 在 ax1 子圖上繪製 sin 正弦函數
[<matplotlib.lines.Line2D object at 0x000001A078E9B080>]
>>> ax1.set_title('Sin function')
Text(0.5, 1.0, 'Sin function')
>>> ax2=fig.add_subplot(212) # 在 2*1 網格的索引 2 位置新增一個子圖 ax2
>>> ax2.plot(x, np.cos(x)) # 在 ax2 子圖上繪製 cos 餘弦函數
[<matplotlib.lines.Line2D object at 0x000001A078EC6668>]
>>> ax2.set_title('Cos function')
>>> plt.show()
效果與上面使用 plt.tight_layout() 一樣 (但更有彈性).
如果網格中部分儲存格沒有建立 AxesSubplot 物件, 則該儲存格會空白; 有建立 AxesSubplot 物件但卻沒呼叫 plot() 函式則會僅顯示座標軸沒有圖形, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 2*np.pi) # X 軸陣列 0~2pi
>>> fig=plt.figure()
>>> ax1=fig.add_subplot(231)
>>> ax1.plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001301FD885C0>]
>>> ax1.set_title('Sin(x)')
Text(0.5, 1.0, 'Sin(x)')
>>> ax2=fig.add_subplot(235)
>>> ax2.plot(x, np.cos(x))
[<matplotlib.lines.Line2D object at 0x000001301EA096D8>]
>>> ax2.set_title('Cos(x)')
Text(0.5, 1.0, 'Cos(x)')
>>> ax3=fig.add_subplot(236)
>>> ax3.plot(x, np.tan(x))
[<matplotlib.lines.Line2D object at 0x000001301FD88550>]
>>> ax3.set_title('Tan(x)')
Text(0.5, 1.0, 'Tan(x)')
>>> ax4=fig.add_subplot(233)
>>> plt.show()
此例有 2*3 共六個網格, 但只在 231, 233, 235, 236 四個儲存格有建立子圖物件, 其餘儲存格都是空白, 其中 233 只建立子圖物件卻沒呼叫 plot() 繪圖, 故只有顯示座標軸而無圖形, 結果如下 :
4. Axes 與 AxesSubplot 物件的成員 :
以上使用 Axes 物件與其衍生物件 AxesSubplot 的 plot() 方法繪圖, 並呼叫 set_xlabel() 與 set_ylabel() 來設定 X, Y 軸標籤, 事實上 Axes 與 AxesSubplot 物件提供了非常豐富的繪圖方法可對圖形進行設定或微調.
以下使用一個自訂模組 members 的 list_members() 函式來檢視模組或套件中的公開成員 (即屬性與方法), 參考 :
# Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法
# Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法
>>> import matplotlib.pyplot as plt
>>> import members
>>> fig=plt.figure()
>>> ax=fig.add_axes([0.1, 0.1,0.8, 0.8])
>>> members.list_members(mpf)
acorr <class 'method'>
add_artist <class 'method'>
add_callback <class 'method'>
add_child_axes <class 'method'>
add_collection <class 'method'>
add_container <class 'method'>
add_image <class 'method'>
add_line <class 'method'>
add_patch <class 'method'>
add_table <class 'method'>
aname <class 'str'>
angle_spectrum <class 'method'>
annotate <class 'method'>
apply_aspect <class 'method'>
arrow <class 'method'>
artists <class 'list'>
autoscale <class 'method'>
autoscale_view <class 'method'>
axes <class 'matplotlib.axes._axes.Axes'>
axhline <class 'method'>
axhspan <class 'method'>
axis <class 'method'>
.... (略) ....
twinx <class 'method'>
twiny <class 'method'>
update <class 'method'>
update_datalim <class 'method'>
update_datalim_bounds <class 'method'>
update_from <class 'method'>
use_sticky_edges <class 'bool'>
viewLim <class 'matplotlib.transforms.Bbox'>
violin <class 'method'>
violinplot <class 'method'>
vlines <class 'method'>
xaxis <class 'matplotlib.axis.XAxis'>
xaxis_date <class 'method'>
xaxis_inverted <class 'method'>
xcorr <class 'method'>
yaxis <class 'matplotlib.axis.YAxis'>
yaxis_date <class 'method'>
yaxis_inverted <class 'method'>
zorder <class 'int'>
由於 Axes 物件的公開成員多達 303 個, 所以上面僅列出頭尾, 完整的成員列表參考 :
而呼叫 fig.add_subplot(), plt.axes(), 以及 plt.subplots() 所傳回 AxesSubplot 物件因為是 Axes 物件的衍生物件, 所以除繼承 Axes 外還添加新的成員, 總計有 319 個, 參考 :
其中較常用的方法如下表 :
Axes 物件常用方法 | 說明 |
ax.set_title(str) | 設定圖形標題 str (字串) |
ax.set_xlabel(str) | 設定 X 軸標籤 str (字串) |
ax.set_ylabel(str) | 設定 Y 軸標籤 str (字串) |
ax.set_xlim([xmin, xmax]) | 設定 X 軸範圍為 [xmin, xmax] |
ax.set_ylim([ymin, ymax]) | 設定 Y 軸範圍為 [ymin, ymax] |
ax.set_xticks(ticks, labels) | 設定 X 軸刻度, ticks=刻度值串列, labels=刻度標籤串列 |
ax.set_yticks(ticks, labels) | 設定 Y 軸刻度, ticks=刻度值串列, labels=刻度標籤串列 |
ax.set(kargs) | 綜合設定以上之全部繪圖區屬性 |
ax.twinx() | 共用 X 軸 |
ax.twiny() | 共用 Y 軸 |
ax.text(x, y, str, fontsize, ha) | 在子圖座標 (x, y) 繪製字串 str, (x, y) 值 0~1, ha=center, left, right |
ax.legend(loc) | 設定圖例位置, loc=0 (最佳位置) ~10 |
ax.grid() | 顯示網格格線 |
ax.table(cellText, colLabels) | 繪製表格 |
ax.plot(x1, y1, x2, y2,...) | 繪製 (x1, y1), (x2, y2), ... 等多個函數圖形 |
可見與 Matlab 風格寫法的差別主要是很多方法的名稱前面多了 "set_", 例如 plt.title() 要改用 ax.set_title() 等, 但 plot(), legend(), grid() 以及統計圖函式如 bar(), scatter() 等函式除外 (前面不須加 'set_').
pyploy 函式與 Figure/Axes 物件常用方法的對照如下表 :
pyplot 函式 | Figure/Axes 物件方法 |
plt.title() | ax.set_title() |
plt.xlabel() | ax.set_xlabel() |
plt.ylabel() | ax.set_ylabel() |
plt.xlim() | ax.set_xlim() |
plt.ylim() | ax.set_ylim() |
plt.xticks() | ax.set_xticks() |
plt.yticks() | ax.set_yticks() |
plt.xticklabels() | ax.set_xticklabels() |
plt.yticklabels() | ax.set_yticklabels() |
plt.text() | ax.set_text() |
plt.grid() | ax.grid() |
無 | ax.xasix.set_visible(False) |
plt.plot() | ax.plot() |
plt.figure(figuresize) | fig.set_size_inches() |
plt.tight_layout() | fig.tight_layout() |
plt.legend() | ax.legend() |
plt.scatter() | ax.scatter() |
plt.bar() | ax.bar() |
plt.barh() | ax.barh() |
下面以物件導向方式呼叫 Axes 物件方法繪製四個函式的線圖, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig=plt.figure()
>>> ax=fig.add_subplot() # 建立 1*1 子圖
>>> x=np.arange(3)
>>> ax.plot(x, -x**2, label='-x**2')
[<matplotlib.lines.Line2D object at 0x000002360A43C908>]
>>> ax.plot(x, -x**3, label='-x**3')
[<matplotlib.lines.Line2D object at 0x000002360A43CC18>]
>>> ax.plot(x, -2*x, label='-2*x')
[<matplotlib.lines.Line2D object at 0x000002360A481048>]
>>> ax.plot(x, -2**x, label='-2**x')
[<matplotlib.lines.Line2D object at 0x000002360A481358>]
>>> ax.set_xlabel('x')
Text(0.5, 0, 'x')
>>> ax.set_ylabel('y = f(x)')
Text(0, 0.5, 'y = f(x)')
>>> ax.set_title('Plotting of Function')
Text(0.5, 1.0, 'Plotting of Function')
>>> ax.legend() # 依據線圖的 label 繪製圖例
<matplotlib.legend.Legend object at 0x000002360A43C2E8>
>>> ax.grid(True) # 顯示格線
>>> ax.text(0.25, -5, "Object Oriented Plotting") # 繪製文字
Text(0.25, -5, 'Object Oriented Plotting')
>>> plt.show()
結果如下 :
上面程式中的所有 'set_' 開頭的方法都可以用一個 set() 方法取代, 只要把欲設定的對象當作參數傳給 set() 即可, 這樣只要一個指令即可, 例如上面三個 'set_' 方法 :
ax.set_xlabel('x')
ax.set_ylabel('y = f(x)')
ax.set_title('Plotting of Function')
可以改成用 set() 方法 :
ax.set(xlabel='x', ylabel='y = f(x)', title='Plotting of Function')
如同基本篇中所述, 呼叫 plot() 函式時傳入的 label 參數會被 ax.legend() 作為該線圖的圖例文字, 但如果在一次呼叫 plot() 時同時繪製多圖就無法這麼用了, 這時必須將圖例文字依照 plot() 中各線圖之相同順序組成一個串列傳給 ax.legend(), 例如 :
ax.plot(x, -x**2, x, -x**3, x, -2*x, x, -2**x) # 一次呼叫 plot() 繪製多線圖
ax.legend(['-x**2', '-x**3', '-2*x', '-2**x']) # 順序須與 plot() 的線圖對應
下面是關於座標軸刻度設定的範例 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig=plt.figure()
>>> ax=fig.add_subplot() # 建立 1*1 子圖
>>> x=np.linspace(0, 2*np.pi)
>>> ax.plot(x, np.sin(x), x, np.cos(x)) # 一次繪製兩個線圖
[<matplotlib.lines.Line2D object at 0x0000023C4E47E8D0>, <matplotlib.lines.Line2D object at 0x0000023C4E47EA20>]
>>> ax.set(xlabel='x',
ylabel='y = f(x)',
title='sin(x) and cos(x)',
xticks=[0, np.pi*0.5, np.pi, np.pi*1.5, np.pi*2],
xticklabels=['0°', '90°', '180°', '270°', '360°']) # 一次設定全部座標軸參數
[Text(0, 0.5, 'y = f(x)'), [<matplotlib.axis.XTick object at 0x0000023C4E45C518>, <matplotlib.axis.XTick object at 0x0000023C4E45C4E0>, <matplotlib.axis.XTick object at 0x0000023C4E45C128>, <matplotlib.axis.XTick object at 0x0000023C4E487C50>, <matplotlib.axis.XTick object at 0x0000023C4E492160>], [Text(0, 0, '0°'), Text(0, 0, '90°'), Text(0, 0, '180°'), Text(0, 0, '270°'), Text(0, 0, '360°')], Text(0.5, 0, 'x'), Text(0.5, 1.0, 'sin(x) and cos(x)')]
>>> ax.legend(['y=sin(x)', 'y=cos(x)'])
<matplotlib.legend.Legend object at 0x0000023C3B014B38>
>>> ax.grid(True)
>>> plt.show()
此例在呼叫 ax.set() 時傳入 xticks 與 xticklabels 設定 X 軸刻度位置與標籤, 結果如下 :
如果將上面 ax.set() 中的 xticks 與 xticklabels 拿掉, 則 X 軸刻度會變成純數值 :
三. 用 pyplot 模組的函式進行物件導向繪圖 :
Matplotlib 物件導向繪圖除了使用 Figure 物件外, 也可以利用 pyplot 模組提供的 axes() 與 subplots() 函式來建立 Figure 與 AxesSubplot 物件.
1. 呼叫 plt.axes() 方法建立 AxesSubplot 物件 :
呼叫 plt.axes() 會傳回一個 AxesSubplot 物件, 常用語法如下 :
ax=plt.axes(rect [, projection=None, polar=False])
參數與上述 fig.add_axes() 用法相同, rect 是四個浮點數元素的 tuple 或 list, 用來定位繪圖區左下角在畫布中的座標位置與尺寸 [left, bottom, width, height], 其值均為 0~1. 注意, 第一參數 rect 不是關鍵字參數, 如果傳入例如 rect=[0.1, 0.1, 0.8, 0.8] 會出現 TypeError 錯誤. 參考 :
例如 :
>>> import matplotlib.pyplot as plt
>>> fig=plt.figure()
>>> ax=plt.axes() # 傳回 AxesSubplot 物件
>>> type(ax)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> fig.axes
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A078F147F0>]
>>> ax.get_position() # 傳回 Axes 物件在畫布中的座標位置
Bbox([[0.125, 0.10999999999999999], [0.9, 0.88]])
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])
[<matplotlib.lines.Line2D object at 0x000001A0790A8CF8>]
>>> ax.set_xlabel('X')
Text(0.5, 0, 'X')
>>> ax.set_ylabel('Y')
Text(0, 0.5, 'Y')
>>> ax.set_title('Demo of plt.axes()')
Text(0.5, 1.0, 'Demo of plt.axes()')
>>> plt.show()
此例在呼叫 plt.axes() 時未傳入參數, 所建立的繪圖區 (即 AxesSubplot 物件) 預設會放在畫布座標 [0.125, 0.11] (左下角) 與 [0.9, 0.88] (右上角), 可利用 ax.get_position() 得知預設之繪圖區位置與尺寸, 結果如下 :
如果傳入 rect 參數 [0.1, 0.1, 0.8, 0.8], 則呼叫 ax.position() 會傳回 Bbox([[0.1, 0.1], [0.9, 0.9]]).
完整程式碼參考 :
測試 : 呼叫 plt.axes() 方法繪製單一子圖 [看原始碼]
如果要用 plt.axes() 繪製多子圖必須自行在畫布上用 rect 參數排版, 例如 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig=plt.figure()
>>> ax1=plt.axes([0.1, 0.6, 0.8, 0.3]) # sin(x) 圖形放在上半部
>>> ax1.get_position() # 上方繪圖區在畫布 fig 上的座標
Bbox([[0.1, 0.6], [0.9, 0.8999999999999999]])
>>> ax2=plt.axes([0.1, 0.1, 0.8, 0.3]) # cos(x) 圖形放在下半部
>>> ax2.get_position()
Bbox([[0.1, 0.1], [0.9, 0.4]]) # 下方繪圖區在畫布 fig 上的座標
>>> ax1.plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001A079B95BA8>]
>>> ax2.plot(x, np.cos(x))
[<matplotlib.lines.Line2D object at 0x000001A079B95F60>]
>>> ax1.set_title('Sin(x)')
Text(0.5, 1.0, 'Sin(x)')
>>> ax2.set_title('Cos(x)')
Text(0.5, 1.0, 'Cos(x)')
>>> plt.show()
此例以畫布水平中央 y=0.5 為界分成上下兩個相等版面, 上半部的繪圖區 ax1 繪製 sin(x); 下半部的繪圖區 ax2 繪製 cos(x), 結果如下 :
可見 plt.axes() 比較適合用在繪製一個子圖的場合, 繪製多個子圖用 plt.subplots() 較方便.
2. 呼叫 plt.subplots() 方法建立 Figure 與 AxesSubplot 物件 :
以物件導向方式繪製多個子圖時可呼叫 plt.subplots() 函式, 因為其傳回值是一個包含 Figure 物件與 Axes 物件陣列的 tuple, 所以不需要用 plt.figure() 自行建立 Figure 物件, 省了一個指令, 其常用語法如下 :
fig, ax=plt.subplots([nrows=1, ncols=1, sharex=False, sharey=False])
參數說明如下 :
- nrows 與 ncols 表示子圖網格的列數與欄數, 預設都是 1 (單子圖)
- sharex 為布林值, 表示是否共用 X 軸 (預設 False 不共用)
- sharey 為布林值, 表示是否共用 Y 軸 (預設 False 不共用)
傳回值是一個 tuple, 第一個元素為 Figure 物件; 第二個元素為子圖 AxesSubplot 物件 (單子圖時)或 AxesSubplot 物件組成之 ndarray 陣列 (多子圖時).
參考 :
呼叫 plt.subplots() 時不傳入參數預設就是建立 1*1 網格的單子圖, 例如 :
>>> import matplotlib.pyplot as plt
>>> fig, ax=plt.subplots() # 沒有傳入參數就是建立單一子圖
>>> type(fig)
<class 'matplotlib.figure.Figure'>
>>> type(ax)
<class 'matplotlib.axes._subplots.AxesSubplot'>
>>> ax.plot([7, 1, 2, 3, 8, 4, 5, 3, 6, 1])
[<matplotlib.lines.Line2D object at 0x000001A07B2DBA20>]
>>> ax.set_title('Demo of plt.subplots()')
Text(0.5, 1.0, 'Demo of plt.subplots()')
>>> plt.show()
此例於呼叫 plt.subplots() 未傳入參數, 故會建立單一子圖, 結果如下 :
這與 plt.subplots(1, 1) 效果是一樣的.
完整程式碼參考 :
測試 : 呼叫 plt.subplots() 方法繪製單一子圖 [看原始碼]
呼叫 plt.subplots() 並傳入網格參數繪製多子圖的範例如下 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig, ax=plt.subplots(2, 1) # 建立 2*1 網格排版繪製兩個子圖
>>> type(ax) # 傳回 AxesSubplot 物件組成之 ndarray 陣列
<class 'numpy.ndarray'>
>>> ax
array([<matplotlib.axes._subplots.AxesSubplot object at 0x000001A07F286198>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000001A07F3FBB00>],
dtype=object)
>>> ax[0].plot(x, np.sin(x)) # ax[0] 物件上繪製 sin(x)
[<matplotlib.lines.Line2D object at 0x000001A07FC3E470>]
>>> ax[1].plot(x, np.cos(x)) # ax[1] 物件上繪製 cos(x)
[<matplotlib.lines.Line2D object at 0x000001A07FC3E5C0>]
>>> ax[0].set_title('Sin(x)')
Text(0.5, 1.0, 'Function of Sin(x)')
>>> ax[1].set_title('Cos(x)')
Text(0.5, 1.0, 'Function of Cos(x)')
>>> plt.show()
此例呼叫 plt.subplots(2, 1) 傳回的 ax 是 AxeSubplot 物件組成之 ndarray 陣列, 可用 ax[0] 與 ax[1] 來存取這兩個 AxesSubplot 物件, 然後分別呼叫 plot() 方法繪製 sin(x) 與 cos(x), 結果如下 :
>>> plt.tight_layout() # 使版面緊緻
>>> plt.show()
結果如下 :
這樣下方子圖的標題就不會與上方子圖的 X 軸刻度重疊了.
同樣地也可以利用 fig.subplts_adjust(wspace=0.2, hspace=0.2) 設定子圖間距來避免重疊, 參考上面 fig.add_subplot() 之範例.
下面是建立垂直多子圖時傳入 sharex=True 共用 X 軸的範例 :
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x=np.linspace(0, 10, 100) # 在 X 軸上 [0, 10] 建立 100 個等間隔之點陣列
>>> fig, ax=plt.subplots(2, 1, sharex=True) # 兩個子圖共用 X 軸
>>> ax[0].plot(x, np.sin(x))
[<matplotlib.lines.Line2D object at 0x000001A001D659B0>]
>>> ax[1].plot(x, np.cos(x))
[<matplotlib.lines.Line2D object at 0x000001A001D65DA0>]
>>> ax[0].set_title('Sin(x)')
Text(0.5, 1.0, 'Sin(x)')
>>> ax[1].set_title('Cos(x)')
Text(0.5, 1.0, 'Cos(x)')
>>> plt.show()
此例在呼叫 plt.subplots() 時傳入 sharex=True 參數以共用 X 軸, 結果如下 :
共用 x 軸因為上方的子圖不會顯示刻度標籤, 不會有與下方子圖之標題重疊情形, 因此通常不需要呼叫 plt.tight_layout() 讓圖形緊密.
綜合以上測試, 建立物件導向的 Figure 與 Axes/AaxeSubplot 物件的方式歸納如下表 :
建立 Figure/Axes 物件的方式 | 說明 |
fig=plt.figure(figsize=[8, 6]) ax=fig.add_axes([0.1, 0.1, 0.8, 0.8]) | 建立 Axes 物件 呼叫 add_axes() 一定要傳入繪圖區尺寸 |
fig=plt.figure(figsize=[8, 6]) ax=fig.add_subplot(3, 2, 1) | 建立 3*2 網格索引 1 之 AxesSubplot 物件 (傳 321 亦可) 呼叫 add_subplot() 可不傳網格配置與索引, 預設 111 (單圖) |
fig=plt.figure(figsize=[8, 6]) ax=plt.axes([0.1, 0.1, 0.8, 0.8]) | 建立 AxesSubplot 物件 呼叫 axes() 可不傳繪圖區尺寸, 預設 [0.125, 0.11, 0.9, 0.88] |
fig, ax=plt.subplots(3, 2, 1) | 建立 3*2 網格索引 1 之 AxesSubplot 物件 (傳 321 亦可) 呼叫 plt.subplots() 可不傳網格配置與索引, 預設 111 (單圖) 不須呼叫 plt.figure() 建立 Figure 物件, plt.subplots() 會傳回 |
如果不需要自訂畫布大小, 使用 plt.subplots() 最方便, 因為不需要另外呼叫 plt.figure() 來建立 Figure 物件, 此函式會自行建立並傳回. 需要調整畫布大小也可以用 fig.set_size() 設定.
參考 :
1 則留言 :
張貼留言