Matplotlib: cómo especificar el ancho del cuadro delimitador de la etiqueta x

Estoy intentando crear un gráfico de barras astackdas en MatPlotLib con dos tags x distintas en la parte superior e inferior. Se supone que el superior tiene un cuadro delimitador con el mismo ancho que las barras.

Parcela que no está del todo bien

Así es como creo las tags:

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 es un diccionario como {bottom-label: [[contents], top-label]}

Así que supongo que mi pregunta se reduce a: ¿Cómo manipulo los cuadros delimitadores de los objetos de texto?

¡Muchas gracias!

Según solicitud, un ejemplo ejecutable:

 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) 

Para los argumentos por los que es difícil establecer el ancho de un cuadro de texto en un ancho definido, vea esta pregunta que trata sobre cómo configurar el ancho del cuadro de texto del título. En principio, la respuesta allí también podría usarse aquí, lo que hace que esto sea bastante complicado.

Una solución relativamente fácil sería especificar la posición x del texto en las coordenadas de datos y su posición y en las coordenadas de los ejes. Esto permite crear un rectángulo como fondo para el texto con las mismas coordenadas, de modo que se vea como un cuadro delimitador del texto.

 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() 

introduzca la descripción de la imagen aquí