Squarify - 树图中的自动调整标签大小

Squarify - Auto resize label in treemap

我正在使用 Squarify 在 Python 中实现一个简单的树状图。

我正在绘制艺术家姓名及其在考虑的歌曲图表中的流百分比,bigger/darker 平方,值越高。

我的代码如下:

dataGoals = sort_by_streams[sort_by_streams["Streams"]>1]

#Utilise matplotlib to scale our stream number between the min and max, then assign this scale to our values.
norm = matplotlib.colors.Normalize(vmin=min(dataGoals.Streams), vmax=max(dataGoals.Streams))
colors = [matplotlib.cm.Blues(norm(value)) for value in dataGoals.Streams]

#Create our plot and resize it.
fig1 = plt.figure()
ax = fig1.add_subplot()
fig1.set_size_inches(16, 4.5)

#Use squarify to plot our data, label it and add colours. We add an alpha layer to ensure black labels show through
labels = ["%s\n%.2f" % (label) for label in zip(dataGoals.Artist, dataGoals.Streams)]

squarify.plot(label=labels,sizes=dataGoals.Streams, color = colors, alpha=.7, bar_kwargs=dict(linewidth=0.5, edgecolor="#222222"),text_kwargs={'fontsize':15})
plt.title("Streams Percentage",fontsize=23,fontweight="bold")

#Remove our axes and display the plot
plt.axis('off')
plt.show()

这是结果:

您可能会注意到,较小方块的标签重叠并超出了边界。 有没有办法自动调整标签大小以适合正方形?

编辑:我尝试用下面的代码实现matplotlib的自动换行功能:squarify.plot(label=labels,sizes=dataGoals.Streams, color = colors, alpha=.7, bar_kwargs=dict(linewidth=0.5, edgecolor="#222222"),text_kwargs={'fontsize':20, 'wrap':True})但这并没有解决我的问题,我的文本标签仍然存在出界了。

我在尝试使用 squarify 绘制树状图时遇到了同样的问题。经过一番搜索,我想出了一个解决方案,似乎按预期工作。

import matplotlib.patches as mpatches
import matplotlib.text as mtext

# Refrence 
# and 
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

此函数支持auto-fitting 将文本放入框中。如果 wrapTrue,则文本将根据框的大小 auto-wrapped。

下图为auto-fitting(grow=True)和auto-wraping(wrap=True)

数据是来自treemapify的G20,这是一个优秀的绘制树图的R包。

图auto-fitting:

带有 auto-fitting 和 auto-wraping 的图:

auto-fitting的基本过程是根据框的高度设置字体大小,将文本宽度与框的宽度进行比较,减小字体大小,直到文本宽度小于框的宽度。

至于auto-wrapping,底层过程依赖matplotlib中的built-in auto-wrap设置wrap=True。 auto-adjusting fontsize的过程是一样的

不过auto-fitting的过程有点慢。我希望有人能想出一些更有效的 auto-fitting.

算法

希望这个功能可以帮到你。