使用Matplotlib的AuxTransformBox旋转图形元素出现异常问题
前置说明
我完全接受能实现相同功能的其他方法——原本认为这种方式最直接。先导入以下模块:
import matplotlib import matplotlib.artist import matplotlib.lines import matplotlib.pyplot import matplotlib.patches import matplotlib.patheffects import matplotlib.offsetbox import matplotlib.transforms
问题1:嵌套AuxTransformBox失效
我想创建一条2英寸长、旋转45度的直线:使用单个AuxTransformBox(同时叠加缩放与旋转变换)的方式可运行,但直线位置偏移明显;但嵌套两个AuxTransformBox(分别实现缩放和旋转)的方式却失效:直线仅完成缩放未旋转,且出现在图底部边缘。开启AnchoredOffsetBox边框后发现,AOB位置正确,但内部直线未正确绘制。为何嵌套AuxTransformBox无法逐步应用变换?
问题2:旋转其他OffsetBox失效
我尝试将AuxTransformBox应用于混合文本与图形元素的VPacker等OffsetBox,但同样失效。未添加旋转AuxTransformBox时,VPacker可正常垂直堆叠文本区域和直线;但将VPacker放入旋转的AuxTransformBox后,复合元素出现在屏幕底部,未遵循AnchoredOffsetBox的居中位置。这是为何?
问题解析与解决方案
核心原理
AuxTransformBox的变换是作用于内部元素的局部坐标系原点,而非元素的整体中心或AnchoredOffsetBox的定位点。嵌套或直接旋转时,若未调整变换原点,元素会基于局部(0,0)点发生偏移,导致显示位置不符合预期。
问题1:嵌套AuxTransformBox失效的原因与解决
失效原因
嵌套AuxTransformBox时,第二个盒子的旋转变换会基于第一个盒子变换后的局部坐标系原点(即原始(0,0)经过缩放后的位置),而非直线的中心。直线的坐标是[0,2]到[0,0],旋转后会以该原点为中心转动,最终偏移到图底部;同时嵌套的变换不会自动继承元素位置,导致旋转效果未正确叠加。
解决方案:单盒子组合变换+调整原点
直接在单个AuxTransformBox中组合缩放与旋转变换,并将旋转原点设为直线的中心,修正位置偏移:
fig, ax = matplotlib.pyplot.subplots() # 创建2英寸长的直线 line = matplotlib.lines.Line2D([0, 2], [0, 0], color='red') # 组合变换:以直线中点(1,0)为原点旋转45度 transform = matplotlib.transforms.Affine2D().rotate_deg_around(1, 0, 45) aux_box = matplotlib.offsetbox.AuxTransformBox(transform) aux_box.add_artist(line) # 定位到图中心 anchored_box = matplotlib.offsetbox.AnchoredOffsetbox(loc='center', child=aux_box, frameon=True) ax.add_artist(anchored_box) matplotlib.pyplot.xlim(-1, 3) matplotlib.pyplot.ylim(-1, 3) matplotlib.pyplot.show()
问题2:旋转VPacker失效的原因与解决
失效原因
VPacker作为复合OffsetBox,其边界框是基于内部元素的布局计算的。放入AuxTransformBox后,旋转变换默认以局部坐标系原点(0,0)为中心,而VPacker的中心并不在该点,旋转后整体就会偏离AnchoredOffsetBox的居中位置,跑到图底部。
解决方案:变换前平移到原点,旋转后平移回中心
先计算VPacker的边界框中心,通过平移变换将中心移到局部原点,旋转后再平移回原位置,保证旋转后整体仍处于AnchoredOffsetBox的定位点:
fig, ax = matplotlib.pyplot.subplots() # 创建文本和直线元素 text_box = matplotlib.offsetbox.TextArea("测试文本") line = matplotlib.lines.Line2D([0, 1], [0, 0], color='blue') line_box = matplotlib.offsetbox.AuxTransformBox(matplotlib.transforms.Affine2D()) line_box.add_artist(line) # 垂直堆叠元素 vpacker = matplotlib.offsetbox.VPacker(children=[text_box, line_box], align='center', pad=0.5) # 计算VPacker的边界框中心 renderer = fig.canvas.get_renderer() bbox = vpacker.get_window_extent(renderer) center_x, center_y = bbox.width / 2, bbox.height / 2 # 组合变换:平移到原点→旋转→平移回中心 transform = matplotlib.transforms.Affine2D().translate(-center_x, -center_y)\ .rotate_deg(45)\ .translate(center_x, center_y) aux_box = matplotlib.offsetbox.AuxTransformBox(transform) aux_box.add_artist(vpacker) # 定位到图中心 anchored_box = matplotlib.offsetbox.AnchoredOffsetbox(loc='center', child=aux_box, frameon=True) ax.add_artist(anchored_box) matplotlib.pyplot.xlim(-2, 2) matplotlib.pyplot.ylim(-2, 2) matplotlib.pyplot.show()
内容的提问来源于stack exchange,提问作者moss_xyz

