注释

使用文本框进行注释

先看一个简单的例子:

In [1]:

  1. import numpy.random
  2. import matplotlib.pyplot as plt
  3. %matplotlib inline
  4.  
  5. fig = plt.figure(1, figsize=(5,5))
  6. fig.clf()
  7.  
  8. ax = fig.add_subplot(111)
  9. ax.set_aspect(1)
  10.  
  11. x1 = -1 + numpy.random.randn(100)
  12. y1 = -1 + numpy.random.randn(100)
  13. x2 = 1. + numpy.random.randn(100)
  14. y2 = 1. + numpy.random.randn(100)
  15.  
  16. ax.scatter(x1, y1, color="r")
  17. ax.scatter(x2, y2, color="g")
  18.  
  19. # 加上两个文本框
  20. bbox_props = dict(boxstyle="round", fc="w", ec="0.5", alpha=0.9)
  21. ax.text(-2, -2, "Sample A", ha="center", va="center", size=20,
  22. bbox=bbox_props)
  23. ax.text(2, 2, "Sample B", ha="center", va="center", size=20,
  24. bbox=bbox_props)
  25.  
  26. # 加上一个箭头文本框
  27. bbox_props = dict(boxstyle="rarrow", fc=(0.8,0.9,0.9), ec="b", lw=2)
  28. t = ax.text(0, 0, "Direction", ha="center", va="center", rotation=45,
  29. size=15,
  30. bbox=bbox_props)
  31.  
  32. bb = t.get_bbox_patch()
  33. bb.set_boxstyle("rarrow", pad=0.6)
  34.  
  35. ax.set_xlim(-4, 4)
  36. ax.set_ylim(-4, 4)
  37.  
  38. plt.show()

06.06 注释 - 图1

text() 函数接受 bbox 参数来绘制文本框。

  1. bbox_props = dict(boxstyle="rarrow,pad=0.3", fc="cyan", ec="b", lw=2)
  2. t = ax.text(0, 0, "Direction", ha="center", va="center", rotation=45,
  3. size=15,
  4. bbox=bbox_props)

可以这样来获取这个文本框,并对其参数进行修改:

  1. bb = t.get_bbox_patch()
  2. bb.set_boxstyle("rarrow", pad=0.6)

可用的文本框风格有:

class name attrs
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

In [2]:

  1. import matplotlib.patches as mpatch
  2. import matplotlib.pyplot as plt
  3.  
  4. styles = mpatch.BoxStyle.get_styles()
  5.  
  6. figheight = (len(styles)+.5)
  7. fig1 = plt.figure(figsize=(4/1.5, figheight/1.5))
  8. fontsize = 0.3 * 72
  9. ax = fig1.add_subplot(111)
  10.  
  11. for i, (stylename, styleclass) in enumerate(styles.items()):
  12. ax.text(0.5, (float(len(styles)) - 0.5 - i)/figheight, stylename,
  13. ha="center",
  14. size=fontsize,
  15. transform=fig1.transFigure,
  16. bbox=dict(boxstyle=stylename, fc="w", ec="k"))
  17.  
  18. # 去掉轴的显示
  19. ax.spines['right'].set_color('none')
  20. ax.spines['top'].set_color('none')
  21. ax.spines['left'].set_color('none')
  22. ax.spines['bottom'].set_color('none')
  23. plt.xticks([])
  24. plt.yticks([])
  25.  
  26. plt.show()

06.06 注释 - 图2

各个风格的文本框如上图所示。

使用箭头进行注释

In [3]:

  1. plt.figure(1, figsize=(3,3))
  2. ax = plt.subplot(111)
  3.  
  4. ax.annotate("",
  5. xy=(0.2, 0.2), xycoords='data',
  6. xytext=(0.8, 0.8), textcoords='data',
  7. arrowprops=dict(arrowstyle="->",
  8. connectionstyle="arc3"),
  9. )
  10.  
  11. plt.show()

06.06 注释 - 图3

之前介绍了 annotatexy, xycoords, xytext, textcoords 参数的含义,通常我们把 xy 设在 data 坐标系,把 xytext 设在 offset 即以注释点为原点的参考系。

箭头显示是可选的,用 arrowprops 参数来指定,接受一个字典作为参数。

不同类型的绘制箭头方式:

