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 将文本放入框中。如果 wrap
为 True
,则文本将根据框的大小 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.
算法
希望这个功能可以帮到你。
我正在使用 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 将文本放入框中。如果 wrap
为 True
,则文本将根据框的大小 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.
算法希望这个功能可以帮到你。