0

I'm trying to correctly align the numbers and percentages in the barplots I'm making so that they're exactly after each bar, but having some troubles trying to do so. I want the: "number (%percentage)" to come exactly after each bar (very small space between). However, now in case of the upper ones (Top 200, 400) the number and percentage are already shifted more forwards than the bottom ones (Top1-50). So when I try to shift them the bottom ones lack behind the upper ones, resulting in not very aesthetically pleasing look. Is there a way to fix this?

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams.update(plt.rcParamsDefault)

labels = ["Top1", "Top3", "Top5", "Top10", "Top20", "Top50", "Top100", "Top200", "Top400"]
native_shape_1000=[38, 42, 44, 48, 52, 57, 61, 66, 68] #Native pose (restr. to shapes & it0 1000)
native_pocket_1000=[43, 51, 51,57,60,64,67,68,69] #Native pose (restr. to pocket residues)

x = np.arange(len(native_shape_1000))  # the label locations
width = 0.4  
fig, axes = plt.subplots(2,2, figsize=(12,8), sharey= False, sharex=False)
axes= axes.flatten()
rects1=axes[0].barh(x - width/2, native_shape_1000, width, label=' Restraints to rank 1 \n pocket shapes;\n it0 = 1000')
rects2=axes[0].barh(x + width/2, native_pocket_1000, width, label=' Restraints to rank 1 \n pocket residues;\n it0 = 1000')

axes[0].set_xlim(xmin=0, xmax=102)
axes[0].set_xticks([x for x in range(0,103,10)])
axes[0].set_yticks(x)
axes[0].set_yticklabels(labels)
axes[0].legend(loc=4, prop={'size': 8})


def autolabel(rects, axes):

    for rect in rects:
        width = rect.get_width()
        perc=int(round(width/102*100))
        axes.annotate(f'{width} ({perc}%) ',
                      xy=(width, rect.get_y()+ rect.get_height()), 
                      xytext=(0,0),
                      textcoords="offset points",
                      ha='left', va='bottom')

autolabel(rects1, axes[0])
# autolabel(rects2)
# autolabel rects3,4 etc.
# fig.tight_layout()

plt.show()

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
user21398
  • 419
  • 6
  • 13

1 Answers1

3

matplotlib >= 3.4.2

def autolabel(rects, axes):
    labels = [f'{w} ({int(round(w/102*100))}%)' for rect in rects if (w := rect.get_width()) is not None]
    axes.bar_label(rects, labels=labels, label_type='edge', fontsize=8, padding=3)

enter image description here

matplotlib < 3.4.2

  • rect.get_y() + rect.get_height() / 2 see that rect.get_height() needs to be divided by 2.
  • Change to va='center_baseline' and add fontsize=8
def autolabel(rects, axes):
    for rect in rects:
        width = rect.get_width()
        perc=int(round(width/102*100))
        axes.annotate(f'{width} ({perc}%)',
                      xy=(width, rect.get_y() + rect.get_height() / 2), 
                      xytext=(0, 0),
                      textcoords="offset points",
                      ha='left', va='center_baseline', fontsize=8)


autolabel(rects1, axes[0])
autolabel(rects2, axes[0])

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158