In [4]:

  1. import matplotlib.pyplot as plt
  2. import matplotlib.patches as mpatches
  3.  
  4. x1, y1 = 0.3, 0.3
  5. x2, y2 = 0.7, 0.7
  6.  
  7. fig = plt.figure(1, figsize=(8,3))
  8. fig.clf()
  9. from mpl_toolkits.axes_grid.axes_grid import AxesGrid
  10. from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
  11.  
  12. #from matplotlib.font_manager import FontProperties
  13.  
  14. def add_at(ax, t, loc=2):
  15. fp = dict(size=10)
  16. _at = AnchoredText(t, loc=loc, prop=fp)
  17. ax.add_artist(_at)
  18. return _at
  19.  
  20.  
  21. grid = AxesGrid(fig, 111, (1, 4), label_mode="1", share_all=True)
  22.  
  23. grid[0].set_autoscale_on(False)
  24.  
  25. ax = grid[0]
  26. ax.plot([x1, x2], [y1, y2], ".")
  27. el = mpatches.Ellipse((x1, y1), 0.3, 0.4, angle=30, alpha=0.2)
  28. ax.add_artist(el)
  29. ax.annotate("",
  30. xy=(x1, y1), xycoords='data',
  31. xytext=(x2, y2), textcoords='data',
  32. arrowprops=dict(arrowstyle="-", #linestyle="dashed",
  33. color="0.5",
  34. patchB=None,
  35. shrinkB=0,
  36. connectionstyle="arc3,rad=0.3",
  37. ),
  38. )
  39.  
  40. add_at(ax, "connect", loc=2)
  41.  
  42. ax = grid[1]
  43. ax.plot([x1, x2], [y1, y2], ".")
  44. el = mpatches.Ellipse((x1, y1), 0.3, 0.4, angle=30, alpha=0.2)
  45. ax.add_artist(el)
  46. ax.annotate("",
  47. xy=(x1, y1), xycoords='data',
  48. xytext=(x2, y2), textcoords='data',
  49. arrowprops=dict(arrowstyle="-", #linestyle="dashed",
  50. color="0.5",
  51. patchB=el,
  52. shrinkB=0,
  53. connectionstyle="arc3,rad=0.3",
  54. ),
  55. )
  56.  
  57. add_at(ax, "clip", loc=2)
  58.  
  59.  
  60. ax = grid[2]
  61. ax.plot([x1, x2], [y1, y2], ".")
  62. el = mpatches.Ellipse((x1, y1), 0.3, 0.4, angle=30, alpha=0.2)
  63. ax.add_artist(el)
  64. ax.annotate("",
  65. xy=(x1, y1), xycoords='data',
  66. xytext=(x2, y2), textcoords='data',
  67. arrowprops=dict(arrowstyle="-", #linestyle="dashed",
  68. color="0.5",
  69. patchB=el,
  70. shrinkB=5,
  71. connectionstyle="arc3,rad=0.3",
  72. ),
  73. )
  74.  
  75. add_at(ax, "shrink", loc=2)
  76.  
  77.  
  78. ax = grid[3]
  79. ax.plot([x1, x2], [y1, y2], ".")
  80. el = mpatches.Ellipse((x1, y1), 0.3, 0.4, angle=30, alpha=0.2)
  81. ax.add_artist(el)
  82. ax.annotate("",
  83. xy=(x1, y1), xycoords='data',
  84. xytext=(x2, y2), textcoords='data',
  85. arrowprops=dict(arrowstyle="fancy", #linestyle="dashed",
  86. color="0.5",
  87. patchB=el,
  88. shrinkB=5,
  89. connectionstyle="arc3,rad=0.3",
  90. ),
  91. )
  92.  
  93. add_at(ax, "mutate", loc=2)
  94.  
  95. grid[0].set_xlim(0, 1)
  96. grid[0].set_ylim(0, 1)
  97. grid[0].axis["bottom"].toggle(ticklabels=False)
  98. grid[0].axis["left"].toggle(ticklabels=False)
  99. fig.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95)
  100.  
  101. plt.draw()
  102. plt.show()

06.06 注释 - 图4

字典中,connectionstyle 参数控制路径的风格:

Name Attr
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

