Matplotlib 中文用戶指南 3.7 變換教程

變換教程

原文:Transformations Tutorial

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

像任何圖形包一樣,matplotlib 建立在變換框架之上,以便在坐標(biāo)系,用戶數(shù)據(jù)坐標(biāo)系,軸域坐標(biāo)系,圖形坐標(biāo)系和顯示坐標(biāo)系之間輕易變換。 在 95 %的繪圖中,你不需要考慮這一點(diǎn),因?yàn)樗l(fā)生在背后,但隨著你接近自定義圖形生成的極限,它有助于理解這些對(duì)象,以便可以重用 matplotlib 提供給你的現(xiàn)有變換,或者創(chuàng)建自己的變換(見(jiàn)matplotlib.transforms)。 下表總結(jié)了現(xiàn)有的坐標(biāo)系,你應(yīng)該在該坐標(biāo)系中使用的變換對(duì)象,以及該系統(tǒng)的描述。 在『變換對(duì)象』一列中,axAxes實(shí)例,fig是一個(gè)圖形實(shí)例。

坐標(biāo)系 變換對(duì)象 描述
數(shù)據(jù) ax.transData 用戶數(shù)據(jù)坐標(biāo)系,由xlimylim控制
軸域 ax.transAxes 軸域坐標(biāo)系;(0,0)是軸域左下角,(1,1)是軸域右上角
圖形 fig.transFigure 圖形坐標(biāo)系;(0,0)是圖形左下角,(1,1)是圖形右上角
顯示 None 這是顯示器的像素坐標(biāo)系; (0,0)是顯示器的左下角,(width, height)是顯示器的右上角,以像素為單位。 或者,可以使用恒等變換(matplotlib.transforms.IdentityTransform())來(lái)代替None

上表中的所有變換對(duì)象都接受以其坐標(biāo)系為單位的輸入,并將輸入變換到顯示坐標(biāo)系。 這就是為什么顯示坐標(biāo)系沒(méi)有『變換對(duì)象』的原因 - 它已經(jīng)以顯示坐標(biāo)為單位了。 變換也知道如何反轉(zhuǎn)自身,從顯示返回自身的坐標(biāo)系。 這在處理來(lái)自用戶界面的事件(通常發(fā)生在顯示空間中),并且你想知道數(shù)據(jù)坐標(biāo)系中鼠標(biāo)點(diǎn)擊或按鍵按下的位置時(shí)特別有用。

數(shù)據(jù)坐標(biāo)

讓我們從最常用的坐標(biāo),數(shù)據(jù)坐標(biāo)系開(kāi)始。 每當(dāng)向軸域添加數(shù)據(jù)時(shí),matplotlib 會(huì)更新數(shù)據(jù)對(duì)象,set_xlim()set_ylim()方法最常用于更新。 例如,在下圖中,數(shù)據(jù)的范圍在x軸上為從 0 到 10,在y軸上為從 -1 到 1。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 10, 0.005)
y = np.exp(-x/2.) * np.sin(2*np.pi*x)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y)
ax.set_xlim(0, 10)
ax.set_ylim(-1, 1)

plt.show()

你可以使用ax.transData實(shí)例將數(shù)據(jù)變換為顯示坐標(biāo)系,無(wú)論是單個(gè)點(diǎn)或是一系列點(diǎn),如下所示:

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()方法創(chuàng)建一個(gè)變換,從顯示坐標(biāo)變換為數(shù)據(jù)坐標(biāo):

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.])

如果你一直關(guān)注本教程,如果你的窗口大小或 dpi 設(shè)置不同,顯示坐標(biāo)的確切值可能會(huì)有所不同。 同樣,在下面的圖形中,在 ipython 會(huì)話中,由顯示標(biāo)記的點(diǎn)可能并不相同,因?yàn)槲臋n圖形大小默認(rèn)值是不同的。

注意

如果在 GUI 后端中運(yùn)行上述示例中的源代碼,你還可能發(fā)現(xiàn)數(shù)據(jù)和顯示標(biāo)注的兩個(gè)箭頭不會(huì)指向完全相同的點(diǎn)。 這是因?yàn)轱@示點(diǎn)是在顯示圖形之前計(jì)算的,并且 GUI 后端可以在創(chuàng)建圖形時(shí)稍微調(diào)整圖形大小。 如果你自己調(diào)整圖的大小,效果更明顯。 這是你很少想要處理顯示空間的一個(gè)很好的原因,但是你可以連接到'on_draw'事件來(lái)更新圖上的圖坐標(biāo);請(qǐng)參閱事件處理和選擇。

當(dāng)你更改軸的xy的范圍時(shí),將更新數(shù)據(jù)范圍,以便變換生成新的顯示點(diǎn)。 注意,當(dāng)我們只是改變ylim,只有y顯示坐標(biāo)改變,當(dāng)我們改變xlim也同理。 我們?cè)谡務(wù)?Bbox 時(shí)會(huì)深入。

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])

