Matplotlib 畫圖標注annotate詳解


 

標注

原文:Annotation

譯者:飛龍

協議:CC BY-NC-SA 4.0

基本標注

使用text()會將文本放置在軸域的任意位置。 文本的一個常見用例是標注繪圖的某些特征,而annotate()方法提供輔助函數,使標注變得容易。 在標注中,有兩個要考慮的點:由參數xy表示的標注位置和xytext的文本位置。 這兩個參數都是(x, y)元組。

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

t = np.arange(0.0, 5.0, 0.01)
s = np.cos(2*np.pi*t)
line, = ax.plot(t, s, lw=2)

ax.annotate('local max', xy=(2, 1), xytext=(3, 1.5),
            arrowprops=dict(facecolor='black', shrink=0.05),
            )

ax.set_ylim(-2,2)
plt.show()

源代碼

在該示例中,xy(箭頭尖端)和xytext位置(文本位置)都以數據坐標為單位。 有多種可以選擇的其他坐標系 - 你可以使用xycoordstextcoords以及下列字符串之一(默認為data)指定xyxytext的坐標系。

| 參數 | 坐標系 | 
'figure points' | 距離圖形左下角的點數量 | 
'figure pixels' | 距離圖形左下角的像素數量 | 
'figure fraction' | 0,0 是圖形左下角,1,1 是右上角 | 
'axes points' | 距離軸域左下角的點數量 | 
'axes pixels' | 距離軸域左下角的像素數量 | 
'axes fraction' | 0,0 是軸域左下角,1,1 是右上角 | 
'data' | 使用軸域數據坐標系 |

例如將文本以軸域小數坐標系來放置,我們可以:

ax.annotate('local max', xy=(3, 1),  xycoords='data',
            xytext=(0.8, 0.95), textcoords='axes fraction',
            arrowprops=dict(facecolor='black', shrink=0.05),
            horizontalalignment='right', verticalalignment='top',
            )

對於物理坐標系(點或像素),原點是圖形或軸的左下角。

或者,你可以通過在可選關鍵字參數arrowprops中提供箭頭屬性字典來繪制從文本到注釋點的箭頭。

arrowprops 描述
width 箭頭寬度,以點為單位
frac 箭頭頭部所占據的比例
headwidth 箭頭的底部的寬度,以點為單位
shrink 移動提示,並使其離注釋點和文本一些距離
**kwargs matplotlib.patches.Polygon的任何鍵,例如facecolor

在下面的示例中,xy點是原始坐標(xycoords默認為'data')。 對於極坐標軸,它在(theta, radius)空間中。 此示例中的文本放置在圖形小數坐標系中。 matplotlib.text.Text關鍵字args,例如horizontalalignmentverticalalignmentfontsize,從annotate傳給Text實例。

源代碼

注釋(包括花式箭頭)的所有高上大的內容的更多信息,請參閱高級標注pylab_examples示例代碼:annotation_demo.py

不要繼續,除非你已經閱讀了基本標注text()annotate()

高級標注

使用框和文本來標注

讓我們以一個簡單的例子來開始。

源代碼

pyplot模塊(或Axes類的text方法)中的text()函數接受bbox關鍵字參數,並且在提供時,在文本周圍繪制一個框。

與文本相關聯的補丁對象可以通過以下方式訪問:

bb = t.get_bbox_patch()

返回值是FancyBboxPatch的一個實例,並且補丁屬性(如facecoloredgewidth等)可以像平常一樣訪問和修改。 為了更改框的形狀,請使用set_boxstyle方法。

bb.set_boxstyle("rarrow", pad=0.6)

該參數是框樣式的名稱與其作為關鍵字參數的屬性。 目前,實現了以下框樣式。

名稱 屬性
Circle circle pad=0.3
DArrow darrow pad=0.3
LArrow larrow pad=0.3
RArrow rarrow pad=0.3
Round round pad=0.3,rounding_size=None
Round4 round4 pad=0.3,rounding_size=None
Roundtooth roundtooth pad=0.3,tooth_size=None
Sawtooth sawtooth pad=0.3,tooth_size=None
Square square pad=0.3

源代碼

注意,屬性參數可以在樣式名稱中用逗號分隔(在初始化文本實例時,此形式可以用作bbox參數的boxstyle的值)。

bb.set_boxstyle("rarrow,pad=0.6")

