我正在尝试在 MatPlotLib 中创建一个堆叠条形图,顶部和底部有两个不同的 x 标签。上面的应该有一个与条本身宽度相同的边界框。
\n\n情节不太正确
\n\n这就是我创建标签的方式:
\n\nplt.tick_params(axis="both", left=False, bottom=False, labelleft=False)\nplt.xticks(ind, diagram.keys())\nax.set_frame_on(False)\n\nfor label, x in zip([q[1] for q in diagram.values()], ind):\n ax.text(\n x, 1.05, \'{:4.0%}\'.format(label), \n ha="center", va="center", \n bbox={"facecolor": "blue", "pad": 3}\n )\n
Run Code Online (Sandbox Code Playgroud)\n\ndiagram
是一本字典像{bottom-label: [[contents], top-label]}
所以我想我的问题可以归结为:如何操作文本对象的边界框?
\n\n多谢!
\n\n根据请求,一个可运行的示例:
\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\ndef stacked_bar_chart(\n diagram, title="example question", img_name="test_image", width=0.7, clusters=None, show_axes=True,\n show_legend=True, show_score=True):\n """\n Builds one or multiple scaled stacked bar charts for grade\n distributions. saves image as png.\n :param show_score: whether the score should be shown on top\n :param show_legend: whether the legend should be shown\n :param show_axes: whether question name should be shown on bottom\n :param clusters: indices of clusters to be displayed.\n :param width: the width of the bars as fraction of available space\n :param title: diagram title\n :param img_name: output path\n :param diagram: dictionary: {x-label: [[grade distribution], score]}\n :return: nothing.\n """\n\n grades = {\n "sehr gut": "#357100",\n "gut": "#7fb96a",\n "befriedigend": "#fdd902",\n "ausreichend": "#f18d04",\n "mangelhaft": "#e3540e",\n "ungen\xc3\xbcgend": "#882d23"\n }\n\n # select clusters\n if clusters is not None:\n diagram = {i: diagram[i] for i in clusters}\n\n # normalize score distribution => sum of votes = 1.0\n normalized = []\n for question in diagram.values():\n s = sum(question[0])\n normalized.append([x / s for x in question[0]])\n\n # transpose dict values (score distributions) to list of lists\n transformed = list(map(list, zip(*normalized)))\n\n # input values for diagram generation\n n = len(diagram) # number of columns\n ind = np.arange(n) # x values for bar center\n base = [0] * n # lower bounds for individual color set\n bars = []\n fig, ax = plt.subplots()\n\n # loop over grades\n for name, grade in zip(grades.keys(), transformed):\n assert len(grade) == n, \\\n "something went wrong in plotting grade stack " + img_name\n bar = plt.bar(ind, grade, width=width, color=grades[name], bottom=base)\n bars.append(bar)\n\n # loop over bars\n for i, (rect, score) in enumerate(zip(bar, grade)):\n # update lower bound for next bar section\n base[i] += grade[i]\n # label with percentage\n # TODO text color white\n ax.text(\n rect.get_x() + width / 2, rect.get_height() / 2 + rect.get_y(), "{0:.0f}%".format(score * 100),\n va="center", ha="center")\n\n # label diagram\n\n plt.suptitle(title)\n if show_axes:\n plt.tick_params(axis="both", left=False, bottom=False, labelleft=False)\n plt.xticks(ind, diagram.keys())\n ax.set_frame_on(False)\n\n else:\n plt.tick_params(axis="both", left=False, bottom=False, labelleft=False, labelbottom=False)\n plt.axis("off")\n\n # show score label above\n if show_score:\n for label, x in zip([q[1] for q in diagram.values()], ind):\n ax.text(\n x, 1.05, \'{:4.0%}\'.format(label),\n ha="center", va="center",\n bbox={"facecolor": "blue", "pad": 3}\n )\n\n # create legend\n if show_legend:\n plt.legend(\n reversed(bars), reversed([*grades]),\n bbox_to_anchor=(1, 1), borderaxespad=0)\n\n # save file\n plt.show()\n\n\ndiagram = {\n "q1": [[1, 2, 3, 4, 5, 6], 0.6],\n "q2": [[2, 3, 1, 2, 3, 1], 0.4]\n}\nstacked_bar_chart(diagram)\n
Run Code Online (Sandbox Code Playgroud)\n
对于为什么将文本框的宽度设置为定义的宽度很难的争论,请参阅这个关于设置标题文本框宽度的问题。原则上,那里的答案也可以在这里使用——这使得事情变得相当复杂。
一个相对简单的解决方案是在数据坐标中指定文本的 x 位置,在轴坐标中指定文本的 y 位置。这允许创建一个矩形作为具有相同坐标的文本的背景,使其看起来像文本的边界框。
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()
Run Code Online (Sandbox Code Playgroud)