pythonmatplotlib

Align bars on different axes on top on each other in matplotlib


I have a df and I am trying to create horizontal bar charts. Currently, I have the code like below.


import pandas as pd
import matplotlib.pyplot as plt
data = {'Name': ["A", "B", "C", "D",'E'],
        'Todo': [4, 5, 6, 7, 3],
        'Done': [6, 2, 6, 8, 6],
        'TimeRemaining': [4, 4, 4, 4, 4]}
df = pd.DataFrame(data)

fig, ax1 = plt.subplots(figsize=(10, 8))
ax2 = ax1.twinx()
# Get the names of columns at indices 1 to 10
selected_column_names = df.columns[0:2].to_list()

ax = df.plot(kind='barh',y=selected_column_names, stacked=True, ax=ax2, )
for c in ax.containers:
    # Optional: if the segment is small or 0, customize the labels
    labels = [v.get_width() if v.get_width() > 0 else '' for v in c]

    # remove the labels parameter if it's not needed for customized labels
    ax.bar_label(c, fmt=lambda x: f'{x:.0f}' if x > 0 else '',label_type='center')

df.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke', alpha=0.3,ax=ax1, align='center', width=0.8, edgecolor='blue',)
# Hide y-axis tick labels
ax2.tick_params(axis='y', labelright=False, right=False)
ax2.set_yticklabels([])
ax1.get_legend().remove()

plt.title('Status Chart')
plt.tight_layout()
plt.show()

Which results in a plot like so & as you can see the dark blue bars are not centered with the semi-transparent bars (both bars are on different axes) enter image description here

If I put them on the same axis by changing ax1 to ax2 on the second plot like so df.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke', alpha=0.3,ax=ax2, align='center', width=0.8, edgecolor='blue',) they align perfectly but the names are not visible any more! I also get the legend for "TimeRemaining" which I don't want and the semi transparent bars are in the front now.

enter image description here

How do I fix the chart such that both bars are on top & I also have the name shown in the y-axis on the left?


Solution

  • You are using two different axes - ax1 and ax2 ; which have different y-axis positioning. When you put both plots on the same axis, they align properly but you lose the y-axis labels. You need to Put both plots on the same axis - ax2; so they align perfectly.

    import pandas as pd
    import matplotlib.pyplot as plt
    
    data = {'Name': ["A", "B", "C", "D", 'E'],
            'Todo': [4, 5, 6, 7, 3],
            'Done': [6, 2, 6, 8, 6],
            'TimeRemaining': [4, 4, 4, 4, 4]}
    
    df = pd.DataFrame(data)
    
    fig, ax1 = plt.subplots(figsize=(10, 8))
    ax2 = ax1.twinx()
    
    selected_column_names = df.columns[1:3].to_list()
    
    ax = df.set_index('Name').plot(kind='barh', y=selected_column_names, stacked=True, ax=ax2)
    
    for c in ax.containers:
        labels = [v.get_width() if v.get_width() > 0 else '' for v in c]
        ax.bar_label(c, fmt=lambda x: f'{x:.0f}' if x > 0 else '', label_type='center')
    
    df.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke', 
                             alpha=0.3, ax=ax2, align='center', width=0.8, 
                             edgecolor='blue', zorder=0)
    
    ax2.tick_params(axis='y', labelright=False, right=False)
    ax1.tick_params(axis='y', labelleft=True, left=True)
    
    ax1.set_ylim(ax2.get_ylim())
    ax1.set_yticks(ax2.get_yticks())
    ax1.set_yticklabels(df['Name'])
    
    handles, labels = ax2.get_legend_handles_labels()
    ax2.legend([handles[0]], [labels[0]], loc='upper right')
    
    plt.title('Status Chart')
    plt.tight_layout()
    plt.show()
    

    Thsi gives
    enter image description here