I have the same problem when trying to draw a treemap with squarify
. After some search, I come up with a solution, which seems to work as expected.
import matplotlib.patches as mpatches
import matplotlib.text as mtext
# Refrence https://stackoverflow.com/questions/48079364/wrapping-text-not-working-in-matplotlib
# and https://stackoverflow.com/questions/50742503/how-do-i-get-the-height-of-a-wrapped-text-in-matplotlib
class WrapText(mtext.Text):
def __init__(self,
x=0, y=0, text='',
width=0,
**kwargs):
mtext.Text.__init__(self,
x=x, y=y, text=text,
wrap=True,
**kwargs)
self.width = width # in screen pixels. You could do scaling first
def _get_wrap_line_width(self):
return self.width
def get_lines_num(self):
return len(self._get_wrapped_text().split('\n'))
class WrapAnnotation(mtext.Annotation):
def __init__(self,
text, xy,
width, **kwargs):
mtext.Annotation.__init__(self,
text=text,
xy=xy,
wrap=True,
**kwargs)
self.width = width
def _get_wrap_line_width(self):
return self.width
def get_lines_num(self):
return len(self._get_wrapped_text().split('\n'))
def text_with_autofit(self, txt, xy, width, height, *,
transform=None,
ha='center', va='center',
wrap=False, show_rect=False,
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):
if not wrap:
text = self.text(*xy, txt, ha=ha, va=va, transform=transform,
fontsize=min_size,
**kwargs)
else:
fontsize /= 2
text = WrapText(*xy, txt, width=rect_width, ha=ha, va=va,
transform=transform, fontsize=fontsize,
**kwargs)
self.add_artist(text)
if isinstance(self, Axes):
if not wrap:
text = self.annotate(txt, xy, ha=ha, va=va, xycoords=transform,
fontsize=min_size,
**kwargs)
else:
fontsize /= 2
text = WrapAnnotation(txt, xy, ha=ha, va=va, xycoords=transform,
fontsize=fontsize, width=rect_width,
**kwargs)
self.add_artist(text)
while fontsize > min_size:
text.set_fontsize(fontsize)
bbox = text.get_window_extent(fig.canvas.get_renderer())
bbox_width = bbox.width / text.get_lines_num() if wrap else bbox.width
if bbox_width <= rect_width:
while bbox_width <= rect_width:
fontsize += 1
text.set_fontsize(fontsize)
bbox = text.get_window_extent(fig.canvas.get_renderer())
bbox_width = bbox.width / text.get_lines_num() if wrap else bbox.width
else:
fontsize = fontsize - 1
text.set_fontsize(fontsize)
break;
fontsize /= 2
if fig.get_constrained_layout():
c_fontsize = fontsize + adjust + 0.5
text.set_fontsize(c_fontsize if c_fontsize > min_size else min_size)
if fig.get_tight_layout():
c_fontsize = fontsize + adjust
text.set_fontsize(c_fontsize if c_fontsize > min_size else min_size)
if show_rect and isinstance(self, Axes):
rect = mpatches.Rectangle((x_data[ha][0], y_data[va][0]),
width, height, fill=False, ls='--')
self.add_patch(rect)
return text
This function supports auto-fitting text into a box. If wrap
is True
, then the text will be auto-wrapped according to the size of the box.
The following is the figure with auto-fitting (grow=True
) and auto-wraping (wrap=True
)
The data is G20 from treemapify, which is an excellent R's package to plot a treemap.
Figure with auto-fitting:

Figure with auto-fitting and auto-wraping:

The basic process of auto-fitting is setting the font size according to the height of the box, comparing text width with the box's width and decreasing the font size until the text width is less than box's width.
As for auto-wrapping, the underlying process depends on the built-in auto-wrap in matplotlib by setting wrap=True
. The process of auto-adjusting the fontsize is same.
However, the process of auto-fitting is a little slow. I hope some one can figure out some more efficient algorithm of auto-fitting.
Hope this function can help you.