matplotlib 调整大小后保持文本在数据坐标系中旋转?

jum4pzuy  于 2023-08-06  发布在  其他
关注(0)|答案(3)|浏览(147)

我正在尝试在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),但它似乎是默认值,对旋转没有帮助
是否有方法在数据坐标系中进行旋转?

编辑:

我感兴趣的是能够调整大小的数字后,我画它-这是因为我通常画的东西,然后玩的数字之前,保存它

lh80um4z

lh80um4z1#

您可以使用下面的类创建沿着的文本。而不是一个Angular ,它需要两个点(ppa)作为输入。这两个点之间的连接定义了数据坐标中的Angular 。如果没有给出pa,则使用pxy(文本坐标)之间的连接线。
然后,Angular 将自动更新,以使文本始终沿着直线定向。这甚至适用于对数标度。

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


的数据

边缘 shell 的替代方案

在某些情况下,如果文本沿着垂直线或在具有高度不相似的x和y单位(example here)的刻度上,上述方法可能会失败。在这种情况下,以下内容更合适。它计算屏幕坐标中的Angular ,而不是依赖于Angular 变换。

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)


对于通常的情况,这两种方法会产生相同的输出。我不确定第二类是否有任何缺点,所以我将两者都留在这里,选择你认为更合适的。

ubbxdtey

ubbxdtey2#

好的,从类似于你的例子的代码开始:

%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中已经解释了文本对象相对于直线旋转的坐标系分离。解决方案是将文本旋转Angular 从绘图转换到屏幕坐标系,让我们看看调整绘图大小是否会导致您建议的问题:

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

看起来不错,即使与调整大小。现在,如果您使线更长,轴限制更长,那么当然您必须调整文本绘制,使其出现在图的新中心。

gcuhipw9

gcuhipw93#

如果有人感兴趣,下面是我对ImportanceOfBeingErnest的代码所做的修改。此版本将采用偏移值并计算x,y坐标,以偏移垂直于其计算的Angular 的点的数量。

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, perpendicular_offset=None, ax=None, **kwargs):
        self.ax = ax or plt.gca()
        self.p = p
        self.xy = xy
        if not pa:
            self.pa = xy
        else:
            self.pa = pa
        self.calc_angle_data()
        self.perpendicular_offset = perpendicular_offset
        kwargs.update(rotation_mode=kwargs.get("rotation_mode", "anchor"))
        xytext = self.get_offset_coordinates()
        mtext.Annotation.__init__(self, s, xy, xytext=xytext, **kwargs)  # Pass xy and xytext
        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)

    def get_offset_coordinates(self):
        angle_rad = np.deg2rad(self.angle_data + 90)  # Calculate the perpendicular angle
        dx = self.perpendicular_offset * np.cos(angle_rad)  # Calculate the x offset
        dy = self.perpendicular_offset * np.sin(angle_rad)  # Calculate the y offset
        return (self.xy[0] + dx, self.xy[1] + dy)  # Return the offset coordinates

fig, ax = plt.subplots(figsize=(8, 8))
t = np.arange(0.0, 1.0, 0.01)
line, = ax.plot(t, t, color='blue', lw=2)
ax.set_yscale('log')

ra = RotationAwareAnnotation(
    "test label",
    xy=(.5, .5),
    p=(.8, .8),
    ax=ax,
    perpendicular_offset=10,
    textcoords="offset points", 
    va="bottom",
    ha="center"
)

plt.show()

字符串

相关问题