To create a stacked bar chart in Matplotlib, draw the first series with plt.bar(), then draw each next series with the bottom argument set to the height of everything below it. For a horizontal stacked bar chart, use plt.barh() with left instead. This guide builds stacked bar charts in Python step by step: two series, many series in a for loop, value labels and totals, 100% stacked bars, horizontal bars, pandas DataFrames and negative values.
Tested with Python 3.12.5, Matplotlib 3.11.2, NumPy 2.5.3 and pandas 3.0.6; every chart is a screenshot of the real Matplotlib window. See the official Matplotlib stacked bar chart example and the pyplot.bar reference for all options.
Basic stacked bar chart with bottom=
import matplotlib.pyplot as plt
quarters = ["Q1", "Q2", "Q3", "Q4"]
online = [120, 150, 170, 210]
retail = [200, 190, 185, 220]
plt.bar(quarters, online, label="Online")
plt.bar(quarters, retail, bottom=online, label="Retail") # bottom= starts each bar on top of the first one
plt.ylabel("Sales (thousand USD)")
plt.title("Sales by channel")
plt.legend()
plt.show()

bottom=online.Stacked bars are best when the total matters as much as the parts. If you want to compare the series with each other rather than the total, use side-by-side (grouped) bars instead.
Stacked bar chart with a for loop
With three or more series, keep a running bottom array and add each series to it inside a loop. A NumPy array makes the addition element-wise:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = {
"Online": [120, 150, 170, 210],
"Retail": [200, 190, 185, 220],
"Wholesale": [80, 95, 110, 105],
"Partners": [40, 55, 60, 75],
}
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for channel, values in sales.items():
ax.bar(quarters, values, bottom=bottom, label=channel)
bottom += values # the next series starts where this one ends
print(f"{channel:<10} bottom after stacking: {bottom}")
ax.set_ylabel("Sales (thousand USD)")
ax.legend(loc="upper left")
plt.tight_layout()
plt.show()
Output:
Online bottom after stacking: [120. 150. 170. 210.]
Retail bottom after stacking: [320. 340. 355. 430.]
Wholesale bottom after stacking: [400. 435. 465. 535.]
Partners bottom after stacking: [440. 490. 525. 610.]


bottom after each series, printed in the Command Prompt.Using a plain Python list for bottom would not work: list += list joins the lists instead of adding the numbers.
Add value labels and totals to a stacked bar chart
ax.bar_label() (Matplotlib 3.4+) writes the values on a set of bars. Use label_type="center" for the segments, then label the top series once more with the totals:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = {"Online": [120, 150, 170, 210], "Retail": [200, 190, 185, 220], "Wholesale": [80, 95, 110, 105]}
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for channel, values in sales.items():
bars = ax.bar(quarters, values, bottom=bottom, label=channel)
ax.bar_label(bars, label_type="center", color="white", fontsize=9) # value inside each segment
bottom += values
ax.bar_label(bars, labels=[f"{t:.0f}" for t in bottom], padding=3, fontweight="bold") # total on top
ax.set_ylim(0, bottom.max() * 1.1)
ax.set_ylabel("Sales (thousand USD)")
ax.legend(loc="upper left")
plt.tight_layout()
plt.show()

100% stacked bar chart (percentages)
To compare shares instead of amounts, divide every column by its total so each bar reaches 100:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = np.array([[120, 150, 170, 210], # Online
[200, 190, 185, 220], # Retail
[80, 95, 110, 105]]) # Wholesale
channels = ["Online", "Retail", "Wholesale"]
share = sales / sales.sum(axis=0) * 100 # each column now adds up to 100
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for name, row in zip(channels, share):
bars = ax.bar(quarters, row, bottom=bottom, label=name)
ax.bar_label(bars, labels=[f"{v:.0f}%" for v in row], label_type="center", color="white")
bottom += row
ax.set_ylabel("Share of sales (%)")
ax.legend(loc="upper left", bbox_to_anchor=(1, 1))
plt.tight_layout()
plt.show()

Horizontal stacked bar chart
barh() draws horizontal bars. Stack them with left= instead of bottom=. This layout suits long category names and survey results:
import matplotlib.pyplot as plt
import numpy as np
teams = ["Support", "Sales", "Engineering", "Marketing"]
answers = {"Agree": [45, 38, 52, 30], "Neutral": [30, 35, 28, 40], "Disagree": [25, 27, 20, 30]}
colors = ["#2e7d32", "#bdbdbd", "#c62828"]
fig, ax = plt.subplots(figsize=(8, 3.8))
left = np.zeros(len(teams))
for (answer, values), color in zip(answers.items(), colors):
bars = ax.barh(teams, values, left=left, label=answer, color=color) # barh stacks with left=
ax.bar_label(bars, label_type="center", fmt="%d%%")
left += values
ax.invert_yaxis() # first team at the top
ax.set_xlabel("Responses (%)")
ax.legend(ncols=3, loc="lower center", bbox_to_anchor=(0.5, 1.0))
plt.tight_layout()
plt.show()

Stacked bar chart from a pandas DataFrame
With pandas, one line does the stacking: df.plot(kind="bar", stacked=True). Each row becomes a bar and each column a segment. Use kind="barh" for horizontal bars.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"quarter": ["Q1", "Q2", "Q3", "Q4"],
"Online": [120, 150, 170, 210],
"Retail": [200, 190, 185, 220],
"Wholesale": [80, 95, 110, 105],
}).set_index("quarter")
ax = df.plot(kind="bar", stacked=True, figsize=(7, 4.5), rot=0) # one stacked bar per row
for container in ax.containers:
ax.bar_label(container, label_type="center", color="white", fontsize=9)
ax.set_ylabel("Sales (thousand USD)")
plt.tight_layout()
plt.show()

df.plot(kind="bar", stacked=True) with labels from ax.containers.Stacked bar chart with negative values
If you pass negative numbers with a single running bottom, segments overlap. Keep two running totals, one for positive values stacking up and one for negative values stacking down:
import matplotlib.pyplot as plt
import numpy as np
months = ["Jan", "Feb", "Mar", "Apr", "May"]
data = {"Product A": np.array([30, -10, 25, -15, 20]),
"Product B": np.array([15, 20, -20, 10, -5]),
"Product C": np.array([-5, 10, 15, -10, 25])}
fig, ax = plt.subplots(figsize=(7, 4.5))
pos_bottom = np.zeros(len(months))
neg_bottom = np.zeros(len(months))
for name, values in data.items():
bottom = np.where(values >= 0, pos_bottom, neg_bottom) # positives stack up, negatives stack down
ax.bar(months, values, bottom=bottom, label=name)
pos_bottom += np.clip(values, 0, None)
neg_bottom += np.clip(values, None, 0)
ax.axhline(0, color="black", linewidth=0.8)
ax.set_ylabel("Profit change (thousand USD)")
ax.legend(ncols=3, loc="lower center", bbox_to_anchor=(0.5, 1.0))
plt.tight_layout()
plt.show()

Tips for readable stacked bar charts
- Keep it to about five segments or fewer; with more, only the bottom segment is easy to com