How to Create a Horizontal Bar Chart in Matplotlib
Horizontal bar charts flip the traditional bar chart on its side, placing categories on the y-axis and values on the x-axis. This orientation solves specific visualization problems that vertical bars...
Key Insights
- Horizontal bar charts excel at displaying long category labels and ranked data—use
barh()instead ofbar()when category names would overlap or when showing rankings where reading top-to-bottom feels natural - Always sort your data before plotting horizontal bars (descending for rankings, ascending for “worst to best” narratives) to make patterns immediately obvious to readers
- Add data labels directly on or next to bars using
ax.text()to eliminate the need for readers to trace back to the axis, especially important when precise values matter
Introduction & Setup
Horizontal bar charts flip the traditional bar chart on its side, placing categories on the y-axis and values on the x-axis. This orientation solves specific visualization problems that vertical bars handle poorly: long category labels that would overlap or require rotation, ranked lists that benefit from top-to-bottom reading order, and comparisons where horizontal space is abundant but vertical space is limited.
You’ll reach for horizontal bars when displaying survey results with lengthy response options, showing rankings (top 10 lists, leaderboards), or presenting data in dashboards where width exceeds height. They’re also more accessible—horizontal text is always easier to read than rotated text.
Here’s what you need to get started:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Verify installation
print(f"Matplotlib version: {plt.__version__}")
If you don’t have Matplotlib installed, run pip install matplotlib in your terminal.
Creating a Basic Horizontal Bar Chart
The barh() function is your primary tool. It takes y-positions and widths (not heights—this trips up newcomers who think in vertical bar terms).
# Basic horizontal bar chart
categories = ['Python', 'JavaScript', 'Java', 'C#', 'C++', 'Go']
values = [29.9, 19.5, 17.4, 6.8, 6.5, 4.2]
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(categories, values)
ax.set_xlabel('Popularity (%)')
ax.set_title('Programming Language Popularity 2024')
plt.tight_layout()
plt.show()
Notice the syntax difference between vertical and horizontal bars:
# Vertical bar chart
ax.bar(x_positions, heights)
# Horizontal bar chart
ax.barh(y_positions, widths)
The parameter order matters. For vertical bars, you specify x-positions and heights. For horizontal bars, you specify y-positions and widths. The value you’re measuring always goes in the second parameter.
Customizing Appearance
Default charts are functional but bland. Here’s how to make them publication-ready.
Custom colors make categories distinguishable and can encode additional information:
categories = ['Python', 'JavaScript', 'Java', 'C#', 'C++', 'Go']
values = [29.9, 19.5, 17.4, 6.8, 6.5, 4.2]
# Single color
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(categories, values, color='steelblue', alpha=0.8)
# Color list (manual assignment)
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#C7B8EA']
ax.barh(categories, values, color=colors)
# Colormap (gradient based on values)
colors = plt.cm.viridis(np.linspace(0.3, 0.9, len(values)))
ax.barh(categories, values, color=colors)
Adding value labels eliminates guesswork:
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.barh(categories, values, color='steelblue', alpha=0.8)
# Add values at the end of each bar
for i, (bar, value) in enumerate(zip(bars, values)):
ax.text(value + 0.5, i, f'{value}%',
va='center', fontsize=10, fontweight='bold')
ax.set_xlabel('Popularity (%)')
ax.set_title('Programming Language Popularity 2024')
plt.tight_layout()
plt.show()
Adjusting dimensions improves readability:
# Control figure size and bar height
fig, ax = plt.subplots(figsize=(12, 8))
ax.barh(categories, values, height=0.6, color='coral', edgecolor='black', linewidth=1.2)
ax.set_xlim(0, max(values) * 1.15) # Add 15% padding for labels
Sorting and Ordering Data
Unsorted data forces readers to do mental work. Always sort horizontal bar charts—usually descending so the largest value is at the top.
Using pandas for sorting (recommended for real projects):
# Create DataFrame
df = pd.DataFrame({
'language': ['Python', 'JavaScript', 'Java', 'C#', 'C++', 'Go'],
'popularity': [29.9, 19.5, 17.4, 6.8, 6.5, 4.2]
})
# Sort descending (largest at top)
df_sorted = df.sort_values('popularity', ascending=True) # ascending=True for top-to-bottom descending visual
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(df_sorted['language'], df_sorted['popularity'], color='teal')
ax.set_xlabel('Popularity (%)')
plt.tight_layout()
plt.show()
Note the counterintuitive ascending=True: Matplotlib plots the first y-value at the bottom, so ascending order in the data produces descending visual order.
Using NumPy for sorting:
categories = np.array(['Python', 'JavaScript', 'Java', 'C#', 'C++', 'Go'])
values = np.array([29.9, 19.5, 17.4, 6.8, 6.5, 4.2])
# Get indices that would sort the array
sort_idx = np.argsort(values) # ascending order
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(categories[sort_idx], values[sort_idx], color='mediumseagreen')
ax.set_xlabel('Popularity (%)')
plt.tight_layout()
plt.show()
Advanced Techniques
Grouped horizontal bars compare multiple datasets:
categories = ['Python', 'JavaScript', 'Java', 'C#', 'Go']
values_2023 = [28.5, 20.1, 18.2, 7.1, 3.8]
values_2024 = [29.9, 19.5, 17.4, 6.8, 4.2]
y_pos = np.arange(len(categories))
bar_height = 0.35
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(y_pos - bar_height/2, values_2023, bar_height, label='2023', color='lightcoral')
ax.barh(y_pos + bar_height/2, values_2024, bar_height, label='2024', color='steelblue')
ax.set_yticks(y_pos)
ax.set_yticklabels(categories)
ax.set_xlabel('Popularity (%)')
ax.set_title('Programming Language Popularity: 2023 vs 2024')
ax.legend()
plt.tight_layout()
plt.show()
Stacked horizontal bars show part-to-whole relationships:
categories = ['Project A', 'Project B', 'Project C', 'Project D']
completed = [45, 60, 30, 75]
in_progress = [25, 20, 40, 15]
planned = [30, 20, 30, 10]
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(categories, completed, label='Completed', color='#2ecc71')
ax.barh(categories, in_progress, left=completed, label='In Progress', color='#f39c12')
ax.barh(categories, planned, left=np.array(completed) + np.array(in_progress),
label='Planned', color='#95a5a6')
ax.set_xlabel('Tasks')
ax.set_title('Project Status Overview')
ax.legend(loc='lower right')
plt.tight_layout()
plt.show()
Real-World Example
Here’s a complete implementation analyzing quarterly sales by region:
# Realistic sales data
data = {
'Region': ['North America', 'Europe', 'Asia Pacific', 'Latin America',
'Middle East', 'Africa'],
'Q4_Sales': [2.4, 1.8, 3.1, 0.9, 0.7, 0.5],
'Target': [2.2, 1.9, 2.8, 1.0, 0.8, 0.6]
}
df = pd.DataFrame(data)
df['Performance'] = (df['Q4_Sales'] / df['Target'] - 1) * 100
df_sorted = df.sort_values('Q4_Sales', ascending=True)
# Create visualization
fig, ax = plt.subplots(figsize=(12, 7))
# Color bars based on performance vs target
colors = ['#2ecc71' if perf >= 0 else '#e74c3c' for perf in df_sorted['Performance']]
bars = ax.barh(df_sorted['Region'], df_sorted['Q4_Sales'], color=colors, alpha=0.8, edgecolor='black')
# Add value labels with performance indicators
for i, (region, sales, perf) in enumerate(zip(df_sorted['Region'],
df_sorted['Q4_Sales'],
df_sorted['Performance'])):
label = f'${sales}M ({perf:+.1f}%)'
ax.text(sales + 0.1, i, label, va='center', fontsize=11, fontweight='bold')
ax.set_xlabel('Sales (Millions USD)', fontsize=12, fontweight='bold')
ax.set_title('Q4 2024 Sales by Region\nGreen: Above Target | Red: Below Target',
fontsize=14, fontweight='bold', pad=20)
ax.set_xlim(0, max(df_sorted['Q4_Sales']) * 1.25)
ax.grid(axis='x', alpha=0.3, linestyle='--')
plt.tight_layout()
plt.show()
Best Practices & Tips
Choose horizontal over vertical when:
- Category labels exceed 5-6 characters (anything longer gets cramped vertically)
- You’re showing rankings or ordered lists (top-to-bottom reading is natural)
- You have more than 10 categories (horizontal scales better)
- Your audience will view on mobile devices (horizontal bars use screen width effectively)
Accessibility considerations:
- Use colorblind-friendly palettes (avoid red-green combinations alone)
- Maintain 4.5:1 contrast ratio between bars and background
- Always include data labels—don’t rely solely on axis scales
- Use texture or patterns in addition to color when distinguishing categories
Common pitfalls to avoid:
- Don’t forget to sort: Unsorted bars create cognitive load
- Don’t use 3D effects: They distort perception and look dated
- Don’t start axes at arbitrary values: Always start at zero for bar charts to avoid misleading comparisons
- Don’t overcrowd: If you have more than 20 categories, consider showing top/bottom 10 or using a different chart type
Performance tip: For datasets with hundreds of bars, use ax.barh() with arrays rather than looping—it’s orders of magnitude faster and produces cleaner code.
Horizontal bar charts are workhorses of data visualization. Master these techniques and you’ll create clear, professional charts that communicate insights instantly.