In [5]:

  1. import matplotlib.pyplot as plt
  2. import matplotlib.patches as mpatches
  3.  
  4. fig = plt.figure(1, figsize=(8,5))
  5. fig.clf()
  6. from mpl_toolkits.axes_grid.axes_grid import AxesGrid
  7. from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
  8.  
  9. #from matplotlib.font_manager import FontProperties
  10.  
  11. def add_at(ax, t, loc=2):
  12. fp = dict(size=8)
  13. _at = AnchoredText(t, loc=loc, prop=fp)
  14. ax.add_artist(_at)
  15. return _at
  16.  
  17.  
  18. grid = AxesGrid(fig, 111, (3, 5), label_mode="1", share_all=True)
  19.  
  20. grid[0].set_autoscale_on(False)
  21.  
  22.  
  23. x1, y1 = 0.3, 0.3
  24. x2, y2 = 0.7, 0.7
  25.  
  26.  
  27. def demo_con_style(ax, connectionstyle, label=None):
  28.  
  29. if label is None:
  30. label = connectionstyle
  31.  
  32. x1, y1 = 0.3, 0.2
  33. x2, y2 = 0.8, 0.6
  34.  
  35. ax.plot([x1, x2], [y1, y2], ".")
  36. ax.annotate("",
  37. xy=(x1, y1), xycoords='data',
  38. xytext=(x2, y2), textcoords='data',
  39. arrowprops=dict(arrowstyle="->", #linestyle="dashed",
  40. color="0.5",
  41. shrinkA=5, shrinkB=5,
  42. patchA=None,
  43. patchB=None,
  44. connectionstyle=connectionstyle,
  45. ),
  46. )
  47.  
  48. add_at(ax, label, loc=2)
  49.  
  50. column = grid.axes_column[0]
  51.  
  52. demo_con_style(column[0], "angle3,angleA=90,angleB=0",
  53. label="angle3,\nangleA=90,\nangleB=0")
  54. demo_con_style(column[1], "angle3,angleA=0,angleB=90",
  55. label="angle3,\nangleA=0,\nangleB=90")
  56.  
  57.  
  58.  
  59. column = grid.axes_column[1]
  60.  
  61. demo_con_style(column[0], "arc3,rad=0.")
  62. demo_con_style(column[1], "arc3,rad=0.3")
  63. demo_con_style(column[2], "arc3,rad=-0.3")
  64.  
  65.  
  66.  
  67. column = grid.axes_column[2]
  68.  
  69. demo_con_style(column[0], "angle,angleA=-90,angleB=180,rad=0",
  70. label="angle,\nangleA=-90,\nangleB=180,\nrad=0")
  71. demo_con_style(column[1], "angle,angleA=-90,angleB=180,rad=5",
  72. label="angle,\nangleA=-90,\nangleB=180,\nrad=5")
  73. demo_con_style(column[2], "angle,angleA=-90,angleB=10,rad=5",
  74. label="angle,\nangleA=-90,\nangleB=10,\nrad=0")
  75.  
  76.  
  77. column = grid.axes_column[3]
  78.  
  79. demo_con_style(column[0], "arc,angleA=-90,angleB=0,armA=30,armB=30,rad=0",
  80. label="arc,\nangleA=-90,\nangleB=0,\narmA=30,\narmB=30,\nrad=0")
  81. demo_con_style(column[1], "arc,angleA=-90,angleB=0,armA=30,armB=30,rad=5",
  82. label="arc,\nangleA=-90,\nangleB=0,\narmA=30,\narmB=30,\nrad=5")
  83. demo_con_style(column[2], "arc,angleA=-90,angleB=0,armA=0,armB=40,rad=0",
  84. label="arc,\nangleA=-90,\nangleB=0,\narmA=0,\narmB=40,\nrad=0")
  85.  
  86.  
  87. column = grid.axes_column[4]
  88.  
  89. demo_con_style(column[0], "bar,fraction=0.3",
  90. label="bar,\nfraction=0.3")
  91. demo_con_style(column[1], "bar,fraction=-0.3",
  92. label="bar,\nfraction=-0.3")
  93. demo_con_style(column[2], "bar,angle=180,fraction=-0.2",
  94. label="bar,\nangle=180,\nfraction=-0.2")
  95.  
  96.  
  97. #demo_con_style(column[1], "arc3,rad=0.3")
  98. #demo_con_style(column[2], "arc3,rad=-0.3")
  99.  
  100.  
  101. grid[0].set_xlim(0, 1)
  102. grid[0].set_ylim(0, 1)
  103. grid.axes_llc.axis["bottom"].toggle(ticklabels=False)
  104. grid.axes_llc.axis["left"].toggle(ticklabels=False)
  105. fig.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95)
  106.  
  107. plt.draw()
  108. plt.show()

06.06 注释 - 图5

arrowstyle 参数控制小箭头的风格:

Name Attrs
- 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
<¦-¦> 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

In [6]:

  1. import matplotlib.patches as mpatches
  2. import matplotlib.pyplot as plt
  3.  
  4. styles = mpatches.ArrowStyle.get_styles()
  5.  
  6. ncol=2
  7. nrow = (len(styles)+1) // ncol
  8. figheight = (nrow+0.5)
  9. fig1 = plt.figure(1, (4.*ncol/1.5, figheight/1.5))
  10. fontsize = 0.2 * 70
  11.  
  12.  
  13. ax = fig1.add_axes([0, 0, 1, 1], frameon=False, aspect=1.)
  14.  
  15. ax.set_xlim(0, 4*ncol)
  16. ax.set_ylim(0, figheight)
  17.  
  18. def to_texstring(s):
  19. s = s.replace("<", r"$<$")
  20. s = s.replace(">", r"$>$")
  21. s = s.replace("|", r"$|$")
  22. return s
  23.  
  24. for i, (stylename, styleclass) in enumerate(sorted(styles.items())):
  25. x = 3.2 + (i//nrow)*4
  26. y = (figheight - 0.7 - i%nrow) # /figheight
  27. p = mpatches.Circle((x, y), 0.2, fc="w")
  28. ax.add_patch(p)
  29.  
  30. ax.annotate(to_texstring(stylename), (x, y),
  31. (x-1.2, y),
  32. #xycoords="figure fraction", textcoords="figure fraction",
  33. ha="right", va="center",
  34. size=fontsize,
  35. arrowprops=dict(arrowstyle=stylename,
  36. patchB=p,
  37. shrinkA=5,
  38. shrinkB=5,
  39. fc="w", ec="k",
  40. connectionstyle="arc3,rad=-0.05",
  41. ),
  42. bbox=dict(boxstyle="square", fc="w"))
  43.  
  44. ax.xaxis.set_visible(False)
  45. ax.yaxis.set_visible(False)
  46.  
  47.  
  48.  
  49. plt.draw()
  50. plt.show()

06.06 注释 - 图6

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/06-matplotlib/06.06-annotating-axes.ipynb