2017-12-02 203 views
1

我想在MatPlotLib中创建一个堆叠的条形图,顶部和底部有两个不同的x标签。上面的那个应该有一个与条形本身宽度相同的边界框。Matplotlib:如何指定x标签边界框的宽度

情节这并不完全正确

这是我如何创建标签:

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就像{bottom-label: [[contents], top-label]}

字典所以我想我的问题归结到:如何操纵t的边界框ext对象?

非常感谢!

根据要求,可运行例如:

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) 
+1

这是相当麻烦的,见[这个问题](https://stackoverflow.com/questions/40796117/how-do-i-make-the-width-of-the- title-box-span-the-entire-plot),它希望为标题框做到这一点。当然,人们可以根据宽度来调整它。另一方面,人们可能会将一些文本放在轴上,并在背景中绘制所需尺寸的矩形。你想去哪个方向? – ImportanceOfBeingErnest

+0

我更喜欢前一个版本。我已经探索了绘制矩形的选项,但由于我不记得的原因而失败了......然后,我是寻求建议的人,所以我为任何你认为最好的东西敞开心扉。 – azrael

+0

我可以看到我能做什么,但是你会介意提供一个[mcve](不要使用你的真实数据,只是我可以复制,粘贴和运行的东西)。 – ImportanceOfBeingErnest

回答

1

对于参数为什么一个文本框的宽度设置为定义的宽度是难以看到this question这大约是设置标题文本框宽度。原则上,这里的答案也可以用在这里 - 这使得它变得相当复杂。

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

enter image description here

+0

非常感谢!它做到了。 尽管由于某种原因,可能是因为我在此期间转而使用熊猫创建了实际的条形图,但我必须使用'ax = plt.gca()'和'fig = plt.gcf()'而不是'plt。子图()'或它将创建分离的情节。 – azrael