Reputation: 1200
Problem
My matplotlib application generates user-defined dynamic images and so things like page title text can be of varying length. I want to be able to specify a bounding box to matplotlib and then have it auto-scale the font size so that the text fits within that bounding box. My application only uses the AGG backend.
My hack solution
I am the least sharp tool in the toolbox, but here is what I came up with for a solution to this problem. I brute force start at a fontsize of 50
and then iterate downward until I think I can fit the text into the box.
def fitbox(fig, text, x0, x1, y0, y1, **kwargs):
"""Fit text into a NDC box."""
figbox = fig.get_window_extent().transformed(
fig.dpi_scale_trans.inverted())
# need some slop for decimal comparison below
px0 = x0 * fig.dpi * figbox.width - 0.15
px1 = x1 * fig.dpi * figbox.width + 0.15
py0 = y0 * fig.dpi * figbox.height - 0.15
py1 = y1 * fig.dpi * figbox.height + 0.15
# print("px0: %s px1: %s py0: %s py1: %s" % (px0, px1, py0, py1))
xanchor = x0
if kwargs.get('ha', '') == 'center':
xanchor = x0 + (x1 - x0) / 2.
yanchor = y0
if kwargs.get('va', '') == 'center':
yanchor = y0 + (y1 - y0) / 2.
txt = fig.text(
xanchor, yanchor, text,
fontsize=50, ha=kwargs.get('ha', 'left'),
va=kwargs.get('va', 'bottom'),
color=kwargs.get('color', 'k')
)
for fs in range(50, 1, -2):
txt.set_fontsize(fs)
tbox = txt.get_window_extent(fig.canvas.get_renderer())
# print("fs: %s tbox: %s" % (fs, str(tbox)))
if (tbox.x0 >= px0 and tbox.x1 < px1 and tbox.y0 >= py0 and
tbox.y1 <= py1):
break
return txt
So then I can call this function like so
fitbox(fig, "Hello there, this is my title!", 0.1, 0.99, 0.95, 0.99)
Question/Feedback Request
axes
and not the overall figure. Perhaps that already works :)As an aside, I like how some other plotting applications allow the specifying of font-size in non-dimensional display coordinates. For example, PyNGL. So you can set it to fontsize=0.04
for example.
Thank you.
Upvotes: 1
Views: 1581
Reputation: 1779
My implementation of auto-fit text in a box:
def text_with_autofit(self, txt, xy, width, height, *,
transform=None,
ha='center', va='center',
min_size=1, adjust=0,
**kwargs):
if transform is None:
if isinstance(self, Axes):
transform = self.transData
if isinstance(self, Figure):
transform = self.transFigure
x_data = {'center': (xy[0] - width/2, xy[0] + width/2),
'left': (xy[0], xy[0] + width),
'right': (xy[0] - width, xy[0])}
y_data = {'center': (xy[1] - height/2, xy[1] + height/2),
'bottom': (xy[1], xy[1] + height),
'top': (xy[1] - height, xy[1])}
(x0, y0) = transform.transform((x_data[ha][0], y_data[va][0]))
(x1, y1) = transform.transform((x_data[ha][1], y_data[va][1]))
# rectange region size to constrain the text
rect_width = x1 - x0
rect_height = y1- y0
fig = self.get_figure() if isinstance(self, Axes) else self
dpi = fig.dpi
rect_height_inch = rect_height / dpi
fontsize = rect_height_inch * 72
if isinstance(self, Figure):
text = self.text(*xy, txt, ha=ha, va=va, transform=transform,
**kwargs)
if isinstance(self, Axes):
text = self.annotate(txt, xy, ha=ha, va=va, xycoords=transform,
**kwargs)
while fontsize > min_size:
text.set_fontsize(fontsize)
bbox = text.get_window_extent(fig.canvas.get_renderer())
if bbox.width < rect_width:
break;
fontsize -= 1
if fig.get_constrained_layout():
text.set_fontsize(fontsize + adjust + 0.5)
if fig.get_tight_layout():
text.set_fontsize(fontsize + adjust)
return text
Upvotes: 1