Matplotlib:如何指定x标签边界框的宽度

azr*_*ael 4 python matplotlib

我正在尝试在 MatPlotLib 中创建一个堆叠条形图,顶部和底部有两个不同的 x 标签。上面的应该有一个与条本身宽度相同的边界框。

\n\n

情节不太正确

\n\n

\n\n

这就是我创建标签的方式:

\n\n
plt.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\n

diagram是一本字典像{bottom-label: [[contents], top-label]}

\n\n

所以我想我的问题可以归结为:如何操作文本对象的边界框?

\n\n

多谢!

\n\n

根据请求,一个可运行的示例:

\n\n
import 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

Imp*_*est 6

对于为什么将文本框的宽度设置为定义的宽度很难的争论,请参阅这个关于设置标题文本框宽度的问题。原则上,那里的答案也可以在这里使用——这使得事情变得相当复杂。

一个相对简单的解决方案是在数据坐标中指定文本的 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)

在此输入图像描述