备注
Go to the end 下载完整的示例代码.
转换教程#
与任何图形软件包一样,Matplotlib 建立在转换框架之上,以便轻松地在坐标系,用户空间数据坐标系,轴坐标系,图形坐标系和显示坐标系之间移动.在您 95% 的绘图中,您无需考虑这一点,因为它发生在幕后,但是当您突破自定义图形生成的限制时,了解这些对象会有所帮助,因此您可以重用 Matplotlib 提供给您的现有转换,或创建您自己的转换(请参阅 matplotlib.transforms ).下表总结了一些有用的坐标系,每个系统的描述,以及从每个坐标系到显示坐标系的转换对象.在"转换对象"列中, ax 是一个 Axes 实例, fig 是一个 Figure 实例, subfigure 是一个 SubFigure 实例.
坐标系 |
描述 |
从坐标系到显示的转换对象 |
|---|---|---|
"data" |
Axes 中数据的坐标系. |
|
"axes" |
|
|
"subfigure" |
|
|
"figure" |
|
|
"figure-inches" |
|
|
"xaxis", "yaxis" |
混合坐标系,在一个方向上使用数据坐标,在另一个方向上使用 axes 坐标. |
|
"display" |
输出的本地坐标系; (0, 0) 是窗口的左下角,(width, height) 是输出的右上角,单位为"显示单元". 单位的具体解释取决于后端. 例如,Agg 是像素,svg/pdf 是点. |
|
Transform 对象对于源和目标坐标系来说是幼稚的,但是上面表格中引用的对象被构造为在它们的坐标系中获取输入,并将输入转换为显示坐标系. 这就是为什么显示坐标系的"转换对象"列为 None -- 它已经在显示坐标中了. 命名和目标约定有助于跟踪可用的"标准"坐标系和转换.
转换也知道如何反转自身(通过 Transform.inverted )以生成从输出坐标系到输入坐标系的转换. 例如, ax.transData 将数据坐标中的值转换为显示坐标, ax.transData.inverted() 是一个 matplotlib.transforms.Transform ,它从显示坐标转换为数据坐标. 当处理来自用户界面的事件时,这特别有用,这些事件通常发生在显示空间中,并且您想知道鼠标单击或按键发生在您的数据坐标系中的什么位置.
请注意,如果 figure 的 dpi 或大小发生变化,在显示坐标中指定 Artist 的位置可能会改变它们的相对位置. 当打印或更改屏幕分辨率时,这可能会导致混淆,因为对象可能会改变位置和大小. 因此,放置在 Axes 或 figure 中的 artist 最常见的做法是将它们的 transform 设置为 IdentityTransform() 以外的其他值; 使用 add_artist 将 artist 添加到 Axes 时,默认的 transform 是 ax.transData ,以便您可以在数据坐标中工作和思考,并让 Matplotlib 处理到显示的转换.
数据坐标#
让我们从最常用的坐标--数据坐标系开始. 每当您将数据添加到 Axes 时,Matplotlib 都会更新数据限制,最常见的是使用 set_xlim() 和 set_ylim() 方法更新. 例如,在下面的 figure 中,x 轴上的数据限制从 0 延伸到 10,y 轴上的数据限制从 -1 延伸到 1.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as mpatches
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()

您可以使用 ax.transData 实例将数据转换为显示坐标系,可以是单个点或一系列点,如下所示:
In [14]: type(ax.transData)
Out[14]: <class 'matplotlib.transforms.CompositeGenericTransform'>
In [15]: ax.transData.transform((5, 0))
Out[15]: array([ 335.175, 247. ])
In [16]: ax.transData.transform([(5, 0), (1, 2)])
Out[16]:
array([[ 335.175, 247. ],
[ 132.435, 642.2 ]])
您可以使用 inverted() 方法创建一个转换,它将您从显示坐标带到数据坐标:
In [41]: inv = ax.transData.inverted()
In [42]: type(inv)
Out[42]: <class 'matplotlib.transforms.CompositeGenericTransform'>
In [43]: inv.transform((335.175, 247.))
Out[43]: array([ 5., 0.])
如果您正在跟着本教程输入,如果您的窗口大小或 dpi 设置不同,则显示坐标的确切值可能会有所不同. 同样,在下面的 figure 中,标记的显示点可能与 ipython 会话中的不同,因为文档 figure 大小的默认值不同.
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 = 72
ax.annotate(f'data = ({xdata:.1f}, {ydata:.1f})',
(xdata, ydata), xytext=(-2*offset, offset), textcoords='offset points',
bbox=bbox, arrowprops=arrowprops)
disp = ax.annotate(f'display = ({xdisplay:.1f}, {ydisplay:.1f})',
(xdisplay, ydisplay), xytext=(0.5*offset, -offset),
xycoords='figure pixels',
textcoords='offset points',
bbox=bbox, arrowprops=arrowprops)
plt.show()

