教程 - 艺术家教程

优质
小牛编辑
131浏览
2023-12-01

原文:Artist tutorial

matplotlib API 有三个层级。 matplotlib.backend_bases.FigureCanvas是绘制图形的区域,matplotlib.backend_bases.Renderer是知道如何在ChartCanvas上绘制的对象,而matplotlib.artist.Artist是知道如何使用渲染器在画布上画图的对象。 FigureCanvasRenderer处理与用户界面工具包(如 wxPython)或 PostScript® 等绘图语言交互的所有细节,Artist处理所有高级结构,如表示和布局图形,文本和线条。用户通常要花费95%的时间来处理艺术家。

有两种类型的艺术家:基本类型和容器类型。基本类型表示我们想要绘制到画布上的标准图形对象:Line2DRectangleTextAxesImage等,容器是放置它们的位置(AxisAxesFigure)。标准用法是创建一个Figure实例,使用Figure创建一个或多个AxesSubplot实例,并使用Axes实例的辅助方法来创建基本类型。在下面的示例中,我们使用matplotlib.pyplot.figure()创建一个Figure实例,这是一个便捷的方法,用于实例化Figure实例并将它们与你的用户界面或绘图工具包FigureCanvas连接。正如我们将在下面讨论的,这不是必须的 - 你可以直接使用 PostScript,PDF,Gtk+ 或 wxPython FigureCanvas实例,直接实例化你的图形并连接它们 - 但是因为我们在这里关注艺术家 API,我们让pyplot为我们处理一些细节:

  1. import matplotlib.pyplot as plt
  2. fig = plt.figure()
  3. ax = fig.add_subplot(2,1,1) # two rows, one column, first plot

Axes可能是 matplotlib API 中最重要的类,你将在大多数时间使用它。 这是因为Axes是大多数对象所进入的绘图区域,Axes有许多特殊的辅助方法(plot()text()hist()imshow())来创建最常见的图形基本类型 Line2DTextRectangleImage)。 这些辅助方法将获取你的数据(例如 numpy 数组和字符串),并根据需要创建基本Artist实例(例如,Line2D),将它们添加到相关容器中,并在请求时绘制它们。 大多数人可能熟悉子图,这只是Axes的一个特例,它存在于Subplot实例的列网格的固定行上。 如果要在任意位置创建Axes,只需使用add_axes()方法,该方法接受[left, bottom, width, height]值的列表,以 0~1 的图形相对坐标为单位:

  1. fig2 = plt.figure()
  2. ax2 = fig2.add_axes([0.15, 0.1, 0.7, 0.3])

以我们的例子继续:

  1. import numpy as np
  2. t = np.arange(0.0, 1.0, 0.01)
  3. s = np.sin(2*np.pi*t)
  4. 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...调用返回的相同线条:

  1. In [101]: ax.lines[0]
  2. Out[101]: <matplotlib.lines.Line2D instance at 0x19a95710>
  3. In [102]: line
  4. Out[102]: <matplotlib.lines.Line2D instance at 0x19a95710>

如果你对ax.plot进行连续调用(并且保持状态为『on』,这是默认值),则将在列表中添加其他线条。 你可以稍后通过调用列表方法删除线条;任何一个方法都可以:

  1. del ax.lines[0]
  2. ax.lines.remove(line) # one or the other, not both!

轴域也拥有辅助方法,用于设置和装饰 x 和 y 轴的刻度、刻度标签和轴标签:

  1. xtext = ax.set_xlabel('my xdata') # returns a Text instance
  2. ytext = ax.set_ylabel('my ydata')

当你调用ax.set_xlabel时,它将信息传递给XAxisText实例,每个Axes实例都包含XAxisYAxis,它们处理刻度、刻度标签和轴标签的布局和绘制。

尝试创建下面的图形:

艺术家教程 - 图1

自定义你的对象

图中的每个元素都由一个 matplotlib 艺术家表示,每个元素都有一个扩展属性列表用于配置它的外观。 图形本身包含一个Rectangle,正好是图形的大小,你可以使用它来设置图形的背景颜色和透明度。 同样,每个Axes边框(在通常的 matplotlib 绘图中是标准的白底黑边)拥有一个Rectangle实例,用于确定轴域的颜色,透明度和其他属性,这些实例存储为成员变量Figure.patchAxes.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 透明度)

每个属性都使用一个老式的settergetter(是的,我们知道这会刺激 Python 爱好者,我们计划支持通过属性或 traits 直接访问,但它还没有完成)。 例如,要将当前alpha值变为一半:

  1. a = o.get_alpha()
  2. o.set_alpha(0.5*a)

如果你打算可以一次性设置一些属性,你也可以以关键字参数使用set方法,例如:

  1. o.set(alpha=0.5, zorder=2)

