Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 68 additions & 39 deletions plots/streamgraph-basic/implementations/python/plotly.py
Original file line number Diff line number Diff line change
@@ -1,103 +1,132 @@
""" pyplots.ai
""" anyplot.ai
streamgraph-basic: Basic Stream Graph
Library: plotly 6.5.0 | Python 3.13.11
Quality: 92/100 | Created: 2025-12-23
Library: plotly 6.7.0 | Python 3.13.13
Quality: 88/100 | Updated: 2026-05-05
"""

import sys


sys.path.pop(0)

import os

import numpy as np
import pandas as pd
import plotly.graph_objects as go


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"

# Okabe-Ito palette — first series always #009E73
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9"]

# Data - Monthly streaming hours by music genre over 2 years
np.random.seed(42)
months = pd.date_range(start="2022-01-01", periods=24, freq="ME")
genres = ["Pop", "Rock", "Hip-Hop", "Electronic", "Jazz", "Classical"]

# Generate smooth, realistic streaming data
base_values = {"Pop": 45, "Rock": 35, "Hip-Hop": 40, "Electronic": 25, "Jazz": 15, "Classical": 12}
data = {}
for genre in genres:
# Create smooth trends with seasonal variation
trend = np.cumsum(np.random.randn(24) * 2)
seasonal = 5 * np.sin(np.linspace(0, 4 * np.pi, 24))
noise = np.random.randn(24) * 3
values = base_values[genre] + trend + seasonal + noise
values = np.maximum(values, 5) # Ensure positive values
values = np.maximum(values, 5)
data[genre] = values

df = pd.DataFrame(data, index=months)
month_labels = months.strftime("%Y-%m").tolist()

# Calculate streamgraph layout (centered baseline)
values_array = df.values.T # Shape: (n_genres, n_time_points)
n_genres, n_time = values_array.shape

# Calculate cumulative sums for stacking
cumsum = np.vstack([np.zeros(n_time), np.cumsum(values_array, axis=0)])

# Center the baseline symmetrically around the x-axis
total = cumsum[-1]
offset = total / 2

# Colors - Python Blue and Yellow first, then colorblind-safe palette
colors = ["#306998", "#FFD43B", "#E24A33", "#8EBA42", "#988ED5", "#348ABD"]

# Create figure
# Plot
fig = go.Figure()

# Add each genre as a filled area with smooth spline curves
for i, genre in enumerate(genres):
y_lower = cumsum[i] - offset
y_upper = cumsum[i + 1] - offset

# Create coordinates for fill (forward then backward)
x_fill = list(months) + list(months)[::-1]
x_fill = month_labels + month_labels[::-1]
y_fill = list(y_upper) + list(y_lower)[::-1]

# Dominant stream (Pop) at full opacity; others slightly dimmed for visual hierarchy
opacity = 1.0 if i == 0 else 0.80

fig.add_trace(
go.Scatter(
x=x_fill,
y=y_fill,
fill="toself",
fillcolor=colors[i],
line={"color": colors[i], "width": 0.5, "shape": "spline", "smoothing": 1.0},
fillcolor=OKABE_ITO[i],
opacity=opacity,
line={"color": OKABE_ITO[i], "width": 0.5, "shape": "spline", "smoothing": 1.0},
name=genre,
mode="none",
hoverinfo="name+x",
hoveron="fills",
)
)

# Update layout for 4800x2700 px
subtitle = f"<span style='font-size:18px;color:{INK_SOFT}'>Monthly streaming hours by music genre, 2022–2023</span>"

fig.update_layout(
title={"text": "streamgraph-basic · plotly · pyplots.ai", "font": {"size": 36}, "x": 0.5, "xanchor": "center"},
title={
"text": f"streamgraph-basic · plotly · anyplot.ai<br>{subtitle}",
"font": {"size": 28, "color": INK},
"x": 0.5,
"xanchor": "center",
},
xaxis={
"title": {"text": "Month", "font": {"size": 28}},
"tickfont": {"size": 22},
"showgrid": True,
"gridcolor": "rgba(128,128,128,0.2)",
"gridwidth": 1,
"title": {"text": "Month", "font": {"size": 22, "color": INK}},
"tickfont": {"size": 18, "color": INK_SOFT},
"showgrid": False,
"showline": True,
"linecolor": INK_SOFT,
"mirror": False, # bottom spine only — no top spine
"zeroline": False,
},
yaxis={
"title": {"text": "Streaming Hours (Millions)", "font": {"size": 28}},
"tickfont": {"size": 22},
"showticklabels": False, # hide confusing negative offset values
"showgrid": True,
"gridcolor": "rgba(128,128,128,0.2)",
"gridcolor": GRID,
"gridwidth": 1,
"zeroline": True,
"zerolinecolor": "rgba(128,128,128,0.3)",
"zerolinecolor": INK_SOFT,
"zerolinewidth": 1,
"showline": False, # no left spine (y labels hidden anyway)
"mirror": False,
},
legend={
"font": {"size": 18, "color": INK_SOFT},
"bgcolor": ELEVATED_BG,
"bordercolor": INK_SOFT,
"borderwidth": 1,
"orientation": "h",
"yanchor": "top",
"y": -0.12,
"xanchor": "center",
"x": 0.5,
},
legend={"font": {"size": 22}, "orientation": "h", "yanchor": "bottom", "y": 1.02, "xanchor": "center", "x": 0.5},
template="plotly_white",
plot_bgcolor="white",
paper_bgcolor="white",
paper_bgcolor=PAGE_BG,
plot_bgcolor=PAGE_BG,
hovermode="x unified",
margin={"l": 100, "r": 50, "t": 120, "b": 80},
margin={"l": 60, "r": 50, "t": 140, "b": 120},
)

# Save as PNG (4800x2700 px)
fig.write_image("plot.png", width=1600, height=900, scale=3)

# Save interactive HTML
fig.write_html("plot.html", include_plotlyjs="cdn")
# Save
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")
Loading
Loading