警告
如果在GUI后端运行上述示例中的源代码,您可能还会发现数据和显示注释的两个箭头没有指向完全相同的点.这是因为显示点是在显示图形之前计算的,并且GUI后端在创建图形时可能会稍微调整图形的大小.如果您自己调整图形大小,则效果会更明显.这是您很少希望在显示空间中工作的一个很好的理由,但是您可以连接到 'on_draw' Event 来更新图形绘制时的图形坐标;请参阅 事件处理和拾取 .
当您更改轴的 x 或 y 限制时,数据限制会更新,因此转换会产生新的显示点.请注意,当我们只更改 ylim 时,只会更改 y 显示坐标,而当我们同时更改 xlim 时,两者都会更改.稍后当我们讨论 Bbox 时,会详细介绍这一点.
In [54]: ax.transData.transform((5, 0))
Out[54]: array([ 335.175, 247. ])
In [55]: ax.set_ylim(-1, 2)
Out[55]: (-1, 2)
In [56]: ax.transData.transform((5, 0))
Out[56]: array([ 335.175 , 181.13333333])
In [57]: ax.set_xlim(10, 20)
Out[57]: (10, 20)
In [58]: ax.transData.transform((5, 0))
Out[58]: array([-171.675 , 181.13333333])
轴坐标#
在数据坐标系之后,轴可能是第二个最有用的坐标系.这里的点 (0, 0) 是您的 Axes 或子图的左下角,(0.5, 0.5) 是中心,(1.0, 1.0) 是右上角.您还可以引用范围之外的点,因此 (-0.1, 1.1) 位于您的 Axes 的左侧和上方.当您在 Axes 中放置文本时,此坐标系非常有用,因为您通常希望在固定位置(例如,Axes 窗格的左上角)放置文本气泡,并且在您平移或缩放时该位置保持固定.这是一个简单的示例,它创建四个面板并将它们标记为"A","B","C","D",这在期刊中经常见到.在 标记子图 中介绍了一种更复杂的标记方法.
fig = 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 ,其中心位于 Axes 的中间,半径为 Axes 的四分之一--如果您的 Axes 不保留纵横比(参见 set_aspect() ),这看起来像一个椭圆.使用平移/缩放工具四处移动,或手动更改数据 xlim 和 ylim,您将看到数据移动,但圆圈将保持固定,因为它不在数据坐标中,并且将始终保持在 Axes 的中心.
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()

混合变换#
在混合坐标空间中绘图(混合轴坐标和数据坐标)非常有用,例如创建一个水平跨度,突出显示 y 数据的某个区域,但跨越 x 轴,而不管数据限制,平移或缩放级别等.事实上,这些混合线和跨度非常有用,我们已经内置函数来简化它们的绘制(参见 axhline() , axvline() , axhspan() , axvspan() ),但出于教学目的,我们将在此处使用混合变换来实现水平跨度.此技巧仅适用于可分离变换,就像您在普通笛卡尔坐标系中看到的那样,但不适用于不可分离变换,如 PolarTransform .
import matplotlib.transforms as transforms
fig, ax = plt.subplots()
x = np.random.randn(1000)
ax.hist(x, 30)
ax.set_title(r'$\sigma=1 \/ \dots \/ \sigma=2$', fontsize=16)
# the x coords of this transformation are data, and the y coord are axes
trans = transforms.blended_transform_factory(
ax.transData, ax.transAxes)
# highlight the 1..2 stddev region with a span.
# We want x to be in data coordinates and y to span from 0..1 in axes coords.
rect = mpatches.Rectangle((1, 0), width=1, height=1, transform=trans,
color='yellow', alpha=0.5)
ax.add_patch(rect)
plt.show()