使用箭頭來標注

pyplot模塊(或Axes類的annotate方法)中的annotate()函數用於繪制連接圖上兩點的箭頭。

ax.annotate("Annotation",
            xy=(x1, y1), xycoords='data',
            xytext=(x2, y2), textcoords='offset points',
            )

這會使用textcoords中提供的,xytext處的文本標注提供坐標(xycoords)中的xy處的點。 通常,數據坐標中規定了標注點,偏移點中規定了標注文本。 請參閱annotate()了解可用的坐標系。

連接兩個點(xyxytext)的箭頭可以通過指定arrowprops參數可選地繪制。 為了僅繪制箭頭,請使用空字符串作為第一個參數。

ax.annotate("",
            xy=(0.2, 0.2), xycoords='data',
            xytext=(0.8, 0.8), textcoords='data',
            arrowprops=dict(arrowstyle="->",
                            connectionstyle="arc3"),
            )

源代碼

箭頭的繪制需要幾個步驟。

  • 創建兩個點之間的連接路徑。 這由connectionstyle鍵值控制。
  • 如果提供了補丁對象(patchApatchB),則會剪切路徑以避開該補丁。
  • 路徑進一步由提供的像素總量來縮小(shirnkA&shrinkB
  • 路徑轉換為箭頭補丁,由arrowstyle鍵值控制。

源代碼

兩個點之間的連接路徑的創建由connectionstyle鍵控制,並且可用以下樣式。

名稱 屬性
angle angleA=90,angleB=0,rad=0.0
angle3 angleA=90,angleB=0
arc angleA=0,angleB=0,armA=None,armB=None,rad=0.0
arc3 rad=0.0
bar armA=0.0,armB=0.0,fraction=0.3,angle=None

注意,angle3arc3中的3意味着所得到的路徑是二次樣條段(三個控制點)。 如下面將討論的,當連接路徑是二次樣條時,可以使用一些箭頭樣式選項。

每個連接樣式的行為在下面的示例中(有限地)演示。 (警告:條形樣式的行為當前未定義好,將來可能會更改)。

源代碼

然后根據給定的箭頭樣式將連接路徑(在剪切和收縮之后)變換為箭頭補丁。

名稱 屬性
- None
-> head_length=0.4,head_width=0.2
-[ widthB=1.0,lengthB=0.2,angleB=None
|-| widthA=1.0,widthB=1.0
-|> head_length=0.4,head_width=0.2
<- head_length=0.4,head_width=0.2
<-> head_length=0.4,head_width=0.2
<|- head_length=0.4,head_width=0.2
< -|>
fancy head_length=0.4,head_width=0.4,tail_width=0.4
simple head_length=0.5,head_width=0.5,tail_width=0.2
wedge tail_width=0.3,shrink_factor=0.5

源代碼

一些箭頭僅適用於生成二次樣條線段的連接樣式。 他們是fancysimplewedge。 對於這些箭頭樣式,必須使用angle3arc3連接樣式。

如果提供了標注字符串,則patchA默認設置為文本的bbox補丁。

源代碼

text命令一樣,可以使用bbox參數來繪制文本周圍的框。

源代碼

默認情況下,起點設置為文本范圍的中心。 可以使用relpos鍵值進行調整。 這些值根據文本的范圍進行歸一化。 例如,(0,0)表示左下角,(1,1)表示右上角。

源代碼

將藝術家放置在軸域的錨定位置

有一類藝術家可以放置在軸域的錨定位置。 一個常見的例子是圖例。 這種類型的藝術家可以使用OffsetBox類創建。 mpl_toolkits.axes_grid.anchored_artists中有幾個預定義類。

from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
at = AnchoredText("Figure 1a",
                  prop=dict(size=8), frameon=True,
                  loc=2,
                  )
at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
ax.add_artist(at)

源代碼

loc關鍵字與legend命令中含義相同。

一個簡單的應用是當藝術家(或藝術家的集合)的像素大小在創建時已知。 例如,如果要繪制一個固定大小為 20 像素 ×20 像素(半徑為 10 像素)的圓,則可以使用AnchoredDrawingArea。 實例使用繪圖區域的大小創建(以像素為單位)。 用戶可以在繪圖區任意添加藝術家。 注意,添加到繪圖區域的藝術家的范圍與繪制區域本身的位置無關,只和初始大小有關。

from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea

ada = AnchoredDrawingArea(20, 20, 0, 0,
                          loc=1, pad=0., frameon=False)
p1 = Circle((10, 10), 10)
ada.drawing_area.add_artist(p1)
p2 = Circle((30, 10), 5, fc="r")
ada.drawing_area.add_artist(p2)

添加到繪圖區域的藝術家不應該具有變換集(它們將被重寫),並且那些藝術家的尺寸被解釋為像素坐標,即,上述示例中的圓的半徑分別是 10 像素和 5 像素。

源代碼

有時,你想讓你的藝術家按數據坐標(或其他坐標,而不是畫布像素)縮放。 你可以使用AnchoredAuxTransformBox類。 這類似於AnchoredDrawingArea,除了藝術家的范圍在繪制時由指定的變換確定。

from mpl_toolkits.axes_grid.anchored_artists import AnchoredAuxTransformBox

box = AnchoredAuxTransformBox(ax.transData, loc=2)
el = Ellipse((0,0), width=0.1, height=0.4, angle=30) # in data coordinates!
box.drawing_area.add_artist(el)

上述示例中的橢圓具有在數據坐標中對應於 0.1 和 0.4 的寬度和高度,並且當軸域的視圖限制改變時將自動縮放。

源代碼

如圖例所示,可以設置bbox_to_anchor參數。 使用HPackerVPacker,你可以像圖例中一樣排列藝術家(事實上,這是圖例的創建方式)。

源代碼

請注意,與圖例不同,默認情況下,bbox_transform設置為IdentityTransform

使用復雜坐標來標注

matplotlib 中的標注支持標注文本中描述的幾種類型的坐標。 對於想要更多控制的高級用戶,它支持幾個其他選項。

    1. Transform實例,例如:

      ax.annotate("Test", xy=(0.5, 0.5), xycoords=ax.transAxes)
    2. 相當於:

      ax.annotate("Test", xy=(0.5, 0.5), xycoords="axes fraction")
    3. 使用它,你可以在其他軸域內標注一個點:

      ax1, ax2 = subplot(121), subplot(122) ax2.annotate("Test", xy=(0.5, 0.5), xycoords=ax1.transData, xytext=(0.5, 0.5), textcoords=ax2.transData, arrowprops=dict(arrowstyle="->"))

 

4.Artist實例。xy值(或xytext)被解釋為藝術家的bboxget_window_extent的返回值)的小數坐標。

an1 = ax.annotate("Test 1", xy=(0.5, 0.5), xycoords="data", va="center", ha="center", bbox=dict(boxstyle="round", fc="w")) an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1, # (1,0.5) of the an1's bbox xytext=(30,0), textcoords="offset points", va="center", ha="left", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))

源代碼

請注意,你的責任是在繪制an2之前確定坐標藝術家(上例中的an1)的范圍。 在大多數情況下,這意味着an2需要晚於an1

    1. 一個返回BboxBaseTransform的實例的可調用對象。 如果返回一個變換,它與 1 相同,如果返回bbox,它與 2 相同。可調用對象應該接受renderer實例的單個參數。 例如,以下兩個命令產生相同的結果:

      an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1, xytext=(30,0), textcoords="offset points") an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1.get_window_extent, xytext=(30,0), textcoords="offset points")
    2. 指定二元坐標的元組。 第一項用於

