# 藝術家教程
> 原文:[Artist tutorial](http://matplotlib.org/users/artists.html)
> 譯者:[飛龍](https://github.com/)
> 協議:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
matplotlib API 有三個層級。 `matplotlib.backend_bases.FigureCanvas`是繪制圖形的區域,`matplotlib.backend_bases.Renderer`是知道如何在`ChartCanvas`上繪制的對象,而`matplotlib.artist.Artist`是知道如何使用渲染器在畫布上畫圖的對象。 `FigureCanvas`和`Renderer`處理與用戶界面工具包(如 wxPython)或 PostScript? 等繪圖語言交互的所有細節,`Artist`處理所有高級結構,如表示和布局圖形,文本和線條。用戶通常要花費95%的時間來處理藝術家。
有兩種類型的藝術家:基本類型和容器類型。基本類型表示我們想要繪制到畫布上的標準圖形對象:`Line2D`,`Rectangle`,`Text`,`AxesImage`等,容器是放置它們的位置(`Axis`,`Axes`和`Figure`)。標準用法是創建一個`Figure`實例,使用`Figure`創建一個或多個`Axes`或`Subplot`實例,并使用`Axes`實例的輔助方法來創建基本類型。在下面的示例中,我們使用`matplotlib.pyplot.figure()`創建一個`Figure`實例,這是一個便捷的方法,用于實例化`Figure`實例并將它們與你的用戶界面或繪圖工具包`FigureCanvas`連接。正如我們將在下面討論的,這不是必須的 - 你可以直接使用 PostScript,PDF,Gtk+ 或 wxPython `FigureCanvas`實例,直接實例化你的圖形并連接它們 - 但是因為我們在這里關注藝術家 API,我們讓`pyplot`為我們處理一些細節:
```py
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(2,1,1) # two rows, one column, first plot
```
`Axes`可能是 matplotlib API 中最重要的類,你將在大多數時間使用它。 這是因為`Axes`是大多數對象所進入的繪圖區域,`Axes`有許多特殊的輔助方法(`plot()`,`text()`,`hist()`,`imshow()`)來創建最常見的圖形基本類型 `Line2D`,`Text`,`Rectangle`,`Image`)。 這些輔助方法將獲取你的數據(例如 numpy 數組和字符串),并根據需要創建基本`Artist`實例(例如,`Line2D`),將它們添加到相關容器中,并在請求時繪制它們。 大多數人可能熟悉子圖,這只是`Axes`的一個特例,它存在于`Subplot`實例的列網格的固定行上。 如果要在任意位置創建`Axes`,只需使用`add_axes()`方法,該方法接受`[left, bottom, width, height]`值的列表,以 0~1 的圖形相對坐標為單位:
```PY
fig2 = plt.figure()
ax2 = fig2.add_axes([0.15, 0.1, 0.7, 0.3])
```
以我們的例子繼續:
```py
import numpy as np
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2*np.pi*t)
line, = ax.plot(t, s, color='blue', lw=2)
```
在這個例子中,`ax`是上面的`fig.add_subplot`調用創建的`Axes`實例(記住`Subplot`只是`Axes`的一個子類),當你調用`ax.plot`時,它創建一個`Line2D`實例并將其添加到`Axes.lines`列表中。 在下面的 ipython 交互式會話中,你可以看到`Axes.lines`列表的長度為 1,并且包含由`line, = ax.plot... `調用返回的相同線條:
```py
In [101]: ax.lines[0]
Out[101]: <matplotlib.lines.Line2D instance at 0x19a95710>
In [102]: line
Out[102]: <matplotlib.lines.Line2D instance at 0x19a95710>
```
如果你對`ax.plot`進行連續調用(并且保持狀態為『on』,這是默認值),則將在列表中添加其他線條。 你可以稍后通過調用列表方法刪除線條;任何一個方法都可以:
```py
del ax.lines[0]
ax.lines.remove(line) # one or the other, not both!
```
軸域也擁有輔助方法,用于設置和裝飾 x 和 y 軸的刻度、刻度標簽和軸標簽:
```py
xtext = ax.set_xlabel('my xdata') # returns a Text instance
ytext = ax.set_ylabel('my ydata')
```
當你調用`ax.set_xlabel`時,它將信息傳遞給`XAxis`的`Text`實例,每個`Axes `實例都包含`XAxis`和`YAxis`,它們處理刻度、刻度標簽和軸標簽的布局和繪制。
嘗試創建下面的圖形:

## 自定義你的對象
圖中的每個元素都由一個 matplotlib 藝術家表示,每個元素都有一個擴展屬性列表用于配置它的外觀。 圖形本身包含一個`Rectangle`,正好是圖形的大小,你可以使用它來設置圖形的背景顏色和透明度。 同樣,每個`Axes`邊框(在通常的 matplotlib 繪圖中是標準的白底黑邊)擁有一個`Rectangle`實例,用于確定軸域的顏色,透明度和其他屬性,這些實例存儲為成員變量`Figure.patch`和`Axes.patch`(『Patch』是一個繼承自 MATLAB 的名稱,它是圖形上的一個顏色的 2D『補丁』,例如矩形,圓和多邊形)。每個 matplotlib 藝術家都有以下屬性。
| 屬性 | 描述 |
| --- | --- |
| alpha | 透明度 - 0 ~ 1 的標量 |
| animated | 用于幫助動畫繪制的布爾值 |
| axes | 藝術家所在的軸域,可能為空 |
| clip_box | 用于剪切藝術家的邊框 |
| clip_on | 剪切是否開啟 |
| clip_path | 藝術家被剪切的路徑 |
| contains | 一個拾取函數,用于判斷藝術家是否位于拾取點 |
| figure | 藝術家所在的圖形實例,可能為空 |
| label | 文本標簽(用于自動標記) |
| picker | 控制對象拾取的 Python 對象 |
| transform | 變換 |
| visible | 布爾值,表示藝術家是否應該繪制 |
| zorder | 確定繪制順序的數值 |
| rasterized | 布爾值,是否將向量轉換為光柵圖形(出于壓縮或 eps 透明度) |
每個屬性都使用一個老式的`setter`或`getter`(是的,我們知道這會刺激 Python 愛好者,我們計劃支持通過屬性或 traits 直接訪問,但它還沒有完成)。 例如,要將當前`alpha`值變為一半:
```py
a = o.get_alpha()
o.set_alpha(0.5*a)
```
如果你打算可以一次性設置一些屬性,你也可以以關鍵字參數使用`set`方法,例如:
```py
o.set(alpha=0.5, zorder=2)
```
如果你在 Python 交互式 Shell 中工作,檢查`Artist`屬性的一種方便的方法是使用`matplotlib.artist.getp()`函數(在 pylab 中只需要`getp()`),它列出了屬性及其值。 這適用于從`Artist`派生的類,例如`Figure`和`Rectangle`。 這里是上面提到的`Figure`的矩形屬性:
```py
In [149]: matplotlib.artist.getp(fig.patch)
alpha = 1.0
animated = False
antialiased or aa = True
axes = None
clip_box = None
clip_on = False
clip_path = None
contains = None
edgecolor or ec = w
facecolor or fc = 0.75
figure = Figure(8.125x6.125)
fill = 1
hatch = None
height = 1
label =
linewidth or lw = 1.0
picker = None
transform = <Affine object at 0x134cca84>
verts = ((0, 0), (0, 1), (1, 1), (1, 0))
visible = True
width = 1
window_extent = <Bbox object at 0x134acbcc>
x = 0
y = 0
zorder = 1
```
所有類的文檔字符串也包含`Artist`屬性,因此你可以查閱交互式『幫助』或 [`Artist`模塊](http://matplotlib.org/api/artist_api.html#artist-api),來獲取給定對象的屬性列表。
## 對象容器
現在我們知道如何檢查和設置我們想要配置的給定對象的屬性,現在我們需要如何獲取該對象。 前面提到了兩種對象:基本類型和容器類型。 基本類型通常是你想要配置的東西(`Text`實例的字體,`Line2D`的寬度),雖然容器也有一些屬性 - 例如 `Axes`是一個容器藝術家,包含你的繪圖中的許多基本類型,但它也有屬性,比如`xscale`來控制`xaxis`是『線性』還是『對數』。 在本節中,我們將回顧各種容器對象存儲你想要訪問的藝術家的位置。
## 圖形容器
頂層容器藝術家是`matplotlib.figure.Figure`,它包含圖形中的所有內容。 圖形的背景是一個`Rectangle`,存儲在`Figure.patch`中。 當你向圖形中添加子圖(`add_subplot()`)和軸域(`add_axes()`)時,這些會附加到`Figure.axes`。 它們也由創建它們的方法返回:
```py
In [156]: fig = plt.figure()
In [157]: ax1 = fig.add_subplot(211)
In [158]: ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
In [159]: ax1
Out[159]: <matplotlib.axes.Subplot instance at 0xd54b26c>
In [160]: print fig.axes
[<matplotlib.axes.Subplot instance at 0xd54b26c>, <matplotlib.axes.Axes instance at 0xd3f0b2c>]
```
因為圖形維護了『當前軸域』(見`figure.gca`和圖`figure.sca`)的概念以支持 pylab/pyplot 狀態機,所以不應直接從軸域列表中插入或刪除軸域,而應使用`add_subplot()`和`add_axes()`方法進行插入,并使用`delaxes()`方法進行刪除。 然而,你可以自由地遍歷軸域列表或索引,來訪問要自定義的`Axes`實例。 下面是一個打開所有軸域網格的示例:
```py
for ax in fig.axes:
ax.grid(True)
```
圖形還擁有自己的文本,線條,補丁和圖像,你可以使用它們直接添加基本類型。 圖形的默認坐標系統簡單地以像素(這通常不是你想要的)為單位,但你可以通過設置你添加到圖中的藝術家的`transform`屬性來控制它。
更有用的是『圖形坐標系』,其中`(0,0)`是圖的左下角,`(1,1)`是圖的右上角,你可以通過將`Artist`的變換設置為`fig.transFigure`來獲得:
```py
In [191]: fig = plt.figure()
In [192]: l1 = matplotlib.lines.Line2D([0, 1], [0, 1],
transform=fig.transFigure, figure=fig)
In [193]: l2 = matplotlib.lines.Line2D([0, 1], [1, 0],
transform=fig.transFigure, figure=fig)
In [194]: fig.lines.extend([l1, l2])
In [195]: fig.canvas.draw()
```

這里是圖形可以包含的藝術家總結:
| 圖形屬性 | 描述 |
| --- | --- |
| `axes` | `Axes`實例的列表(包括`Subplot`) |
| `patch` | `Rectangle`背景 |
| `images` | `FigureImages`補丁的列表 - 用于原始像素顯示 |
| `legends` | 圖形`Legend`實例的列表(不同于`Axes.legends`) |
| `lines` | 圖形`Line2D`實例的列表(很少使用,見`Axes.lines`) |
| `patches` | 圖形補丁列表(很少使用,見`Axes.patches`) |
| `texts` | 圖形`Text`實例的列表 |
## 軸域容器
`matplotlib.axes.Axes`是 matplotlib 宇宙的中心 - 它包含絕大多數在一個圖形中使用的藝術家,并帶有許多輔助方法來創建和添加這些藝術家本身,以及訪問和自定義所包含的藝術家的輔助方法。 就像`Figure`那樣,它包含一個`Patch patch`,它是一個用于笛卡爾坐標的`Rectangle`和一個用于極坐標的`Cirecle`; 這個補丁決定了繪圖區域的形狀,背景和邊框:
```py
ax = fig.add_subplot(111)
rect = ax.patch # a Rectangle instance
rect.set_facecolor('green')
```
當調用繪圖方法(例如通常是`plot()`)并傳遞數組或值列表時,該方法將創建一個`matplotlib.lines.Line2D()`實例,將所有`Line2D`屬性作為關鍵字參數傳遞, 將該線條添加到`Axes.lines`容器,并將其返回給你:
```py
In [213]: x, y = np.random.rand(2, 100)
In [214]: line, = ax.plot(x, y, '-', color='blue', linewidth=2)
```
`plot`返回一個線條列表,因為你可以傳入多個`x,y`偶對來繪制,我們將長度為一的列表的第一個元素解構到`line`變量中。 該線條已添加到`Axes.lines`列表中:
```py
In [229]: print ax.lines
[<matplotlib.lines.Line2D instance at 0xd378b0c>]
```
與之類似,創建補丁的方法(如`bar()`)會創建一個矩形列表,將補丁添加到`Axes.patches`列表中:
```py
In [233]: n, bins, rectangles = ax.hist(np.random.randn(1000), 50, facecolor='yellow')
In [234]: rectangles
Out[234]: <a list of 50 Patch objects>
In [235]: print len(ax.patches)
```
你不應該直接將對象添加到`Axes.lines`或`Axes.patches`列表,除非你確切知道你在做什么,因為`Axes`需要在它創建和添加對象做一些事情。 它設置`Artist`的`figure`和`axes`屬性,以及默認`Axes`變換(除非設置了變換)。 它還檢查`Artist`中包含的數據,來更新控制自動縮放的數據結構,以便可以調整視圖限制來包含繪制的數據。 但是,你可以自己創建對象,并使用輔助方法(如`add_line()`和`add_patch()`)將它們直接添加到`Axes`。 這里是一個注釋的交互式會話,說明正在發生什么:
```py
In [261]: fig = plt.figure()
In [262]: ax = fig.add_subplot(111)
# create a rectangle instance
In [263]: rect = matplotlib.patches.Rectangle( (1,1), width=5, height=12)
# by default the axes instance is None
In [264]: print rect.get_axes()
None
# and the transformation instance is set to the "identity transform"
In [265]: print rect.get_transform()
<Affine object at 0x13695544>
# now we add the Rectangle to the Axes
In [266]: ax.add_patch(rect)
# and notice that the ax.add_patch method has set the axes
# instance
In [267]: print rect.get_axes()
Axes(0.125,0.1;0.775x0.8)
# and the transformation has been set too
In [268]: print rect.get_transform()
<Affine object at 0x15009ca4>
# the default axes transformation is ax.transData
In [269]: print ax.transData
<Affine object at 0x15009ca4>
# notice that the xlimits of the Axes have not been changed
In [270]: print ax.get_xlim()
(0.0, 1.0)
# but the data limits have been updated to encompass the rectangle
In [271]: print ax.dataLim.bounds
(1.0, 1.0, 5.0, 12.0)
# we can manually invoke the auto-scaling machinery
In [272]: ax.autoscale_view()
# and now the xlim are updated to encompass the rectangle
In [273]: print ax.get_xlim()
(1.0, 6.0)
# we have to manually force a figure draw
In [274]: ax.figure.canvas.draw()
```
有非常多的`Axes`輔助方法用于創建基本藝術家并將它們添加到他們各自的容器中。 下表總結了他們的一部分,他們創造的`Artist`的種類,以及他們在哪里存儲它們。
| 輔助方法 | 藝術家 | 容器 |
| --- | --- | --- |
| `ax.annotate` - 文本標注 | `Annotate` | `ax.texts` |
| `ax.bar` - 條形圖 | `Rectangle` | `ax.patches` |
| `ax.errorbar` - 誤差條形圖 | `Line2D` 和 `Rectangle` | `ax.lines` 和 `ax.patches` |
| `ax.fill` - 共享區域 | `Polygon` | `ax.patches` |
| `ax.hist` - 直方圖 | `Rectangle` | `ax.patches` |
| `ax.imshow` - 圖像數據 | `AxesImage` | `ax.images` |
| `ax.legend` - 軸域圖例 | `Legend` | `ax.legends` |
| `ax.plot` - xy 繪圖 | `Line2D` | `ax.lines` |
| `ax.scatter` - 散點圖 | `PolygonCollection` | `ax.collections` |
| `ax.text` - 文本 | `Text` | `ax.texts` |
除了所有這些藝術家,`Axes`包含兩個重要的藝術家容器:`XAxis`和`YAxis`,它們處理刻度和標簽的繪制。 它們被存儲為實例變量`xaxis`和`yaxis`。 `XAxis`和`YAxis`容器將在下面詳細介紹,但請注意,`Axes`包含許多輔助方法,它們會將調用轉發給`Axis`實例,因此你通常不需要直接使用它們,除非你愿意。 例如,你可以使用`Axes`輔助程序方法設置`XAxis`刻度標簽的字體大小:
```py
for label in ax.get_xticklabels():
label.set_color('orange')
```
下面是軸域所包含的藝術家的總結
| 軸域屬性 | 描述 |
| --- | --- |
| `artists` | `Artist`實例的列表 |
| `patch` | 用于軸域背景的`Rectangle`實例 |
| `collections` | `Collection`實例的列表 |
| `images` | `AxesImage`的列表 |
| `legends` | `Legend`實例的列表 |
| `lines` | `Line2D`實例的列表 |
| `patches` | `Patch`實例的列表 |
| `texts` | `Text`實例的列表 |
| `xaxis` | `matplotlib.axis.XAxis`實例 |
| `yaxis` | `matplotlib.axis.YAxis`實例 |
## 軸容器
`matplotlib.axis.Axis`實例處理刻度線,網格線,刻度標簽和軸標簽的繪制。你可以分別為y軸配置左和右刻度,為x軸分別配置上和下刻度。 `Axis`還存儲在自動縮放,平移和縮放中使用的數據和視圖間隔,以及`Locator`和`Formatter`實例,它們控制刻度位置以及它們表示為字符串的方式。
每個`Axis`對象都包含一個`label`屬性(這是 pylab 在調用`xlabel()`和`ylabel()`時修改的東西)以及主和次刻度的列表。刻度是`XTick`和`YTick`實例,它包含渲染刻度和刻度標簽的實際線條和文本基本類型。因為刻度是按需動態創建的(例如,當平移和縮放時),你應該通過訪問器方法`get_major_ticks()`和`get_minor_ticks()`訪問主和次刻度的列表。雖然刻度包含所有下面要提及的基本類型,`Axis`方法包含訪問器方法來返回刻度線,刻度標簽,刻度位置等:
```py
In [285]: axis = ax.xaxis
In [286]: axis.get_ticklocs()
Out[286]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [287]: axis.get_ticklabels()
Out[287]: <a list of 10 Text major ticklabel objects>
# note there are twice as many ticklines as labels because by
# default there are tick lines at the top and bottom but only tick
# labels below the xaxis; this can be customized
In [288]: axis.get_ticklines()
Out[288]: <a list of 20 Line2D ticklines objects>
# by default you get the major ticks back
In [291]: axis.get_ticklines()
Out[291]: <a list of 20 Line2D ticklines objects>
# but you can also ask for the minor ticks
In [292]: axis.get_ticklines(minor=True)
Out[292]: <a list of 0 Line2D ticklines objects>
```
下面是`Axis`的一些有用的訪問器方法的總結(它們擁有相應的`setter`,如`set_major_formatter`)。
| 訪問器方法 | 描述 |
| --- | --- |
| get_scale | 軸的比例,例如`'log'`或`'linear'` |
| get_view_interval | 軸視圖范圍的內部實例 |
| get_data_interval | 軸數據范圍的內部實例 |
| get_gridlines | 軸的網格線列表 |
| get_label | 軸標簽 - `Text`實例 |
| get_ticklabels | `Text`實例的列表 - 關鍵字`minor=True|False` |
| get_ticklines | `Line2D`實例的列表 - 關鍵字`minor=True|False` |
| get_ticklocs | `Tick`位置的列表 - 關鍵字`minor=True|False` |
| get_major_locator | 用于主刻度的`matplotlib.ticker.Locator`實例 |
| get_major_formatter | 用于主刻度的`matplotlib.ticker.Formatter`實例 |
| get_minor_locator | 用于次刻度的`matplotlib.ticker.Locator`實例 |
| get_minor_formatter | 用于次刻度的`matplotlib.ticker.Formatter`實例 |
| get_major_ticks | 用于主刻度的`Tick`實例列表 |
| get_minor_ticks | 用于次刻度的`Tick`實例列表 |
| grid | 為主或次刻度打開或關閉網格 |
這里是個例子,出于美觀不太推薦,它自定義了軸域和刻度屬性。
```py
import numpy as np
import matplotlib.pyplot as plt
# plt.figure creates a matplotlib.figure.Figure instance
fig = plt.figure()
rect = fig.patch # a rectangle instance
rect.set_facecolor('lightgoldenrodyellow')
ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4])
rect = ax1.patch
rect.set_facecolor('lightslategray')
for label in ax1.xaxis.get_ticklabels():
# label is a Text instance
label.set_color('red')
label.set_rotation(45)
label.set_fontsize(16)
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
plt.show()
```

## 刻度容器
`matplotlib.axis.Tick`是我們從`Figure`到`Axes`再到`Axis`再到`Tick`的最終的容器對象。`Tick`包含刻度和網格線的實例,以及上側和下側刻度的標簽實例。 每個都可以直接作為`Tick`的屬性訪問。此外,也有用于確定上標簽和刻度是否對應`x`軸,以及右標簽和刻度是否對應`y`軸的布爾變量。
| 刻度屬性 | 描述 |
| --- | --- |
| `tick1line` | `Line2D`實例 |
| `tick2line` | `Line2D`實例 |
| `gridline` | `Line2D`實例 |
| `label1` | `Text`實例 |
| `label2` | `Text`實例 |
| `gridOn` | 確定是否繪制刻度線的布爾值 |
| `tick1On` | 確定是否繪制主刻度線的布爾值 |
| `tick2On` | 確定是否繪制次刻度線的布爾值 |
| `label1On` | 確定是否繪制主刻度標簽的布爾值 |
| `label2On` | 確定是否繪制次刻度標簽的布爾值 |
這里是個例子,使用美元符號設置右側刻度,并在`y`軸右側將它們設成綠色。
```py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
# Fixing random state for reproducibility
np.random.seed(19680801)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(100*np.random.rand(20))
formatter = ticker.FormatStrFormatter('$%1.2f')
ax.yaxis.set_major_formatter(formatter)
for tick in ax.yaxis.get_major_ticks():
tick.label1On = False
tick.label2On = True
tick.label2.set_color('green')
plt.show()
```
