我正在尝试在matplotlib中使用旋转文本。遗憾的是,旋转似乎是在显示坐标系中,而不是在数据坐标系中。那就是:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes([0.15, 0.1, 0.8, 0.8])
t = np.arange(0.0, 1.0, 0.01)
line, = ax.plot(t, t, color='blue', lw=2)
ax.text (0.51,0.51,"test label", rotation=45)
plt.show()
将在数据坐标系中给出一条45度的线,但伴随的文本将在显示坐标系中45度。
即使在调整图形大小时,我也希望对齐文本和数据。
我看到here我可以转换旋转,但只有在不调整绘图大小的情况下,这才有效。
我试着写ax.text (0.51,0.51,"test label", transform=ax.transData, rotation=45)
,但它似乎是默认的,并没有帮助旋转
有没有办法在数据坐标系中进行旋转?
修改
我感兴趣的是能够在绘制之后调整图形的大小 - 这是因为我通常会绘制一些东西,然后在保存之前使用图形
答案 0 :(得分:4)
您可以使用以下类沿行创建文本。取一个角度(p
和pa
)作为输入,而不是角度。这两个点之间的连接定义了数据坐标中的角度。如果未提供pa
,则使用p
和xy
之间的连接线(文本坐标)。
然后,角度会自动更新,以使文本始终沿直线定向。甚至可以使用对数刻度。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.text as mtext
import matplotlib.transforms as mtransforms
class RotationAwareAnnotation(mtext.Annotation):
def __init__(self, s, xy, p, pa=None, ax=None, **kwargs):
self.ax = ax or plt.gca()
self.p = p
if not pa:
self.pa = xy
self.calc_angle_data()
kwargs.update(rotation_mode=kwargs.get("rotation_mode", "anchor"))
mtext.Annotation.__init__(self, s, xy, **kwargs)
self.set_transform(mtransforms.IdentityTransform())
if 'clip_on' in kwargs:
self.set_clip_path(self.ax.patch)
self.ax._add_text(self)
def calc_angle_data(self):
ang = np.arctan2(self.p[1]-self.pa[1], self.p[0]-self.pa[0])
self.angle_data = np.rad2deg(ang)
def _get_rotation(self):
return self.ax.transData.transform_angles(np.array((self.angle_data,)),
np.array([self.pa[0], self.pa[1]]).reshape((1, 2)))[0]
def _set_rotation(self, rotation):
pass
_rotation = property(_get_rotation, _set_rotation)
用法示例:
fig, ax = plt.subplots()
t = np.arange(0.0, 1.0, 0.01)
line, = ax.plot(t, t, color='blue', lw=2)
ra = RotationAwareAnnotation("test label", xy=(.5,.5), p=(.6,.6), ax=ax,
xytext=(2,-1), textcoords="offset points", va="top")
plt.show()
替代边缘情况
在某些情况下,沿垂直线或x和y单位(example here高度不同的文字,上述内容可能会失败。在这种情况下,以下更适合。它以屏幕坐标为单位计算角度,而不依赖于角度变换。
class RotationAwareAnnotation2(mtext.Annotation):
def __init__(self, s, xy, p, pa=None, ax=None, **kwargs):
self.ax = ax or plt.gca()
self.p = p
if not pa:
self.pa = xy
kwargs.update(rotation_mode=kwargs.get("rotation_mode", "anchor"))
mtext.Annotation.__init__(self, s, xy, **kwargs)
self.set_transform(mtransforms.IdentityTransform())
if 'clip_on' in kwargs:
self.set_clip_path(self.ax.patch)
self.ax._add_text(self)
def calc_angle(self):
p = self.ax.transData.transform_point(self.p)
pa = self.ax.transData.transform_point(self.pa)
ang = np.arctan2(p[1]-pa[1], p[0]-pa[0])
return np.rad2deg(ang)
def _get_rotation(self):
return self.calc_angle()
def _set_rotation(self, rotation):
pass
_rotation = property(_get_rotation, _set_rotation)
在通常情况下,两者都会产生相同的输出。我不确定第二课是否有任何缺点,所以我将两者都留在这里,选择您认为更合适的那个。
答案 1 :(得分:1)
好的,从与您的示例类似的代码开始:
%pylab inline
import numpy as np
fig = plt.figure()
ax = fig.add_axes([0.15, 0.1, 0.8, 0.8])
t = np.arange(0.0, 1.0, 0.01)
line, = ax.plot(t, t, color='blue', lw=2)
ax.text(0.51,0.51,"test label", rotation=45)
plt.show()
如您所示,文本标签未正确旋转以与线平行。
正如您所指出的那样,link解释了文本对象相对于线的旋转的坐标系中的解离。解决方案是将文本旋转角度从绘图转换为屏幕坐标系,让我们看看是否按照您的建议调整绘图大小会出现问题:
for fig_size in [(3.0,3.0),(9.0,3.0),(3.0,9.0)]: #use different sizes, in inches
fig2 = plt.figure(figsize=fig_size)
ax = fig2.add_axes([0.15, 0.1, 0.8, 0.8])
text_plot_location = np.array([0.51,0.51]) #I'm using the same location for plotting text as you did above
trans_angle = gca().transData.transform_angles(np.array((45,)),text_plot_location.reshape((1,2)))[0]
line, = ax.plot(t, t, color='blue', lw=2)
ax.text(0.51,0.51,"test label", rotation=trans_angle)
plt.show()
对我来说很好,即使调整大小。现在,如果你使线条更长并且轴线限制更长,那么当然你必须调整文本绘图以在图的新中心发生。