笔记
单击此处 下载完整的示例代码
转换教程#
与任何图形包一样,Matplotlib 构建在转换框架之上,可以轻松地在坐标系、用户空间数据
坐标系、轴坐标系、图形坐标系和显示坐标系之间移动。在 95% 的绘图中,您不需要考虑这一点,因为它发生在幕后,但是当您突破自定义图形生成的限制时,有助于了解这些对象,以便您可以重用现有的Matplotlib 为您提供的转换,或创建您自己的转换(参见 参考资料matplotlib.transforms
)。下表总结了一些有用的坐标系,每个系统的描述,以及从每个坐标系到显示坐标。在“转换对象”列中,ax
是一个
Axes
实例,fig
是一个
Figure
实例,subfigure
是一个
SubFigure
实例。
坐标系 |
描述 |
从系统到显示的转换对象 |
---|---|---|
“数据” |
Axes 中数据的坐标系。 |
|
“轴” |
的坐标系
|
|
“子图” |
的坐标系
|
|
“数字” |
的坐标系
|
|
“数字英寸” |
|
|
“x轴”、“y轴” |
混合坐标系,在一个方向上使用数据坐标,在另一个方向上使用轴坐标。 |
|
“展示” |
输出的本机坐标系;(0, 0) 是窗口的左下角, (width, height) 是“显示单位”输出的右上角。 单位的确切解释取决于后端。例如,Agg 是像素,svg/pdf 是点。 |
这些Transform
对象对于源坐标系和目标坐标系是幼稚的,但是上表中提到的对象被构造为在其坐标系中接受输入,并将输入转换为显示坐标系。这就是为什么显示
坐标系具有None
“变换对象”列的原因——它已经在显示坐标中。命名和目的地约定有助于跟踪可用的“标准”坐标系和变换。
变换还知道如何反转自身(通过
Transform.inverted
)以生成从输出坐标系返回到输入坐标系的变换。例如,ax.transData
将数据坐标中的值转换为显示坐标,并且
ax.transData.inversed()
是matplotlib.transforms.Transform
从显示坐标到数据坐标的 a。这在处理来自用户界面的事件时特别有用,这些事件通常发生在显示空间中,并且您想知道鼠标单击或按键在数据坐标系中发生的位置。
请注意,如果图形的或大小发生变化,指定艺术家在显示坐标中的位置可能会改变它们的相对位置。dpi
这可能会在打印或更改屏幕分辨率时造成混淆,因为对象可以更改位置和大小。因此,放置在 Axes 或图形中的艺术家最常见的是将其变换
设置为IdentityTransform()
; 将艺术家添加到 Axes 时,默认使用add_artist
的是转换,
ax.transData
以便您可以在数据坐标中工作和思考,并让 Matplotlib 处理要显示的转换。
数据坐标#
让我们从最常用的坐标开始,数据坐标系。每当您向坐标区添加数据时,Matplotlib 都会更新数据限制,最常见的是使用set_xlim()
和
set_ylim()
方法进行更新。例如,在下图中,数据限制在 x 轴上从 0 延伸到 10,在 y 轴上从 -1 延伸到 1。
您可以使用该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 设置,显示坐标的确切值可能会有所不同。同样,在下图中,显示标记点可能与 ipython 会话中的不同,因为文档图形大小默认值不同。
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('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
更新
图形绘制上的图形坐标;请参阅事件处理和挑选。
当您更改坐标区的 x 或 y 范围时,数据范围会更新,因此转换会产生新的显示点。请注意,当我们只更改 ylim 时,只有 y-display 坐标被更改,而当我们也更改 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) 是轴或子图的左下角, (0.5, 0.5) 是中心, (1.0, 1.0) 是右上角。您还可以参考范围之外的点,因此 (-0.1, 1.1) 位于轴的左侧和上方。当在坐标区中放置文本时,此坐标系非常有用,因为您通常希望文本气泡位于固定位置,例如坐标区窗格的左上角,并在平移或缩放时保持该位置固定。这是一个简单的示例,它创建了四个面板并将它们标记为“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
的轴中心,半径为轴的四分之一——如果您的轴不保持纵横比(请参阅set_aspect()
) ,这看起来像一个椭圆。使用平移/缩放工具四处移动,或手动更改数据 xlim 和 ylim,您会看到数据移动,但圆圈将保持固定,因为它不在数据
坐标中,并且始终保持在轴的中心.
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()
在物理坐标中绘图#
有时我们希望对象在绘图上具有一定的物理尺寸。在这里,我们绘制与上面相同的圆,但在物理坐标中。如果以交互方式完成,您可以看到更改图形的大小不会更改圆从左下角的偏移量,也不会更改其大小,并且无论轴的纵横比如何,圆仍然是一个圆。
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
) 中绘制,然后
使用dx
和dy
点移动fig.dpi_scale_trans
。(在排版中,一个点是 1/72 英寸,通过以点为单位指定偏移量,无论保存的 dpi 分辨率如何,您的图形看起来都一样。)
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=fig, dx, dy, units='inches')
转换管道#
ax.transData
我们在本教程中使用的转换是三种不同转换的组合,它们构成了从数据到显示的转换管道
坐标。Michael Droettboom 实现了转换框架,注意提供一个干净的 API,将发生在极坐标和对数图中的非线性投影和比例与平移和缩放时发生的线性仿射转换分开。这里有一个效率,因为您可以平移和缩放影响仿射变换的轴,但您可能不需要计算潜在昂贵的非线性比例或简单导航事件的投影。也可以将仿射变换矩阵相乘,然后一步将它们应用于坐标。并非所有可能的转换都是如此。
以下是ax.transData
在基本可分离轴类中定义实例的方式Axes
:
self.transData = self.transScale + (self.transLimits + self.transAxes)
我们已经在Axes 坐标transAxes
中介绍了上面的实例
,它将坐标轴或子图边界框的 (0, 0)、(1, 1) 角映射到显示空间,所以让我们看看另外两个部分。
self.transLimits
是将您从
数据带到坐标轴坐标的转换;即,它将您的视图 xlim 和 ylim 映射到轴的单位空间(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 轴,还有一个附加部分
transProjection
:
self.transData = self.transScale + self.transProjection + \
(self.transProjectionAffine + self.transAxes)
transProjection
处理从空间到可分离笛卡尔坐标系的投影,例如地图数据的纬度和经度,或极坐标数据的半径和θ。包中有几个投影示例,matplotlib.projections
了解更多信息的最佳方法是打开这些包的源代码并查看如何制作自己的包,因为 Matplotlib 支持可扩展的轴和投影。Michael Droettboom 提供了一个很好的创建 Hammer 投影轴的教程示例;请参阅
自定义投影。
脚本总运行时间:(0分3.353秒)