Task
I tried to code a treemap function in matplotlib, and one of the challenges is auto-fitting text into boxes of different sizes in the treemap, similar to R's package ggfittext.
My Code
My implementation is as follows:
import matplotlib.patches as mpatches
def text_with_autofit(ax, txt, xy, width, height, *,
transform=None,
ha='center', va='center',
min_size=1, show_rect=False,
**kwargs):
if transform is None:
transform = ax.transData
# Different alignments gives different bottom left and top right anchors.
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 in pixel
rect_width = x1 - x0
rect_height = y1- y0
fig = ax.get_figure()
dpi = fig.dpi
rect_height_inch = rect_height / dpi
# Initial fontsize according to the height of boxes
fontsize = rect_height_inch * 72
text = ax.annotate(txt, xy, ha=ha, va=va, xycoords=transform,
**kwargs)
# Adjust the fontsize according to the box size, and this is the slow part.
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 show_rect:
rect = mpatches.Rectangle((x_data[ha][0], y_data[va][0]),
width, height, fill=False, ls='--')
ax.add_patch(rect)
return text
Examples
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2, 1)
# In the box with the width of 0.4 and the height of 0.4 at (0.5, 0.5), add the text.
text_with_autofit(ax[0], "Hello, World! How are you?", (0.5, 0.5), 0.4, 0.4, show_rect=True)
# In the box with the width of 0.6 and the height of 0.4 at (0.5, 0.5), add the text.
text_with_autofit(ax[1], "Hello, World! How are you?", (0.5, 0.5), 0.6, 0.4, show_rect=True)
plt.show()
The resulting figures are as follows:
Problem
The slow part is the while
loop, which sets the font size, compares text width with the box's width and decreases the font size until the text width is less than box's width.
With this code, when I plotted a treemap with 15 items, it took about several seconds, while without auto-fitting, the plotting is fast.
I can't accept the performance. I wonder whether there are some ways to make the code more efficient. Any help is appreciated! Thanks!
treemap
function, which is a little complicated. What this function does is auto-adjusting the fontsize of atxt
in aax
so that thetxt
fits into the box with thewidth
andheight
at specific positionxy
. For simplicity, I add an example how this function works. \$\endgroup\$