备注
x 在数据坐标中,y 在轴坐标中的混合变换非常有用,因此我们有辅助方法来返回 Matplotlib 内部用于绘制刻度,刻度标签等的版本.这些方法是 matplotlib.axes.Axes.get_xaxis_transform() 和 matplotlib.axes.Axes.get_yaxis_transform() .因此,在上面的示例中,对 blended_transform_factory() 的调用可以替换为 get_xaxis_transform
trans = ax.get_xaxis_transform()
在物理坐标中绘图#
有时我们希望对象在图上具有一定的物理尺寸.这里我们绘制与上面相同的圆,但在物理坐标中绘制.如果以交互方式完成,您可以看到更改图形的大小不会改变圆圈与左下角的偏移,不会改变其大小,并且无论 Axes 的纵横比如何,圆圈都保持不变.
fig, ax = plt.subplots(figsize=(5, 4))
x, y = 10*np.random.rand(2, 1000)
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
# add a circle in fixed-coordinates
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
facecolor='blue', alpha=0.75)
ax.add_patch(circ)
plt.show()

如果我们改变图形大小,圆的绝对位置不会改变,并且会被裁剪.
fig, ax = plt.subplots(figsize=(7, 2))
x, y = 10*np.random.rand(2, 1000)
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
# add a circle in fixed-coordinates
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
facecolor='blue', alpha=0.75)
ax.add_patch(circ)
plt.show()

另一个用途是在坐标轴上的数据点周围放置具有设定物理尺寸的补丁.这里我们将两个变换加在一起.第一个设置椭圆应该有多大的比例,第二个设置它的位置.然后将椭圆放置在原点,然后我们使用辅助变换 ScaledTranslation 将其移动到 ax.transData 坐标系中的正确位置.这个辅助变换的实例化方式如下:
trans = ScaledTranslation(xt, yt, scale_trans)
其中 xt 和 yt 是平移偏移量,而 scale_trans 是一个变换,它在应用偏移量之前,在变换时缩放 xt 和 yt.
请注意下面变换中加号运算符的使用.此代码表示:首先应用比例变换 fig.dpi_scale_trans 使椭圆具有适当的大小,但仍以 (0, 0) 为中心,然后将数据平移到数据空间中的 xdata[0] 和 ydata[0] .
在交互式使用中,即使通过缩放更改了轴的限制,椭圆的大小也保持不变.
fig, ax = plt.subplots()
xdata, ydata = (0.2, 0.7), (0.5, 0.5)
ax.plot(xdata, ydata, "o")
ax.set_xlim((0, 1))
trans = (fig.dpi_scale_trans +
transforms.ScaledTranslation(xdata[0], ydata[0], ax.transData))
# plot an ellipse around the point that is 150 x 130 points in diameter...
circle = mpatches.Ellipse((0, 0), 150/72, 130/72, angle=40,
fill=None, transform=trans)
ax.add_patch(circle)
plt.show()

备注
变换的顺序很重要.这里,椭圆首先在显示空间中被赋予正确的尺寸,然后在数据空间中移动到正确的位置.如果我们首先进行 ScaledTranslation ,那么 xdata[0] 和 ydata[0] 将首先被转换为显示坐标(在 200-dpi 的显示器上为 [ 358.4 475.2] ),然后这些坐标将被 fig.dpi_scale_trans 缩放,从而将椭圆的中心推离屏幕(即 [ 71680. 95040.] ).
使用偏移变换创建阴影效果#
ScaledTranslation 的另一个用途是创建一个新的变换,该变换与另一个变换偏移,例如,将一个对象相对于另一个对象稍微移动一点.通常,您希望偏移量在某些物理尺寸中,例如点或英寸,而不是在数据坐标中,以便偏移效果在不同的缩放级别和 dpi 设置下是恒定的.
偏移量的一个用途是创建阴影效果,您可以在第一个对象的右侧和下方绘制一个与第一个对象相同的对象,调整 zorder 以确保首先绘制阴影,然后在阴影上方绘制对象.
这里我们按照与上面 ScaledTranslation 的使用相反的顺序应用变换.首先在数据坐标 ( ax.transData ) 中进行绘制,然后使用 fig.dpi_scale_trans 将其移动 dx 和 dy 个点.(在排版中, point 是 1/72 英寸,并且通过以点为单位指定偏移量,无论保存的分辨率如何,您的图形看起来都将相同.)
fig, ax = plt.subplots()
# make a simple sine wave
x = np.arange(0., 2., 0.01)
y = np.sin(2*np.pi*x)
line, = ax.plot(x, y, lw=3, color='blue')
# shift the object over 2 points, and down 2 points
dx, dy = 2/72., -2/72.
offset = transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
shadow_transform = ax.transData + offset
# now plot the same data with our offset transform;
# use the zorder to make sure we are below the line
ax.plot(x, y, lw=3, color='gray',
transform=shadow_transform,
zorder=0.5*line.get_zorder())
ax.set_title('creating a shadow effect with an offset transform')
plt.show()

