變換教程
譯者:飛龍
協(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)建自己的變換(見matplotlib.transforms
)亚脆。 下表總結(jié)了現(xiàn)有的坐標(biāo)系,你應(yīng)該在該坐標(biāo)系中使用的變換對(duì)象盲泛,以及該系統(tǒng)的描述濒持。 在『變換對(duì)象』一列中,ax
是Axes
實(shí)例寺滚,fig
是一個(gè)圖形實(shí)例柑营。
坐標(biāo)系 | 變換對(duì)象 | 描述 |
---|---|---|
數(shù)據(jù) | ax.transData |
用戶數(shù)據(jù)坐標(biāo)系,由xlim 和ylim 控制 |
軸域 | 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)系開始臼朗。 每當(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)你更改軸的x
或y
的范圍時(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枰诠潭ǖ奈恢茫ɡ绮┲S域窗格的左上角)放置文本氣泡险污,并且在平移或縮放時(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è)圓以軸域的中心為圓心削祈,半徑為軸域的四分之一翅溺。 - 如果你的軸域不保留高寬比(見set_aspect ()
),它將看起來(lái)像一個(gè)橢圓髓抑。 使用平移/縮放工具移動(dòng)咙崎,或手動(dòng)更改數(shù)據(jù)的xlim
和ylim
,你將看到數(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制(參見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)
其中xt
和yt
是變換的偏移饵较,scale_trans
是變換溉跃,在應(yīng)用偏移之前的變換期間縮放xt
和yt
。 一個(gè)典型的用例是告抄,將圖形的fig.dpi_scale_trans
變換用于scale_trans
參數(shù)撰茎,來(lái)在實(shí)現(xiàn)最終的偏移之前,首先將以點(diǎn)為單位的xt
和yt
縮放到顯示空間打洼。
DPI 和英寸偏移是常見的用例龄糊,我們擁有一個(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
芭毙,然后由dx
和dy
點(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ō)朝群,它將你的視圖xlim
和ylim
映射到軸域單位空間(然后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)xaxis
和yaxis
的Axis
實(shí)例的屬性晨抡。 例如,當(dāng)調(diào)用ax.set_xscale('log')
時(shí)则剃,xaxis
會(huì)將其縮放更新為matplotlib.scale.LogScale
實(shí)例耘柱。
對(duì)于不可分離的軸域,PolarAxes
棍现,還有一個(gè)要考慮的部分帆谍,投影變換。 matplotlib.projections.polar.PolarAxes
的transData
類似于典型的可分離 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è)投影示例西土,深入了解的最好方法是打開這些包的源代碼,看看如何自己制作它鞍盗,因?yàn)?matplotlib 支持可擴(kuò)展的軸域和投影需了。 Michael Droettboom 提供了一個(gè)創(chuàng)建一個(gè)錘投影軸域的很好的教程示例;請(qǐng)參閱 api 示例代碼:custom_projection_example.py
般甲。