軸域坐標(biāo)

在數(shù)據(jù)坐標(biāo)系之后,軸域可能是第二有用的坐標(biāo)系。 這里,點(diǎn)(0,0)是軸域或子圖的左下角,(0.5,0.5)是中心,(1.0,1.0)是右上角。 你還可以引用范圍之外的點(diǎn),因此(-0.1,1.1)位于軸的左上方。 此坐標(biāo)系在將文本放置在軸中時(shí)非常有用,因?yàn)槟阃ǔP枰诠潭ǖ奈恢茫ɡ?,軸域窗格的左上角)放置文本氣泡,并且在平移或縮放時(shí)保持該位置固定。 這里是一個(gè)簡(jiǎn)單的例子,創(chuàng)建四個(gè)面板,并將他們標(biāo)記為'A','B','C','D',你經(jīng)常在期刊上看到它們。

你也可以在軸坐標(biāo)系中創(chuàng)建線條或者補(bǔ)丁,但是以我的經(jīng)驗(yàn),這比使用ax.transAxes放置文本更不實(shí)用。 盡管如此,這里是一個(gè)愚蠢的例子,它在數(shù)據(jù)空間中繪制了一些隨機(jī)點(diǎn),并且覆蓋在一個(gè)半透明的圓上面,這個(gè)圓以軸域的中心為圓心,半徑為軸域的四分之一。 - 如果你的軸域不保留高寬比(見(jiàn)set_aspect ()),它將看起來(lái)像一個(gè)橢圓。 使用平移/縮放工具移動(dòng),或手動(dòng)更改數(shù)據(jù)的xlimylim,你將看到數(shù)據(jù)移動(dòng),但圓將保持固定,因?yàn)樗辉跀?shù)據(jù)坐標(biāo)中,并且將始終保持在軸域的中心 。

混合變換

在數(shù)據(jù)與軸域坐標(biāo)混合的混合坐標(biāo)空間中繪制是非常實(shí)用的,例如創(chuàng)建一個(gè)水平跨度,突出y數(shù)據(jù)的一些區(qū)域但橫跨x軸,而無(wú)論數(shù)據(jù)限制,平移或縮放級(jí)別等。實(shí)際上這些混合線條和跨度非常有用,我們已經(jīng)內(nèi)置了一些函數(shù)來(lái)使它們?nèi)菀桌L制(參見(jiàn)axhline()axvline(),axhspan(),axvspan()),但是為了教學(xué)目的,我們使用混合變換實(shí)現(xiàn)這里的水平跨度。 這個(gè)技巧只適用于可分離的變換,就像你在正常的笛卡爾坐標(biāo)系中看到的,但不能為不可分離的變換,如PolarTransform(極坐標(biāo)變換)。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.transforms as transforms

fig = plt.figure()
ax = fig.add_subplot(111)

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 = patches.Rectangle((1,0), width=1, height=1,
                         transform=trans, color='yellow',
                         alpha=0.5)

ax.add_patch(rect)

plt.show()

混合變換非常有用,其中x為數(shù)據(jù)坐標(biāo)而y為軸域坐標(biāo),我們擁有輔助方法來(lái)返回內(nèi)部使用的版本 mpl ,用于繪制ticks,ticklabels以及其他。方法是matplotlib.axes.Axes.get_xaxis_transform()matplotlib.axes.Axes.get_yaxis_transform()。 因此,在上面的示例中,blended_transform_factory()的調(diào)用可以替換為get_xaxis_transform

trans = ax.get_xaxis_transform()

使用偏移變換來(lái)創(chuàng)建陰影效果

變換的一個(gè)用法,是創(chuàng)建偏離另一變換的新變換,例如,放置一個(gè)對(duì)象,相對(duì)于另一對(duì)象有一些偏移。 通常,你希望物理尺寸上有一些移位,例如以點(diǎn)或英寸,而不是數(shù)據(jù)坐標(biāo)為單位,以便移位效果在不同的縮放級(jí)別和 dpi 設(shè)置下保持不變。

偏移的一個(gè)用途是創(chuàng)建一個(gè)陰影效果,其中你繪制一個(gè)與第一個(gè)相同的對(duì)象,剛好在它的右邊和下面,調(diào)整zorder來(lái)確保首先繪制陰影,然后繪制對(duì)象,陰影在它之上。 變換模塊具有輔助變換ScaledTranslation。 它可以這樣來(lái)實(shí)例化:

trans = ScaledTranslation(xt, yt, scale_trans)

其中xtyt是變換的偏移,scale_trans是變換,在應(yīng)用偏移之前的變換期間縮放xtyt。 一個(gè)典型的用例是,將圖形的fig.dpi_scale_trans變換用于scale_trans參數(shù),來(lái)在實(shí)現(xiàn)最終的偏移之前,首先將以點(diǎn)為單位的xtyt縮放到顯示空間。
DPI 和英寸偏移是常見(jiàn)的用例,我們擁有一個(gè)特殊的輔助函數(shù),來(lái)在matplotlib.transforms.offset_copy()中創(chuàng)建它,它返回一個(gè)帶有附加偏移的新變換。 但在下面的示例中,我們將自己創(chuàng)建偏移變換。 注意使用加法運(yùn)算符:

