How to Create a Stacked Bar Chart in Matplotlib (Python)

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()
Matplotlib stacked bar chart with online sales at the bottom and retail sales stacked on top for four quarters
Retail bars start where the online bars end, because 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.]
Matplotlib stacked bar chart with four sales channels stacked for each quarter using a for loop and a running bottom array
Four series stacked with one loop.
Command Prompt output of the for loop printing the running bottom array after each series is stacked
The running 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()
Stacked bar chart in Matplotlib with white value labels in the middle of each segment and bold totals above each bar
Segment values in the middle, totals on top.

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()
Matplotlib 100 percent stacked bar chart showing the share of online, retail and wholesale sales per quarter with percentage labels
Each quarter adds up to 100%; the legend sits outside the plot.

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()
Horizontal stacked bar chart made with Matplotlib barh showing agree, neutral and disagree survey answers for four teams
Survey answers per team as a horizontal stacked bar chart.

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()
pandas DataFrame plotted as a stacked bar chart with df.plot(kind='bar', stacked=True) and labels inside each segment
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()
Matplotlib stacked bar chart with positive values stacked above zero and negative values stacked below the zero line
Positive segments stack upwards and negative segments downwards from zero.

Tips for readable stacked bar charts

  • Keep it to about five segments or fewer; with more, only the bottom segment is easy to compare.
  • Put the most important or most stable series at the bottom, since it shares a common baseline.
  • Use a consistent colour order in every chart, and a light-to-dark or diverging palette for ordered answers.
  • Add totals on top when the total is the main message, and percentages when the mix is.

Continue with these related Matplotlib bar chart guides:

Frequently asked questions

How do I make a stacked bar chart in Matplotlib?

Draw the first series with plt.bar(x, a), then the next with plt.bar(x, b, bottom=a). For more series, keep a running NumPy array of the heights so far and pass it as bottom.

How do I create a stacked bar chart with a for loop?

Start with bottom = np.zeros(len(x)), and inside the loop call ax.bar(x, values, bottom=bottom) followed by bottom += values.

How do I add labels to a stacked bar chart?

Call ax.bar_label(bars, label_type="center") on each series for the segment values, and ax.bar_label() with custom labels= on the top series for totals.

How do I make a horizontal stacked bar chart?

Use ax.barh(categories, values, left=left) and increase left after each series. In pandas, use df.plot(kind="barh", stacked=True).

How do I plot a stacked bar chart from a pandas DataFrame?

Call df.plot(kind="bar", stacked=True). Each row becomes a bar and each column a stacked segment.

How do I handle negative values in a stacked bar chart?

Keep separate running totals for positive and negative values and use np.where(values >= 0, pos_bottom, neg_bottom) as the bottom of each segment.