Matplotlib Horizontal Bar Chart in Python (barh Step by Step)

A sales manager’s chart of seven reps had full names colliding under vertical bars. A Matplotlib horizontal bar chart fixed it: ax.barh() puts each name on the y-axis, where it’s got room.

fig, ax = plt.subplots()
ax.barh(reps, sales)    # labels on y, values on x
plt.show()

In this tutorial, I’ll build a Matplotlib horizontal bar chart step by step on Python 3.12.5 and Matplotlib 3.11.2, then handle negative values.

Here’s what we’re building:

Matplotlib horizontal bar chart of Q1 2026 sales for seven reps, sorted with the top rep highlighted in dark blue and dollar labels on every bar
The finished chart: sorted, labeled in USD, top rep highlighted.

Before You Start: the sales data for the horizontal bar chart

You’ll need Matplotlib (pip install matplotlib) and this sample of Q1 2026 sales:

RepQ1 2026 sales (USD)
Hannah Brooks$48,250.75
Marcus Reed$61,930.40
Olivia Turner$39,875.10
Ethan Walsh$72,410.95
Sofia Ramirez$55,120.00
Caleb Morgan$44,680.35
Natalie Price$67,045.80

Short labels or a time axis? Then a vertical Matplotlib bar chart fits better.

Step 1: Draw the bars with barh()

barh(y, width) takes the labels first and the bar lengths second, just like bar().

import matplotlib.pyplot as plt

reps = ["Hannah Brooks", "Marcus Reed", "Olivia Turner", "Ethan Walsh",
        "Sofia Ramirez", "Caleb Morgan", "Natalie Price"]
sales = [48250.75, 61930.40, 39875.10, 72410.95,
         55120.00, 44680.35, 67045.80]   # Q1 2026, USD

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.barh(reps, sales)
ax.set_xlabel("Sales (USD)")
ax.set_title("Q1 2026 sales by rep")
plt.show()
Basic Matplotlib barh chart with seven sales reps on the y-axis and Hannah Brooks at the bottom
barh() drew the reps in list order from the bottom up.

Checkpoint: Hannah Brooks, first in the list, sits at the bottom because the y-axis counts upward. Her name’s clipped at the left edge too; Step 4 fixes that.

Step 2: Sort the bars so the largest is on top

Sort smallest-first, since the last item ends up on top:

sales, reps = zip(*sorted(zip(sales, reps)))   # smallest first
# then the same plotting code as Step 1
Matplotlib horizontal bar chart sorted by sales with Ethan Walsh's longest bar at the top
Sorted ascending, the longest bar sits on top.

Checkpoint: Ethan Walsh ($72,410.95) is on top and Olivia Turner ($39,875.10) at the bottom.

Data already sorted largest-first? Call ax.invert_yaxis() instead; see inverting the y-axis in Matplotlib.

Step 3: Add dollar value labels to each bar

ax.bar_label() writes each value at the bar’s tip. The {}-style fmt needs Matplotlib 3.7+:

bars = ax.barh(reps, sales)
ax.bar_label(bars, fmt="${:,.2f}", padding=4)
ax.set_xlim(0, 85000)              # room for the labels
Matplotlib horizontal bar chart with dollar value labels such as $72,410.95 at the end of each bar
Each bar shows its exact sales figure in USD.

Checkpoint: the top label reads $72,410.95. Without set_xlim(), it poked past the right edge of the axes.

Labels inside the bars are covered in Matplotlib bar charts with values.

Step 4: Style the chart and highlight the top rep

Thinner bars use height, not width. Add one highlight color, dollar ticks and a grid behind the bars:

import matplotlib.pyplot as plt

# reps and sales from Step 1, sorted as in Step 2

top = max(sales)
colors = ["#1f6fb2" if s == top else "#a9c4de" for s in sales]

