为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

临摹微笑 2023-08-03 16:37:00
我正在使用Squarify在 Python 中实现一个简单的树形图。我正在绘制艺术家姓名及其在所考虑的歌曲图表中的流百分比,正方形越大/越暗,值越高。我的代码如下: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 throughlabels = ["%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 plotplt.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})但这并不能解决我的问题,我的文本标签仍然超出范围。
查看完整描述

1 回答

?
慕妹3242003

TA贡献1824条经验 获得超6个赞

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


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

此功能支持将文本自动调整到框中。如果wrap是True,则文本将根据框的大小自动换行。


grow=True下图是自动调整( )和自动换行(wrap=True)的图


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


自动拟合的图:

https://img1.sycdn.imooc.com//64cb675b000142ba06340475.jpg

自动调整和自动换行的图形: 

https://img1.sycdn.imooc.com//64cb6767000161c306270475.jpg

自动调整的基本过程是根据框的高度设置字体大小,将文本宽度与框的宽度进行比较,然后减小字体大小,直到文本宽度小于框的宽度。

至于自动换行,底层过程依赖于 matplotlib 中内置的自动换行,通过设置wrap=True. 自动调整字体大小的过程是相同的。

然而,自动适配的过程有点慢。我希望有人能够找出一些更有效的自动拟合算法。

希望这个功能可以帮助到您。


查看完整回答
反对 回复 2023-08-03
  • 1 回答
  • 0 关注
  • 273 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信