Basic Stream Graph — plotnine

A streamgraph (also known as a stacked area chart with a centered baseline) displaying the composition of multiple categories over time with smooth, flowing curves. Unlike traditional stacked area charts, streamgraphs use a symmetric baseline centered around the x-axis, creating an organic, river-like appearance that emphasizes the overall shape and relative proportions of each category while minimizing the visual distortion of individual layers.

Basic Stream Graph rendered with plotnine

Python source (plotnine)

""" anyplot.ai
streamgraph-basic: Basic Stream Graph
Library: plotnine 0.15.3 | Python 3.13.13
Quality: 84/100 | Created: 2026-05-06
"""

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    element_text,
    geom_ribbon,
    ggplot,
    labs,
    scale_fill_manual,
    scale_x_continuous,
    theme,
    theme_minimal,
)


# Data: Monthly streaming hours by music genre over two years
np.random.seed(42)

months = np.arange(24)
genres = ["Pop", "Rock", "Hip-Hop", "Electronic", "Jazz"]
n_months = len(months)
n_genres = len(genres)

# Generate realistic streaming data with trends
data_values = {}
for i, genre in enumerate(genres):
    base = 100 + i * 20
    trend = np.sin(np.linspace(0, 4 * np.pi, n_months) + i) * 30
    noise = np.random.randn(n_months) * 10
    data_values[genre] = np.maximum(base + trend + noise, 20)

# Create wide dataframe first
df_wide = pd.DataFrame({"month": months})
for genre in genres:
    df_wide[genre] = data_values[genre]

# Calculate streamgraph positions (centered baseline)
# Stack values and compute symmetric baseline
values_matrix = df_wide[genres].values
totals = values_matrix.sum(axis=1)
baseline = -totals / 2

# Compute y positions for each genre (cumulative)
y_bottom = np.zeros((n_months, n_genres))
y_top = np.zeros((n_months, n_genres))

for i in range(n_genres):
    if i == 0:
        y_bottom[:, i] = baseline
    else:
        y_bottom[:, i] = y_top[:, i - 1]
    y_top[:, i] = y_bottom[:, i] + values_matrix[:, i]

# Create long-form dataframe for plotting
plot_data = []
for i, genre in enumerate(genres):
    for j, month in enumerate(months):
        plot_data.append({"month": month, "genre": genre, "ymin": y_bottom[j, i], "ymax": y_top[j, i]})

df_plot = pd.DataFrame(plot_data)
df_plot["genre"] = pd.Categorical(df_plot["genre"], categories=genres, ordered=True)

# Colors - harmonious palette for adjacent areas
colors = ["#306998", "#FFD43B", "#FF6B6B", "#4ECDC4", "#9B59B6"]

# Create streamgraph using geom_ribbon
plot = (
    ggplot(df_plot, aes(x="month", ymin="ymin", ymax="ymax", fill="genre"))
    + geom_ribbon(alpha=0.85)
    + scale_fill_manual(values=colors)
    + scale_x_continuous(breaks=list(range(0, 24, 6)), labels=["Jan '23", "Jul '23", "Jan '24", "Jul '24"])
    + labs(x="Month", y="Streaming Hours", title="streamgraph-basic · plotnine · pyplots.ai", fill="Genre")
    + theme_minimal()
    + theme(
        figure_size=(16, 9),
        text=element_text(size=14),
        axis_title=element_text(size=20),
        axis_text=element_text(size=16),
        plot_title=element_text(size=24),
        legend_text=element_text(size=16),
        legend_title=element_text(size=18),
    )
)

plot.save("plot.png", dpi=300)

Part of Basic Stream Graph on anyplot.ai.

Other implementations