offset = transforms.ScaledTranslation(dx, dy,
         fig.dpi_scale_trans)
shadow_transform = ax.transData + offset

這里顯示了,可以使用加法運(yùn)算符將變換鏈起來(lái)。 該代碼表示:首先應(yīng)用數(shù)據(jù)變換ax.transData,然后由dxdy點(diǎn)翻譯數(shù)據(jù)。 在排版中,一個(gè)點(diǎn)是 1/72 英寸,通過(guò)以點(diǎn)為單位指定偏移,你的圖形看起來(lái)是一樣的,無(wú)論所保存的 dpi 分辨率。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.transforms as transforms

fig = plt.figure()
ax = fig.add_subplot(111)

# 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()

變換流水線

我們?cè)诒窘坛讨幸恢笔褂玫?code>ax.transData變換是三種不同變換的組合,它們構(gòu)成從數(shù)據(jù)到顯示坐標(biāo)的變換流水線。 Michael Droettboom 實(shí)現(xiàn)了變換框架,提供了一個(gè)干凈的 API,它隔離了在極坐標(biāo)和對(duì)數(shù)坐標(biāo)圖中發(fā)生的非線性投影和尺度,以及在平移和縮放時(shí)發(fā)生的線性仿射變換。 這里有一個(gè)效率問(wèn)題,因?yàn)槟憧梢云揭坪头糯竽愕妮S域,它會(huì)影響仿射變換,但你可能不需要計(jì)算潛在的昂貴的非線性比例或簡(jiǎn)單的導(dǎo)航事件的投影。 也可以將仿射變換矩陣相乘在一起,然后在一步之中將它們應(yīng)用于坐標(biāo)。 這對(duì)所有可能的變換不都是有效的。

這里是在ax.transData實(shí)例在基本可分離的Axes類中的定義方式。

self.transData = self.transScale + (self.transLimits + self.transAxes)

我們已經(jīng)在Axes坐標(biāo)中引入了上面的transAxes實(shí)例,它將軸或子圖邊界框的(0,0)(1,1)角映射到顯示空間,所以讓我們看看這兩個(gè)部分。

self.transLimits是從數(shù)據(jù)到軸域坐標(biāo)的變換; 也就是說(shuō),它將你的視圖xlimylim映射到軸域單位空間(然后transAxes將該單位空間用于顯示空間)。 我們可以在這里看到這一點(diǎn):

In [80]: ax = subplot(111)

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])

而且我們可以使用相同的反轉(zhuǎn)變換,從軸域單位坐標(biāo)變換回?cái)?shù)據(jù)坐標(biāo)。

In [90]: inv.transform((0.25, 0.25))
Out[90]: array([ 2.5, -0.5])

最后一個(gè)是self.transScale屬性,它負(fù)責(zé)數(shù)據(jù)的可選非線性縮放,例如對(duì)數(shù)軸域。 當(dāng)Axes初始化時(shí),這只是設(shè)置為恒等變換,因?yàn)榛镜?matplotlib 軸域具有線性縮放,但是當(dāng)你調(diào)用對(duì)數(shù)縮放函數(shù)如semilogx()或使用set_xscale顯式設(shè)置為對(duì)數(shù)時(shí),ax.transScale屬性為處理非線性投影而設(shè)置。 縮放變換是相應(yīng)xaxisyaxisAxis實(shí)例的屬性。 例如,當(dāng)調(diào)用ax.set_xscale('log')時(shí),xaxis會(huì)將其縮放更新為matplotlib.scale.LogScale實(shí)例。

對(duì)于不可分離的軸域,PolarAxes,還有一個(gè)要考慮的部分,投影變換。 matplotlib.projections.polar.PolarAxestransData類似于典型的可分離 matplotlib 軸域,帶有一個(gè)額外的部分,transProjection

self.transData = self.transScale + self.transProjection + \
    (self.transProjectionAffine + self.transAxes)

transProjection將來(lái)自空間的投影,例如,地圖數(shù)據(jù)的緯度和經(jīng)度,或極坐標(biāo)數(shù)據(jù)的半徑和極角,處理為可分離的笛卡爾坐標(biāo)系。 在matplotlib.projections包中有幾個(gè)投影示例,深入了解的最好方法是打開(kāi)這些包的源代碼,看看如何自己制作它,因?yàn)?matplotlib 支持可擴(kuò)展的軸域和投影。 Michael Droettboom 提供了一個(gè)創(chuàng)建一個(gè)錘投影軸域的很好的教程示例;請(qǐng)參閱 api 示例代碼:custom_projection_example.py。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容