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:

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:
| Rep | Q1 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()

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

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

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()

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

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()

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:
- Labels on a multiple bar chart
- Overlay two bar charts in Matplotlib
- Bar charts with error bars
- Stacked bar plots with pandas
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?

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn