Matplotlib:如何指定X标签边框的宽度 [英] Matplotlib: how to specify width of x-label bounding box
问题描述
我正在尝试在 MatPlotLib 中创建一个堆叠条形图,顶部和底部有两个不同的 x 标签.上面的应该有一个与条本身宽度相同的边界框.
情节不太对
这是我创建标签的方式:
plt.tick_params(axis="both", left=False, bottom=False, labelleft=False)plt.xticks(ind,diagram.keys())ax.set_frame_on(False)对于标签,zip 中的 x([q[1] for q in diagram.values()], ind):ax.text(x, 1.05, '{:4.0%}'.format(label),ha ="center",va ="center",bbox = {"facecolor":蓝色","pad":3})
diagram
是一个类似于 {bottom-label: [[contents], top-label]}
所以我想我的问题归结为:如何操作文本对象的边界框?
非常感谢!
根据请求,一个可运行的示例:
将matplotlib.pyplot导入为plt将numpy导入为npdef stacked_bar_chart(图表,标题="示例问题",img_name="test_image",宽度=0.7,集群=无,show_axes=True,show_legend=True,show_score=True):"""为等级构建一个或多个缩放的堆积条形图分布.将图像另存为png.:param show_score:分数是否应显示在最前面:param show_legend:是否应显示图例:param show_axes:问题名称是否应显示在底部:param clusters:要显示的簇的索引.:param width: 条的宽度作为可用空间的一部分:param title:图表标题:param img_name:输出路径:param diagram:字典:{x-label:[[等级分布],分数]}:return: 没什么."""成绩= {"sehr 肠道": "#357100","gut": "#7fb96a","befriedigend": "#fdd902","ausreichend":#f18d04","mangelhaft":#e3540e",ungenügend":#882d23"}#选择集群如果群集不是无:图= {i:集群中i的diagram [i]}#归一化分数分布=>投票总数= 1.0归一化 = []对于 diagram.values() 中的问题:s = sum(问题[0])normalized.append([x/s for x in question[0]])# 将字典值(分数分布)转置为列表列表转换 = 列表(地图(列表,zip(*标准化)))# 图表生成的输入值n = len(diagram)#列数ind = np.arange(n) # 条形中心的 x 值base = [0] * n#各个颜色集的下限条 = []无花果,ax = plt.subplots()#遍历成绩对于名称,zip 中的等级(grades.keys(),已转换):断言len(grade)== n,\在绘制成绩堆栈时出了问题" + img_namebar = plt.bar(ind,grade, width=width, color=grades[name], bottom=base)bar.append(bar)# 遍历条形对于 i, (rect, score) in enumerate(zip(bar,grade)):# 更新下一个柱节的下限基数[i] += 等级[i]# 带有百分比的标签#TODO文字颜色为白色ax.text(rect.get_x()+宽度/2,rect.get_height()/2 + rect.get_y(),"{0:.0f}%".format(分数* 100),va="中心", ha="中心")#标签图plt.suptitle(标题)如果show_axes:plt.tick_params(axis ="both",left = False,bottom = False,labelleft = False)plt.xticks(ind,diagram.keys())ax.set_frame_on(False)别的:plt.tick_params(axis ="both",left = False,bottom = False,labelleft = False,labelbottom = False)plt.axis(关闭")# 在上面显示分数标签如果show_score:对于标签,zip 中的 x([q[1] for q in diagram.values()], ind):ax.text(x, 1.05, '{:4.0%}'.format(label),ha ="center",va ="center",bbox = {"facecolor":蓝色","pad":3})#创建图例如果show_legend:plt.图例(反转(条),反转([*等级]),bbox_to_anchor=(1, 1), borderaxespad=0)# 保存存档plt.show()图 = {"q1":[[1、2、3、4、5、6],0.6],"q2":[[2,3,1,2,3,1],0.4]}stacked_bar_chart(图表)
有关为什么将文本框的宽度设置为定义的宽度很难的争论,请参阅
I'm trying to create a stacked bar chart in MatPlotLib with two distinct x-labels on top and bottom. The upper one is supposed to have a bounding box with the same width as the bars themselves.
Plot that's not quite right
This is how I create the labels:
plt.tick_params(axis="both", left=False, bottom=False, labelleft=False)
plt.xticks(ind, diagram.keys())
ax.set_frame_on(False)
for label, x in zip([q[1] for q in diagram.values()], ind):
ax.text(
x, 1.05, '{:4.0%}'.format(label),
ha="center", va="center",
bbox={"facecolor": "blue", "pad": 3}
)
diagram
is a dictionary like {bottom-label: [[contents], top-label]}
So I guess my question boils down to: How do I manipulate the bounding boxs of the text objects?
Thanks a lot!
As per request, a runnable example:
import matplotlib.pyplot as plt
import numpy as np
def stacked_bar_chart(
diagram, title="example question", img_name="test_image", width=0.7, clusters=None, show_axes=True,
show_legend=True, show_score=True):
"""
Builds one or multiple scaled stacked bar charts for grade
distributions. saves image as png.
:param show_score: whether the score should be shown on top
:param show_legend: whether the legend should be shown
:param show_axes: whether question name should be shown on bottom
:param clusters: indices of clusters to be displayed.
:param width: the width of the bars as fraction of available space
:param title: diagram title
:param img_name: output path
:param diagram: dictionary: {x-label: [[grade distribution], score]}
:return: nothing.
"""
grades = {
"sehr gut": "#357100",
"gut": "#7fb96a",
"befriedigend": "#fdd902",
"ausreichend": "#f18d04",
"mangelhaft": "#e3540e",
"ungenügend": "#882d23"
}
# select clusters
if clusters is not None:
diagram = {i: diagram[i] for i in clusters}
# normalize score distribution => sum of votes = 1.0
normalized = []
for question in diagram.values():
s = sum(question[0])
normalized.append([x / s for x in question[0]])
# transpose dict values (score distributions) to list of lists
transformed = list(map(list, zip(*normalized)))
# input values for diagram generation
n = len(diagram) # number of columns
ind = np.arange(n) # x values for bar center
base = [0] * n # lower bounds for individual color set
bars = []
fig, ax = plt.subplots()
# loop over grades
for name, grade in zip(grades.keys(), transformed):
assert len(grade) == n, \
"something went wrong in plotting grade stack " + img_name
bar = plt.bar(ind, grade, width=width, color=grades[name], bottom=base)
bars.append(bar)
# loop over bars
for i, (rect, score) in enumerate(zip(bar, grade)):
# update lower bound for next bar section
base[i] += grade[i]
# label with percentage
# TODO text color white
ax.text(
rect.get_x() + width / 2, rect.get_height() / 2 + rect.get_y(), "{0:.0f}%".format(score * 100),
va="center", ha="center")
# label diagram
plt.suptitle(title)
if show_axes:
plt.tick_params(axis="both", left=False, bottom=False, labelleft=False)
plt.xticks(ind, diagram.keys())
ax.set_frame_on(False)
else:
plt.tick_params(axis="both", left=False, bottom=False, labelleft=False, labelbottom=False)
plt.axis("off")
# show score label above
if show_score:
for label, x in zip([q[1] for q in diagram.values()], ind):
ax.text(
x, 1.05, '{:4.0%}'.format(label),
ha="center", va="center",
bbox={"facecolor": "blue", "pad": 3}
)
# create legend
if show_legend:
plt.legend(
reversed(bars), reversed([*grades]),
bbox_to_anchor=(1, 1), borderaxespad=0)
# save file
plt.show()
diagram = {
"q1": [[1, 2, 3, 4, 5, 6], 0.6],
"q2": [[2, 3, 1, 2, 3, 1], 0.4]
}
stacked_bar_chart(diagram)
For arguments why setting the width of a text box to a defined width is hard see this question which is about setting the title text box width. In principle the answer over there could be used here as well - making this rather complicated.
A relatively easy solution would be to specify the x-position of the text in data coordinates and its y position in axes coordinates. This allows to create a rectangle as background for the text with the same coordinates such that it looks like a bounding box of the text.
import matplotlib.pyplot as plt
import numpy as np
ind = [1,2,4,5]
data = [4,5,6,4]
perc = np.array(data)/float(np.array(data).sum())
width=0.7
pad = 3 # points
fig, ax = plt.subplots()
bar = ax.bar(ind, data, width=width)
fig.canvas.draw()
for label, x in zip(perc, ind):
text = ax.text(
x, 1.00, '{:4.0%}'.format(label),
ha="center", va="center" , transform=ax.get_xaxis_transform(), zorder=4)
bb= ax.get_window_extent()
h = bb.height/fig.dpi
height = ((text.get_size()+2*pad)/72.)/h
rect = plt.Rectangle((x-width/2.,1.00-height/2.), width=width, height=height,
transform=ax.get_xaxis_transform(), zorder=3,
fill=True, facecolor="lightblue", clip_on=False)
ax.add_patch(rect)
plt.show()
这篇关于Matplotlib:如何指定X标签边框的宽度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!