2025, Oct 28 03:00
How to Align Horizontal Stacked Bar Charts in Pandas and Matplotlib with a Twin Axis: Labels, Legend, and Background Band
Learn to fix misaligned horizontal bar charts in pandas and matplotlib: align stacked bars, use a twin axis for labels, keep band behind, tidy the legend.
Horizontal bar charts with pandas and matplotlib look straightforward until stacked bars and a secondary axis enter the mix. A common pitfall is misaligned bars when one layer is drawn on a twin axis. The visual mismatch is subtle, yet it breaks the story the chart is trying to tell. Here’s how to fix the alignment without losing category labels, while keeping the background band behind and the legend tidy.
Reproducing the issue
The following snippet builds a simple DataFrame and draws two horizontal bar layers on different axes. Stacked bars go on a secondary y-axis, while a semi-transparent background band sits on the primary y-axis. The result is misaligned bars and layout quirks.
import pandas as pd
import matplotlib.pyplot as plt
records = {'Name': ["A", "B", "C", "D", 'E'],
           'Todo': [4, 5, 6, 7, 3],
           'Done': [6, 2, 6, 8, 6],
           'TimeRemaining': [4, 4, 4, 4, 4]}
frame = pd.DataFrame(records)
canvas, left_ax = plt.subplots(figsize=(10, 8))
right_ax = left_ax.twinx()
pick_cols = frame.columns[0:2].to_list()
bar_ax = frame.plot(kind='barh', y=pick_cols, stacked=True, ax=right_ax)
for box in bar_ax.containers:
    labels = [r.get_width() if r.get_width() > 0 else '' for r in box]
    bar_ax.bar_label(box, fmt=lambda val: f'{val:.0f}' if val > 0 else '', label_type='center')
frame.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke',
                             alpha=0.3, ax=left_ax, align='center', width=0.8,
                             edgecolor='blue')
right_ax.tick_params(axis='y', labelright=False, right=False)
right_ax.set_yticklabels([])
left_ax.get_legend().remove()
plt.title('Status Chart')
plt.tight_layout()
plt.show()
What’s really happening
Two different axes are being used: one for the stacked bars and one for the semi-transparent band. Each axis manages its own y-axis positions. Because the bars live on different axes, their category centers don’t match, and the layers don’t align. If you move everything onto one axis, the bars line up, but you also lose the left-side category labels and get an unwanted legend entry, with the translucent band drawn on top.
The fix
Put both plots on the same axis so the bar centers match, and use the twin axis only to show the left-side labels. Sync the left y-axis with the right y-axis positions, hide the right-side tick labels, keep only the legend you need, and send the background band behind with zorder. If a right-side ylabel appears, clear it explicitly.
import pandas as pd
import matplotlib.pyplot as plt
records = {'Name': ["A", "B", "C", "D", 'E'],
           'Todo': [4, 5, 6, 7, 3],
           'Done': [6, 2, 6, 8, 6],
           'TimeRemaining': [4, 4, 4, 4, 4]}
frame = pd.DataFrame(records)
canvas, left_ax = plt.subplots(figsize=(10, 8))
right_ax = left_ax.twinx()
stack_cols = frame.columns[1:3].to_list()
bars_ax = frame.set_index('Name').plot(kind='barh', y=stack_cols, stacked=True, ax=right_ax)
for grp in bars_ax.containers:
    labels = [seg.get_width() if seg.get_width() > 0 else '' for seg in grp]
    bars_ax.bar_label(grp, fmt=lambda v: f'{v:.0f}' if v > 0 else '', label_type='center')
frame.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke',
                             alpha=0.3, ax=right_ax, align='center', width=0.8,
                             edgecolor='blue', zorder=0)
right_ax.tick_params(axis='y', labelright=False, right=False)
left_ax.tick_params(axis='y', labelleft=True, left=True)
left_ax.set_ylim(right_ax.get_ylim())
left_ax.set_yticks(right_ax.get_yticks())
left_ax.set_yticklabels(frame['Name'])
handles, labels = right_ax.get_legend_handles_labels()
right_ax.legend([handles[0]], [labels[0]], loc='upper right')
right_ax.set_ylabel('')
plt.title('Status Chart')
plt.tight_layout()
plt.show()
Why this matters
Category alignment in horizontal bar charts is easy to miss and hard to unsee. When layers represent different measures, misalignment can imply incorrect relationships. Ensuring both layers share the same axis preserves accuracy while keeping the chart readable, especially when you want stacked values, a translucent baseline band, centered labels, and a clean legend.
Wrap-up
Use a single plotting axis for all bar layers to guarantee alignment. Let the twin axis serve the labels on the left, mirror the y-axis limits and ticks, and keep the background band behind with z-order. Clean up the legend and clear any unwanted right-side ylabel. The result is a consistent, readable horizontal bar chart that communicates the right story without layout surprises.