如何修改Matplotlib代码实现文本适配方框并支持自动换行?
问题描述
我尝试实现文本适配到指定边界框的功能,需求是当文本较长时自动换行,避免挤在一行,同时保证字号足够大以保证可读性。
我的尝试
我参考了相关问题的已采纳答案并修改,以下是最小可复现示例(MWE):
from typing import Optional, Literal import matplotlib.patches as mpatches import matplotlib.pyplot as plt from matplotlib.text import Annotation from matplotlib.transforms import Transform, Bbox from matplotlib.figure import Figure def text_with_autofit( ax: plt.Axes, txt: str, xy: tuple[float, float], width: float, height: float, *, transform: Optional[Transform] = None, ha: Literal["left", "center", "right"] = "center", va: Literal["bottom", "center", "top"] = "center", show_rect: bool = False, **kwargs, ): if transform is None: transform = ax.transData # 不同对齐方式对应不同的锚点坐标 x, y = xy xa0, xa1 = { "center": (x - width / 2, x + width / 2), "left": (x, x + width), "right": (x - width, x), }[ha] ya0, ya1 = { "center": (y - height / 2, y + height / 2), "bottom": (y, y + height), "top": (y - height, y), }[va] a0 = xa0, ya0 a1 = xa1, ya1 x0, y0 = transform.transform(a0) x1, y1 = transform.transform(a1) # 转换为像素单位的边界框尺寸 rect_width = x1 - x0 rect_height = y1 - y0 fig: Figure = ax.get_figure() dpi = fig.dpi rect_height_inch = rect_height / dpi # 按边界框高度初始化字号 fontsize = rect_height_inch * 72 text: Annotation = ax.annotate(txt, xy, ha=ha, va=va, xycoords=transform, **kwargs) # 根据边界框宽度调整字号 text.set_fontsize(fontsize) bbox: Bbox = text.get_window_extent(fig.canvas.get_renderer()) adjusted_size = fontsize * rect_width / bbox.width text.set_fontsize(adjusted_size) if show_rect: rect = mpatches.Rectangle(a0, width, height, fill=False, ls="--") ax.add_patch(rect) return text def main() -> None: fig, ax = plt.subplots(2, 1) # 在(0.5,0.5)位置的0.4x0.4边界框中添加文本 text_with_autofit( ax[0], "Hello, World! How are you?", (0.5, 0.5), 0.4, 0.4, show_rect=True ) # 在同一位置的0.4x0.4边界框中添加长文本 text_with_autofit( ax[1], "Hello, World! How are you? I'm actually a much longer text block, i.e. my width is longer than the other text block.", (0.5, 0.5), 0.4, 0.4, show_rect=True, wrap=True, ) plt.show()
上述代码生成如下输出:
预期效果
我期望下方子图中的文本能够自动换行,并且字号更大,但目前换行功能似乎被忽略了。如何修改该MWE以实现文本自动换行?
解决方案
问题核心是ax.annotate的wrap参数不会自动关联边界框宽度,且原有字号调整逻辑仅考虑单行文本宽度,未兼顾换行后的高度约束。修改如下:
from typing import Optional, Literal import matplotlib.patches as mpatches import matplotlib.pyplot as plt from matplotlib.text import Annotation from matplotlib.transforms import Transform, Bbox from matplotlib.figure import Figure def text_with_autofit( ax: plt.Axes, txt: str, xy: tuple[float, float], width: float, height: float, *, transform: Optional[Transform] = None, ha: Literal["left", "center", "right"] = "center", va: Literal["bottom", "center", "top"] = "center", show_rect: bool = False, **kwargs, ): if transform is None: transform = ax.transData # 计算边界框锚点坐标 x, y = xy xa0, xa1 = { "center": (x - width / 2, x + width / 2), "left": (x, x + width), "right": (x - width, x), }[ha] ya0, ya1 = { "center": (y - height / 2, y + height / 2), "bottom": (y, y + height), "top": (y - height, y), }[va] a0 = xa0, ya0 a1 = xa1, ya1 # 转换为像素单位的边界框尺寸 x0, y0 = transform.transform(a0) x1, y1 = transform.transform(a1) rect_width_pix = x1 - x0 rect_height_pix = y1 - y0 fig: Figure = ax.get_figure() dpi = fig.dpi renderer = fig.canvas.get_renderer() # 创建文本对象 text: Annotation = ax.annotate(txt, xy, ha=ha, va=va, xycoords=transform, **kwargs) # 按边界框高度初始化字号 fontsize = (rect_height_pix / dpi) * 72 text.set_fontsize(fontsize) # 手动指定换行宽度,激活wrap功能 text._get_wrap_line_width = lambda: rect_width_pix # 迭代调整字号,确保文本完全适配边界框 max_iter = 10 for _ in range(max_iter): bbox: Bbox = text.get_window_extent(renderer) # 取宽度和高度缩放比例的最小值,避免超出边界 scale_width = rect_width_pix / bbox.width scale_height = rect_height_pix / bbox.height scale = min(scale_width, scale_height) if abs(scale - 1) < 0.01: # 误差足够小时停止迭代 break fontsize *= scale text.set_fontsize(fontsize) if show_rect: rect = mpatches.Rectangle(a0, width, height, fill=False, ls="--") ax.add_patch(rect) return text def main() -> None: fig, ax = plt.subplots(2, 1) text_with_autofit( ax[0], "Hello, World! How are you?", (0.5, 0.5), 0.4, 0.4, show_rect=True ) text_with_autofit( ax[1], "Hello, World! How are you? I'm actually a much longer text block, i.e. my width is longer than the other text block.", (0.5, 0.5), 0.4, 0.4, show_rect=True, wrap=True, ) plt.show()
关键修改说明
- 激活自动换行:通过
text._get_wrap_line_width手动指定换行宽度为边界框的像素宽度,让wrap=True参数真正生效。 - 双向约束字号:同时对比文本的宽高与边界框的宽高,取最小缩放比例调整字号,确保文本既不超出宽度,也不超出高度,同时保证字号最大化。
- 迭代优化:加入循环迭代调整,缩小文本尺寸与边界框的误差,避免单次调整的偏差。
内容的提问来源于stack exchange,提问作者Siem
相关产品推荐
相关产品推荐