fig, ax = plt.subplots(figsize=(8, 4.5), layout="constrained")
bars = ax.barh(reps, sales, height=0.6, color=colors)
ax.bar_label(bars, fmt="${:,.2f}", padding=4)
ax.set_xlim(0, 85000)
ax.xaxis.set_major_formatter("${x:,.0f}")
ax.xaxis.grid(True, linestyle="--", alpha=0.5)
ax.set_axisbelow(True)                 # grid behind the bars
ax.spines[["top", "right"]].set_visible(False)
ax.set_title("Q1 2026 sales by rep")
plt.show()
Styled Matplotlib horizontal bar chart with the top rep Ethan Walsh highlighted, dollar tick labels and dashed vertical gridlines
Only the top bar is dark blue, and full names fit.

Checkpoint: only Ethan Walsh is dark blue, and layout="constrained" made room for every name.

Want a color per bar? Read Matplotlib bar chart colors.

Test the horizontal bar chart: save it and check the numbers

I save the chart and print what was drawn (VS Code run):

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

# reps and sales from Step 1
# sort + styling from Step 4
fig.savefig("q1-2026-sales-by-rep.png", dpi=150)

print("Bars drawn:", len(bars))
print("Top bar:", reps[-1], f"${sales[-1]:,.2f}")
print("Bottom bar:", reps[0], f"${sales[0]:,.2f}")
print(f"Team total: ${sum(sales):,.2f}")
print("Saved q1-2026-sales-by-rep.png")

Output:

Bars drawn: 7
Top bar: Ethan Walsh $72,410.95
Bottom bar: Olivia Turner $39,875.10
Team total: $389,313.35
Saved q1-2026-sales-by-rep.png
VS Code output confirming 7 bars, Ethan Walsh on top, Olivia Turner at the bottom and a team total of $389,313.35
Seven bars, the right rep on top, PNG saved.

The $389,313.35 total matches the table, so sorting didn’t drop anything.

Variant: show negative values against a quota

Compare each rep with a $50,000.00 quota. Negative widths extend left of zero, so you’ll just add a color per sign and a zero line:

import matplotlib.pyplot as plt

# reps and sales from Step 1
sales, reps = zip(*sorted(zip(sales, reps)))   # smallest first
quota = 50000.00
diff = [s - quota for s in sales]
colors = ["#2e8b57" if d >= 0 else "#d9534f" for d in diff]
labels = [f"+${d:,.2f}" if d >= 0 else f"-${-d:,.2f}" for d in diff]

fig, ax = plt.subplots(figsize=(8, 4.5), layout="constrained")
bars = ax.barh(reps, diff, color=colors)
ax.bar_label(bars, labels=labels, padding=4)
ax.axvline(0, color="black", linewidth=0.8)
ax.set_xlim(-18000, 30000)
ax.set_xlabel("Q1 2026 sales vs $50,000.00 quota (USD)")
plt.show()
Matplotlib diverging horizontal bar chart of sales versus a $50,000.00 quota with green bars above quota and red bars below
Three reps missed quota (red); four beat it (green).

Olivia Turner missed by $10,124.90, while Ethan Walsh beat it by $22,410.95.

Troubleshooting: why is my horizontal bar chart not working?

TypeError on width. In barh(), width is the bar length, so a width= keyword clashes with your values:

try:
    ax.barh(reps, sales, width=0.5)
except TypeError as err:
    print("TypeError:", err)
ax.barh(reps, sales, height=0.5)   # bar thickness
print("height=0.5 works")

Output:

TypeError: Axes.barh() got multiple values for argument 'width'
height=0.5 works

Names cut off. Use layout="constrained" or fig.tight_layout(); see Matplotlib tight_layout().

Important: sort labels and values together with zip(), never one list alone.

Where to Take It Next with horizontal bars

Stack quarters with left=, as in stacked bar charts in Matplotlib, or try a multiple bar chart.

A few more bar chart guides worth a look:

Frequently asked questions

What is the difference between bar() and barh()?

bar() draws vertical bars, barh() horizontal ones.

Why is my first category at the bottom of a barh chart?

The y-axis counts upward. Sort ascending or call ax.invert_yaxis().

How do I change the bar thickness?

Pass height, like height=0.6. The default is 0.8.

How do I add values to a horizontal bar chart?

Call ax.bar_label(bars) on the container ax.barh() returns.

Can barh() show negative values?

Yes. Negative widths extend left of zero.

Where is barh() documented?

Leave a Comment