2017-12-02 197 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