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 of bar() 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.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.