如果你在 Python 交互式 Shell 中工作,检查Artist属性的一种方便的方法是使用matplotlib.artist.getp()函数(在 pylab 中只需要getp()),它列出了属性及其值。 这适用于从Artist派生的类,例如FigureRectangle。 这里是上面提到的Figure的矩形属性:

  1. In [149]: matplotlib.artist.getp(fig.patch)
  2. alpha = 1.0
  3. animated = False
  4. antialiased or aa = True
  5. axes = None
  6. clip_box = None
  7. clip_on = False
  8. clip_path = None
  9. contains = None
  10. edgecolor or ec = w
  11. facecolor or fc = 0.75
  12. figure = Figure(8.125x6.125)
  13. fill = 1
  14. hatch = None
  15. height = 1
  16. label =
  17. linewidth or lw = 1.0
  18. picker = None
  19. transform = <Affine object at 0x134cca84>
  20. verts = ((0, 0), (0, 1), (1, 1), (1, 0))
  21. visible = True
  22. width = 1
  23. window_extent = <Bbox object at 0x134acbcc>
  24. x = 0
  25. y = 0
  26. zorder = 1

所有类的文档字符串也包含Artist属性,因此你可以查阅交互式『帮助』或 Artist模块,来获取给定对象的属性列表。

对象容器

现在我们知道如何检查和设置我们想要配置的给定对象的属性,现在我们需要如何获取该对象。 前面提到了两种对象:基本类型和容器类型。 基本类型通常是你想要配置的东西(Text实例的字体,Line2D的宽度),虽然容器也有一些属性 - 例如 Axes是一个容器艺术家,包含你的绘图中的许多基本类型,但它也有属性,比如xscale来控制xaxis是『线性』还是『对数』。 在本节中,我们将回顾各种容器对象存储你想要访问的艺术家的位置。

图形容器

顶层容器艺术家是matplotlib.figure.Figure,它包含图形中的所有内容。 图形的背景是一个Rectangle,存储在Figure.patch中。 当你向图形中添加子图(add_subplot())和轴域(add_axes())时,这些会附加到Figure.axes。 它们也由创建它们的方法返回:

  1. In [156]: fig = plt.figure()
  2. In [157]: ax1 = fig.add_subplot(211)
  3. In [158]: ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
  4. In [159]: ax1
  5. Out[159]: <matplotlib.axes.Subplot instance at 0xd54b26c>
  6. In [160]: print fig.axes
  7. [<matplotlib.axes.Subplot instance at 0xd54b26c>, <matplotlib.axes.Axes instance at 0xd3f0b2c>]

因为图形维护了『当前轴域』(见figure.gca和图figure.sca)的概念以支持 pylab/pyplot 状态机,所以不应直接从轴域列表中插入或删除轴域,而应使用add_subplot()add_axes()方法进行插入,并使用delaxes()方法进行删除。 然而,你可以自由地遍历轴域列表或索引,来访问要自定义的Axes实例。 下面是一个打开所有轴域网格的示例:

  1. for ax in fig.axes:
  2. ax.grid(True)

图形还拥有自己的文本,线条,补丁和图像,你可以使用它们直接添加基本类型。 图形的默认坐标系统简单地以像素(这通常不是你想要的)为单位,但你可以通过设置你添加到图中的艺术家的transform属性来控制它。

更有用的是『图形坐标系』,其中(0,0)是图的左下角,(1,1)是图的右上角,你可以通过将Artist的变换设置为fig.transFigure来获得:

  1. In [191]: fig = plt.figure()
  2. In [192]: l1 = matplotlib.lines.Line2D([0, 1], [0, 1],
  3. transform=fig.transFigure, figure=fig)
  4. In [193]: l2 = matplotlib.lines.Line2D([0, 1], [1, 0],
  5. transform=fig.transFigure, figure=fig)
  6. In [194]: fig.lines.extend([l1, l2])
  7. In [195]: fig.canvas.draw()

艺术家教程 - 图2

这里是图形可以包含的艺术家总结:

图形属性描述
axesAxes实例的列表(包括Subplot
patchRectangle背景
imagesFigureImages补丁的列表 - 用于原始像素显示
legends图形Legend实例的列表(不同于Axes.legends
lines图形Line2D实例的列表(很少使用,见Axes.lines
patches图形补丁列表(很少使用,见Axes.patches
texts图形Text实例的列表

轴域容器

matplotlib.axes.Axes是 matplotlib 宇宙的中心 - 它包含绝大多数在一个图形中使用的艺术家,并带有许多辅助方法来创建和添加这些艺术家本身,以及访问和自定义所包含的艺术家的辅助方法。 就像Figure那样,它包含一个Patch patch,它是一个用于笛卡尔坐标的Rectangle和一个用于极坐标的Cirecle; 这个补丁决定了绘图区域的形状,背景和边框:

  1. ax = fig.add_subplot(111)
  2. rect = ax.patch # a Rectangle instance
  3. rect.set_facecolor('green')

当调用绘图方法(例如通常是plot())并传递数组或值列表时,该方法将创建一个matplotlib.lines.Line2D()实例,将所有Line2D属性作为关键字参数传递, 将该线条添加到Axes.lines容器,并将其返回给你:

  1. In [213]: x, y = np.random.rand(2, 100)
  2. In [214]: line, = ax.plot(x, y, '-', color='blue', linewidth=2)

plot返回一个线条列表,因为你可以传入多个x,y偶对来绘制,我们将长度为一的列表的第一个元素解构到line变量中。 该线条已添加到Axes.lines列表中:

  1. In [229]: print ax.lines
  2. [<matplotlib.lines.Line2D instance at 0xd378b0c>]

与之类似,创建补丁的方法(如bar())会创建一个矩形列表,将补丁添加到Axes.patches列表中:

  1. In [233]: n, bins, rectangles = ax.hist(np.random.randn(1000), 50, facecolor='yellow')
  2. In [234]: rectangles
  3. Out[234]: <a list of 50 Patch objects>
  4. In [235]: print len(ax.patches)

你不应该直接将对象添加到Axes.linesAxes.patches列表,除非你确切知道你在做什么,因为Axes需要在它创建和添加对象做一些事情。 它设置Artistfigureaxes属性,以及默认Axes变换(除非设置了变换)。 它还检查Artist中包含的数据,来更新控制自动缩放的数据结构,以便可以调整视图限制来包含绘制的数据。 但是,你可以自己创建对象,并使用辅助方法(如add_line()add_patch())将它们直接添加到Axes。 这里是一个注释的交互式会话,说明正在发生什么:

  1. In [261]: fig = plt.figure()
  2. In [262]: ax = fig.add_subplot(111)
  3. # create a rectangle instance
  4. In [263]: rect = matplotlib.patches.Rectangle( (1,1), width=5, height=12)
  5. # by default the axes instance is None
  6. In [264]: print rect.get_axes()
  7. None
  8. # and the transformation instance is set to the "identity transform"
  9. In [265]: print rect.get_transform()
  10. <Affine object at 0x13695544>
  11. # now we add the Rectangle to the Axes
  12. In [266]: ax.add_patch(rect)
  13. # and notice that the ax.add_patch method has set the axes
  14. # instance
  15. In [267]: print rect.get_axes()
  16. Axes(0.125,0.1;0.775x0.8)
  17. # and the transformation has been set too
  18. In [268]: print rect.get_transform()
  19. <Affine object at 0x15009ca4>
  20. # the default axes transformation is ax.transData
  21. In [269]: print ax.transData
  22. <Affine object at 0x15009ca4>
  23. # notice that the xlimits of the Axes have not been changed
  24. In [270]: print ax.get_xlim()
  25. (0.0, 1.0)
  26. # but the data limits have been updated to encompass the rectangle
  27. In [271]: print ax.dataLim.bounds
  28. (1.0, 1.0, 5.0, 12.0)
  29. # we can manually invoke the auto-scaling machinery
  30. In [272]: ax.autoscale_view()
  31. # and now the xlim are updated to encompass the rectangle
  32. In [273]: print ax.get_xlim()
  33. (1.0, 6.0)
  34. # we have to manually force a figure draw
  35. In [274]: ax.figure.canvas.draw()

有非常多的Axes辅助方法用于创建基本艺术家并将它们添加到他们各自的容器中。 下表总结了他们的一部分,他们创造的Artist的种类,以及他们在哪里存储它们。

辅助方法艺术家容器
ax.annotate - 文本标注Annotateax.texts
ax.bar - 条形图Rectangleax.patches
ax.errorbar - 误差条形图Line2DRectangleax.linesax.patches
ax.fill - 共享区域Polygonax.patches
ax.hist - 直方图Rectangleax.patches
ax.imshow - 图像数据AxesImageax.images
ax.legend - 轴域图例Legendax.legends
ax.plot - xy 绘图Line2Dax.lines
ax.scatter - 散点图PolygonCollectionax.collections
ax.text - 文本Textax.texts

除了所有这些艺术家,Axes包含两个重要的艺术家容器:XAxisYAxis,它们处理刻度和标签的绘制。 它们被存储为实例变量xaxisyaxisXAxisYAxis容器将在下面详细介绍,但请注意,Axes包含许多辅助方法,它们会将调用转发给Axis实例,因此你通常不需要直接使用它们,除非你愿意。 例如,你可以使用Axes辅助程序方法设置XAxis刻度标签的字体大小:

  1. for label in ax.get_xticklabels():
  2. label.set_color('orange')

下面是轴域所包含的艺术家的总结

轴域属性描述
artistsArtist实例的列表
patch用于轴域背景的Rectangle实例
collectionsCollection实例的列表
imagesAxesImage的列表
legendsLegend实例的列表
linesLine2D实例的列表
patchesPatch实例的列表
textsText实例的列表
xaxismatplotlib.axis.XAxis实例
yaxismatplotlib.axis.YAxis实例

轴容器

matplotlib.axis.Axis实例处理刻度线,网格线,刻度标签和轴标签的绘制。你可以分别为y轴配置左和右刻度,为x轴分别配置上和下刻度。 Axis还存储在自动缩放,平移和缩放中使用的数据和视图间隔,以及LocatorFormatter实例,它们控制刻度位置以及它们表示为字符串的方式。

每个Axis对象都包含一个label属性(这是 pylab 在调用xlabel()ylabel()时修改的东西)以及主和次刻度的列表。刻度是XTickYTick实例,它包含渲染刻度和刻度标签的实际线条和文本基本类型。因为刻度是按需动态创建的(例如,当平移和缩放时),你应该通过访问器方法get_major_ticks()get_minor_ticks()访问主和次刻度的列表。虽然刻度包含所有下面要提及的基本类型,Axis方法包含访问器方法来返回刻度线,刻度标签,刻度位置等:

  1. In [285]: axis = ax.xaxis
  2. In [286]: axis.get_ticklocs()
  3. Out[286]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
  4. In [287]: axis.get_ticklabels()
  5. Out[287]: <a list of 10 Text major ticklabel objects>
  6. # note there are twice as many ticklines as labels because by
  7. # default there are tick lines at the top and bottom but only tick
  8. # labels below the xaxis; this can be customized
  9. In [288]: axis.get_ticklines()
  10. Out[288]: <a list of 20 Line2D ticklines objects>
  11. # by default you get the major ticks back
  12. In [291]: axis.get_ticklines()
  13. Out[291]: <a list of 20 Line2D ticklines objects>
  14. # but you can also ask for the minor ticks
  15. In [292]: axis.get_ticklines(minor=True)
  16. 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_ticklabelsText实例的列表 - 关键字`minor=TrueFalse`
get_ticklinesLine2D实例的列表 - 关键字`minor=TrueFalse`
get_ticklocsTick位置的列表 - 关键字`minor=TrueFalse`
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为主或次刻度打开或关闭网格

这里是个例子,出于美观不太推荐,它自定义了轴域和刻度属性。

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. # plt.figure creates a matplotlib.figure.Figure instance
  4. fig = plt.figure()
  5. rect = fig.patch # a rectangle instance
  6. rect.set_facecolor('lightgoldenrodyellow')
  7. ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4])
  8. rect = ax1.patch
  9. rect.set_facecolor('lightslategray')
  10. for label in ax1.xaxis.get_ticklabels():
  11. # label is a Text instance
  12. label.set_color('red')
  13. label.set_rotation(45)
  14. label.set_fontsize(16)
  15. for line in ax1.yaxis.get_ticklines():
  16. # line is a Line2D instance
  17. line.set_color('green')
  18. line.set_markersize(25)
  19. line.set_markeredgewidth(3)
  20. plt.show()

艺术家教程 - 图3

刻度容器

matplotlib.axis.Tick是我们从FigureAxes再到Axis再到Tick的最终的容器对象。Tick包含刻度和网格线的实例,以及上侧和下侧刻度的标签实例。 每个都可以直接作为Tick的属性访问。此外,也有用于确定上标签和刻度是否对应x轴,以及右标签和刻度是否对应y轴的布尔变量。

刻度属性描述
tick1lineLine2D实例
tick2lineLine2D实例
gridlineLine2D实例
label1Text实例
label2Text实例
gridOn确定是否绘制刻度线的布尔值
tick1On确定是否绘制主刻度线的布尔值
tick2On确定是否绘制次刻度线的布尔值
label1On确定是否绘制主刻度标签的布尔值
label2On确定是否绘制次刻度标签的布尔值

这里是个例子,使用美元符号设置右侧刻度,并在y轴右侧将它们设成绿色。

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. import matplotlib.ticker as ticker
  4. # Fixing random state for reproducibility
  5. np.random.seed(19680801)
  6. fig = plt.figure()
  7. ax = fig.add_subplot(111)
  8. ax.plot(100*np.random.rand(20))
  9. formatter = ticker.FormatStrFormatter('$%1.2f')
  10. ax.yaxis.set_major_formatter(formatter)
  11. for tick in ax.yaxis.get_major_ticks():
  12. tick.label1On = False
  13. tick.label2On = True
  14. tick.label2.set_color('green')
  15. plt.show()

艺术家教程 - 图4