备注
dpi 和英寸偏移是一个足够常见的用例,我们有一个特殊的辅助函数来在 matplotlib.transforms.offset_copy() 中创建它,它返回一个带有附加偏移量的新变换.所以上面我们可以这样做:
shadow_transform = transforms.offset_copy(ax.transData,
fig, dx, dy, units='inches')
变换管道#
我们一直在本教程中使用的 ax.transData 变换是由三个不同变换组成的复合体,这些变换构成了从数据到显示坐标的变换管道.Michael Droettboom 实现了变换框架,注意提供了一个清晰的 API,该 API 将极坐标和对数图中发生的非线性投影和比例与平移和缩放时发生的线性仿射变换分离开来.这里有一个效率,因为您可以平移和缩放您的坐标轴,这会影响仿射变换,但您可能不需要在简单的导航事件上计算可能很昂贵的非线性比例或投影.也可以将仿射变换矩阵相乘,然后一步将它们应用于坐标.并非所有可能的变换都是如此.
以下是在基本可分离轴 Axes 类中定义 ax.transData 实例的方式:
self.transData = self.transScale + (self.transLimits + self.transAxes)
我们在上面的 轴坐标 中介绍了 transAxes 实例,它将坐标轴或子图边界框的 (0, 0), (1, 1) 角映射到显示空间,所以让我们看看其他两个部分.
self.transLimits 是一个转换,它将数据坐标转换到轴坐标;也就是说,它将你的视图 xlim 和 ylim 映射到 Axes 的单位空间(然后 transAxes 将该单位空间映射到显示空间).我们可以在这里看到它的实际应用
In [80]: ax = plt.subplot()
In [81]: ax.set_xlim(0, 10)
Out[81]: (0, 10)
In [82]: ax.set_ylim(-1, 1)
Out[82]: (-1, 1)
In [84]: ax.transLimits.transform((0, -1))
Out[84]: array([ 0., 0.])
In [85]: ax.transLimits.transform((10, -1))
Out[85]: array([ 1., 0.])
In [86]: ax.transLimits.transform((10, 1))
Out[86]: array([ 1., 1.])
In [87]: ax.transLimits.transform((5, 0))
Out[87]: array([ 0.5, 0.5])
我们可以使用相同的反向转换,从单位轴坐标返回到数据坐标.
In [90]: inv.transform((0.25, 0.25))
Out[90]: array([ 2.5, -0.5])
最后一部分是 self.transScale 属性,它负责数据的可选非线性缩放,例如,对于对数轴.当最初设置一个 Axes 时,它只是被设置为恒等变换,因为基本的 Matplotlib 轴具有线性刻度,但是当你调用像 semilogx() 这样的对数缩放函数,或者使用 set_xscale() 显式地将刻度设置为对数时, ax.transScale 属性被设置为处理非线性投影.刻度变换是各自的 xaxis 和 yaxis Axis 实例的属性.例如,当你调用 ax.set_xscale('log') 时,xaxis 将其刻度更新为 matplotlib.scale.LogScale 实例.
对于不可分离轴 PolarAxes,还需要考虑一个部分,即投影变换. transData matplotlib.projections.polar.PolarAxes 类似于典型的可分离 matplotlib Axes,但增加了一个部分 transProjection
self.transData = (
self.transScale + self.transShift + self.transProjection +
(self.transProjectionAffine + self.transWedge + self.transAxes))
transProjection 处理从空间(例如,地图数据的纬度和经度,或者极坐标数据的半径和 theta)到可分离笛卡尔坐标系的投影. matplotlib.projections 包中有一些投影示例,了解更多信息的最佳方法是打开这些包的源代码,看看如何创建你自己的投影,因为 Matplotlib 支持可扩展的轴和投影.Michael Droettboom 提供了一个创建 Hammer 投影轴的不错的教程示例;请参阅 自定义投影 .
脚本的总运行时间:(0 分钟 1.856 秒)