强基初中数学&学Python——第276课 数字和数学第三方模块Matplotlib之八:高级教程——转换器教程(1)

  与任何图形包一样,Matplotlib构建在转换器框架之上,可以轻松地在坐标系、用户数据坐标系、图表域(axes)坐标系、图表(figure)坐标系和显示坐标系之间变换。在95%的绘图中,你不需要考虑这一点,因为这是在引擎盖下(幕后)发生的,但当你的需求超出了自定义图表生成的极限时,了解这些对象会有所帮助,这样你就可以重用Matplotlib为你提供的现有的转换器,或者创建你自己的转换器(见Matplotlib.transforms)。下表总结了一些有用的坐标系,每个系统的描述以及用于从每个坐标系到显示坐标的变换对象。在“Transformation Object”列表中,ax是Axes实例,fig是Figure实例,subfigure是subfigure实例。

坐标系

描述

显示转换对象

"data"

(数据)

图表域(Axes)中的数据坐标系。

ax.transData

"axes"

(图表域)

图表域坐标系;(0,0)是左下角,(1,1)是右上角。

ax.transAxes

"subfigure"

(子图表)

子图表(Subfigure)坐标系;(0,0)是左下角,(1,1)是右上角。如果一张图表中无子图表,则与transFigure相同。

subfigure.transSubfigure

"figure"

(图表)

图表(Figure)坐标系;(0,0)是左下角,(1,1)是右上角。

fig.transFigure

"figure-inches"

(英寸图表)

以英寸为单位的图表坐标系;(0,0)是左下角,(width,height)是右上角。

fig.dpi_scale_trans

"xaxis","yaxis"

x轴,y轴)

混合坐标系,在一个方向上使用数据坐标(),在另一个方向使用图表域坐标(axes)。

ax.get_xaxis_transform(),ax.get_yaxis_transform()

"display"

(显示)

输出的本地坐标系;(0,0)是窗口的左下角,(宽度,高度)是以“显示单位”为单位的输出的右上角。

单位的确切解释取决于后端。例如,Agg是像素,svg/pdf是点数。

None 或 IdentityTransform()

  源坐标系和目标坐标系对转换(Transform)对象都是无知的,但是上表中提到的对象被构造为在其坐标系中获取输入,并将输入转换为显示坐标系。这就是为什么显示坐标系中“Transformation Object”列为None的原因——它已经在显示坐标中了。命名和目标约定有助于跟踪可用的“标准”坐标系和转换。

  变换还可以反转(通过Transform.inversed),以生成从输出坐标系返回到输入坐标系的变换。例如,ax.transData将数据坐标中的值转换为显示坐标,而ax.transData.inversed()是一个从显示坐标转换为数据坐标的matplotlib.transforms.Transform。这在处理用户界面中的事件时尤其有用,这些事件通常发生在显示空间中,但您想知道鼠标单击或按键在数据坐标系中的位置。

  请注意,如果图形的dpi或大小发生变化,指定艺术家在显示坐标中的位置可能会更改其相对位置。这可能会在打印或更改屏幕分辨率时造成混乱,因为对象可能会更改位置和大小。因此,对于放置在图表域(Axes)或图表(figure)中的艺术家来说,将他们的变换设置为非IdentityTransform()是很常见的;当使用add_artist将艺术家添加到图表域(Axes)时,默认情况下转换器为ax.transData,这样您就可以在数据坐标中工作和思考,并让Matplotlib负责显示转换。

数据坐标(Data coordinates)

  让我们从最常用的坐标系——数据坐标系开始。每当你向图表域(axes)添加数据时,Matplotlib都会更新数据限制,最常见的是使用set_xlim()set_ylim()方法更新。例如,在下图表中,数据限制在x轴上是0到10,在y轴上从-1到1。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import numpy as npimport matplotlib.pyplot as plt
x = np.arange(0, 10, 0.005)y = np.exp(-x/2.) * np.sin(2*np.pi*x)
fig, ax = plt.subplots()ax.plot(x, y)ax.set_xlim(0, 10)ax.set_ylim(-1, 1)
plt.show()

 

如果不使用set_xlim()set_ylim()

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import numpy as npimport matplotlib.pyplot as plt
x = np.arange(0, 10, 0.005)y = np.exp(-x/2.) * np.sin(2*np.pi*x)
fig, ax = plt.subplots()ax.plot(x, y)
plt.show()

 

  您可以使用ax.transData实例将数据转换为显示坐标系,可以是单个点,也可以是一系列点,如下所示:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

In [3]: type(ax.transData)Out[3]: matplotlib.transforms.CompositeGenericTransform
In [4]: ax.transData.transform((5,0))Out[4]: array([328.11278366, 216.7086957 ])
In [5]: ax.transData.transform([(5,0), (1, 2)])Out[5]:array([[328.11278366, 216.7086957 ],       [147.65892037, 643.48791699]])

  您可以使用inverted()方法创建一个从显示坐标到数据坐标的转换:

· 

· 

· 

· 

· 

· 

· 

In [6]: inv = ax.transData.inverted()
In [7]: type(inv)Out[7]: matplotlib.transforms.CompositeGenericTransform
In [8]: inv.transform((328.11278366, 216.7086957))Out[8]: array([ 5.00000000e+00, -1.11151088e-11])

  如果您与本教程一样地键入,如果您的窗口大小或dpi设置不同,则显示坐标的确切值可能会有所不同。同样,在下图表中,显示标记的点可能与ipython会话中的点不同,因为文档图形大小的默认值不同。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import numpy as npimport matplotlib.pyplot as plt
x = np.arange(0, 10, 0.005)y = np.exp(-x/2.) * np.sin(2*np.pi*x)
fig, ax = plt.subplots()ax.plot(x, y)ax.set_xlim(0, 10)ax.set_ylim(-1, 1)
xdata, ydata = 5, 0# This computing the transform now, if anything# (figure size, dpi, axes placement, data limits, scales..)# changes re-calling transform will get a different value.xdisplay, ydisplay = ax.transData.transform((xdata, ydata))
bbox = dict(boxstyle="round", fc="0.8")arrowprops = dict(    arrowstyle="->",    connectionstyle="angle,angleA=0,angleB=90,rad=10")
offset = 72ax.annotate('data = (%.1f, %.1f)' % (xdata, ydata),            (xdata, ydata), xytext=(-2*offset, offset), textcoords='offset points',            bbox=bbox, arrowprops=arrowprops)
disp = ax.annotate('display = (%.1f, %.1f)' % (xdisplay, ydisplay),                   (xdisplay, ydisplay), xytext=(0.5*offset, -offset),                   xycoords='figure pixels',                   textcoords='offset points',                   bbox=bbox, arrowprops=arrowprops)
plt.show()

 

警告

  如果您在GUI后端运行上面示例中的源代码,您可能还会发现数据和显示注释的两个箭头并不完全指向同一点。这是因为显示点是在图表显示之前计算的,并且GUI后端在创建图表时可能会稍微调整图表的大小。如果您自己调整图表的大小,差别会更明显。这就是为什么你很少想在显示空间工作的一个很好的原因,但你可以连接到“on_draw”事件(Event)来更新图表绘制上的图表坐标;请参阅事件处理和拾取(Event handling and picking)。

  当您更改图表域(axes)的x或y轴限制时,数据限制会更新,因此变换会产生一个新的显示点。请注意,当我们只更改ylim时,只有y显示坐标会更改,而当我们也更改xlim时,两者都会更改。稍后我们讨论Bbox时,将对此进行更多介绍。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

In [16]: ax.transData.transform((5, 0))Out[16]: array([328. , 237.6])
In [17]: ax.set_ylim(-1, 2)Out[17]: (-1.0, 2.0)
In [18]: ax.transData.transform((5, 0))Out[18]: array([328., 176.])
In [19]: ax.set_xlim(10, 20)Out[19]: (10.0, 20.0)
In [20]: ax.transData.transform((5, 0))Out[20]: array([-168.,  176.])

 

图表域坐标(Axes coordinates)

  除了数据坐标之外,图表域坐标可能是最常用的坐标系了。在这个坐标系中,原点(0,0)是图表域或子图表的左下角,(0.5, 0.5)是中心,(1.0, 1.0)是右上角。这个范围之外的点也可以被参考的,因此(-0.1, 1.1)位于图表域的左侧上方。在图表域中放置文本时,此坐标系非常有用,因为用户通常希望文本气泡(text bubble)位于同样的位置,例如图表域面板的左上角,而且在平移或缩放时相对位置能保持不变。这里有一个简单的例子,创建了四个面板,并将它们标记为“A”、“B”、“C”、“D”,就像你在期刊上经常看到的那样。

· 

· 

· 

· 

· 

· 

· 

· 

import matplotlib.pyplot as pltfig = plt.figure()for i, label in enumerate(('A', 'B', 'C', 'D')):    ax = fig.add_subplot(2, 2, i+1)    ax.text(0.05, 0.95, label, transform=ax.transAxes,            fontsize=16, fontweight='bold', va='top')
plt.show()

 

  用户也可以在图表域坐标系中制作线或面片,不过,根据我的经验,这比不上使用ax.transAxes放置文本有用。尽管如此,这里有一个直观的示例,它在数据空间中绘制一些随机点,并覆盖一个以图表域中心为中心的半透明圆(Circle),半径为图表域的四分之一——如果图表域不保留纵横比(请参见set_aspect()),这看起来就像一个椭圆。使用平移/缩放工具四处移动,或手动更改数据xlim和ylim,您将看到数据移动,但圆将保持固定,因为它不在数据坐标中,并且将始终保持在图表域的中心。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import numpy as npimport matplotlib.pyplot as pltimport matplotlib.patches as mpatches
fig, ax = plt.subplots()x, y = 10*np.random.rand(2, 1000)ax.plot(x, y, 'go', alpha=0.2)  # plot some data in data coordinates
circ = mpatches.Circle((0.5, 0.5), 0.25, transform=ax.transAxes,                       facecolor='blue', alpha=0.75)ax.add_patch(circ)plt.show()

 

 

matplotlib.transforms:https://matplotlib.org/stable/api/transformations.html#module-matplotlib.transforms

Axes:

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html#matplotlib.axes.Axes

Figure:

https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure

SubFigure:

https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.SubFigure

None:

https://docs.python.org/3/library/constants.html#None

IdentityTransform():

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.IdentityTransform

Transform:

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform

Transform.inverted:

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform.inverted

matplotlib.transforms.Transform:

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform

add_artist:

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.add_artist.html#matplotlib.axes.Axes.add_artist

set_xlim():

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_xlim.html#matplotlib.axes.Axes.set_xlim

set_ylim():

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_ylim.html#matplotlib.axes.Axes.set_ylim

inverted():

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform.inverted

Event:

https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.Event

Event handling and picking:

https://matplotlib.org/stable/users/explain/event_handling.html#event-handling-tutorial

Bbox:

https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Bbox

Circle:

https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Circle.html#matplotlib.patches.Circle

set_aspect():

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_aspect.html#matplotlib.axes.Axes.set_aspect