x

      坐標,第二項用於

y

    坐標。 例如,
  1. annotate("Test", xy=(0.5, 1), xycoords=("data", "axes fraction"))
  2. 0.5 的單位是數據坐標,1 的單位是歸一化軸域坐標。 你可以像使用元組一樣使用藝術家或變換。 例如,

    import matplotlib.pyplot as plt plt.figure(figsize=(3,2)) ax=plt.axes([0.1, 0.1, 0.8, 0.7]) an1 = ax.annotate("Test 1", xy=(0.5, 0.5), xycoords="data", va="center", ha="center", bbox=dict(boxstyle="round", fc="w")) an2 = ax.annotate("Test 2", xy=(0.5, 1.), xycoords=an1, xytext=(0.5,1.1), textcoords=(an1, "axes fraction"), va="bottom", ha="center", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->")) plt.show()源代碼
  3. 有時,您希望您的注釋帶有一些“偏移點”,不是距離注釋點,而是距離某些其他點。 OffsetFrom是這種情況下的輔助類。

    import matplotlib.pyplot as plt plt.figure(figsize=(3,2)) ax=plt.axes([0.1, 0.1, 0.8, 0.7]) an1 = ax.annotate("Test 1", xy=(0.5, 0.5), xycoords="data", va="center", ha="center", bbox=dict(boxstyle="round", fc="w")) from matplotlib.text import OffsetFrom offset_from = OffsetFrom(an1, (0.5, 0)) an2 = ax.annotate("Test 2", xy=(0.1, 0.1), xycoords="data", xytext=(0, -10), textcoords=offset_from, # xytext is offset points from "xy=(0.5, 0), xycoords=an1" va="top", ha="center", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->")) plt.show()
  4. 你可以參考這個鏈接:pylab_examples example code: annotation_demo3.py.

使用ConnectorPatch

ConnectorPatch類似於沒有文本的標注。 雖然在大多數情況下建議使用標注函數,但是當您想在不同的軸上連接點時,ConnectorPatch很有用。

from matplotlib.patches import ConnectionPatch xy = (0.2, 0.2) con = ConnectionPatch(xyA=xy, xyB=xy, coordsA="data", coordsB="data", axesA=ax1, axesB=ax2) ax2.add_artist(con)

上述代碼連接了ax1中數據坐標的xy點,與ax2中數據坐標的xy點。這是個簡單的例子。

源代碼

雖然ConnectorPatch實例可以添加到任何軸,但您可能需要將其添加到繪圖順序中最新的軸,以防止與其他軸重疊。

高級話題

軸域之間的縮放效果

mpl_toolkits.axes_grid.inset_locator定義了一些補丁類,用於互連兩個軸域。 理解代碼需要一些 mpl 轉換如何工作的知識。 但是,利用它的方式很直接。

源代碼

定義自定義盒樣式

你可以使用自定義盒樣式,boxstyle的值可以為如下形式的可調用對象:

def __call__(self, x0, y0, width, height, mutation_size,
             aspect_ratio=1.):
    """
    Given the location and size of the box, return the path of
    the box around it.

      - *x0*, *y0*, *width*, *height* : location and size of the box
      - *mutation_size* : a reference scale for the mutation.
      - *aspect_ratio* : aspect-ratio for the mutation.
    """
    path = ...
    return path

這里是個復雜的例子:

源代碼

但是,推薦你從matplotlib.patches.BoxStyle._Base派生,像這樣:

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

# we may derive from matplotlib.patches.BoxStyle._Base class.
# You need to override transmute method in this case.

class MyStyle(BoxStyle._Base):
    """
    A simple box.
    """

    def __init__(self, pad=0.3):
        """
        The arguments need to be floating numbers and need to have
        default values.

         *pad*
            amount of padding
        """

        self.pad = pad
        super(MyStyle, self).__init__()

    def transmute(self, x0, y0, width, height, mutation_size):
        """
        Given the location and size of the box, return the path of
        the box around it.

         - *x0*, *y0*, *width*, *height* : location and size of the box
         - *mutation_size* : a reference scale for the mutation.

        Often, the *mutation_size* is the font size of the text.
        You don't need to worry about the rotation as it is
        automatically taken care of.
        """

        # padding
        pad = mutation_size * self.pad

        # width and height with padding added.
        width, height = width + 2.*pad, \
                        height + 2.*pad,

        # boundary of the padded box
        x0, y0 = x0-pad, y0-pad,
        x1, y1 = x0+width, y0 + height

        cp = [(x0, y0),
              (x1, y0), (x1, y1), (x0, y1),
              (x0-pad, (y0+y1)/2.), (x0, y0),
              (x0, y0)]

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

        path = Path(cp, com)

        return path


# register the custom style
BoxStyle._style_list["angled"] = MyStyle

plt.figure(1, figsize=(3,3))
ax = plt.subplot(111)
ax.text(0.5, 0.5, "Test", size=30, va="center", ha="center", rotation=30,
        bbox=dict(boxstyle="angled,pad=0.5", alpha=0.2))

del BoxStyle._style_list["angled"]

plt.show()

源代碼

與之類似,您可以定義一個自定義的ConnectionStyle和一個自定義的ArrowStyle。 請參閱lib/matplotlib/patches.py的源代碼,並查看每個樣式類是如何定義的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM