{"spec_id":"streamgraph-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nstreamgraph-basic: Basic Stream Graph\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 91/100 | Created: 2026-08-05\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_ribbon,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_x_continuous,\n    theme,\n    theme_minimal,\n)\nfrom scipy.interpolate import make_interp_spline\n\n\n# Theme-adaptive chrome tokens (Imprint palette)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data: monthly streaming hours by music genre over two years\nnp.random.seed(42)\n\nmonths = np.arange(24)\ngenres = [\"Pop\", \"Hip-Hop\", \"Electronic\", \"Rock\", \"Jazz\"]\nn_months = len(months)\nn_genres = len(genres)\n\ndata_values = {}\nfor i, genre in enumerate(genres):\n    base = 100 + i * 20\n    trend = np.sin(np.linspace(0, 4 * np.pi, n_months) + i) * 30\n    noise = np.random.randn(n_months) * 10\n    data_values[genre] = np.maximum(base + trend + noise, 20)\n\nvalues_matrix = np.column_stack([data_values[g] for g in genres])\ntotals = values_matrix.sum(axis=1)\nbaseline = -totals / 2\n\ny_bottom = np.zeros((n_months, n_genres))\ny_top = np.zeros((n_months, n_genres))\nfor i in range(n_genres):\n    y_bottom[:, i] = baseline if i == 0 else y_top[:, i - 1]\n    y_top[:, i] = y_bottom[:, i] + values_matrix[:, i]\n\n# Upsample onto a fine grid with a cubic B-spline for the organic, flowing\n# streamgraph curve the spec requires (geom_ribbon otherwise draws straight\n# segments between the 24 monthly points).\nmonths_fine = np.linspace(months.min(), months.max(), 240)\nplot_data = []\nfor i, genre in enumerate(genres):\n    bottom_smooth = make_interp_spline(months, y_bottom[:, i], k=3)(months_fine)\n    top_smooth = make_interp_spline(months, y_top[:, i], k=3)(months_fine)\n    for month_val, ymin, ymax in zip(months_fine, bottom_smooth, top_smooth, strict=True):\n        plot_data.append({\"month\": month_val, \"genre\": genre, \"ymin\": ymin, \"ymax\": ymax})\n\ndf_plot = pd.DataFrame(plot_data)\ndf_plot[\"genre\"] = pd.Categorical(df_plot[\"genre\"], categories=genres, ordered=True)\n\ntitle = \"streamgraph-basic · python · plotnine · anyplot.ai\"\n\nanyplot_theme = theme(\n    figure_size=(8, 4.5),\n    text=element_text(size=7),\n    axis_title=element_text(size=10, color=INK),\n    axis_text=element_text(size=8, color=INK_SOFT),\n    axis_title_y=element_blank(),\n    axis_text_y=element_blank(),\n    plot_title=element_text(size=12, color=INK),\n    legend_text=element_text(size=8, color=INK_SOFT),\n    legend_title=element_text(size=9, color=INK),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_border=element_blank(),\n    panel_grid=element_blank(),\n    axis_ticks=element_blank(),\n    axis_line=element_blank(),\n    legend_background=element_rect(fill=ELEVATED_BG, color=ELEVATED_BG),\n    legend_key=element_rect(fill=ELEVATED_BG, color=ELEVATED_BG),\n)\n\nplot = (\n    ggplot(df_plot, aes(x=\"month\", ymin=\"ymin\", ymax=\"ymax\", fill=\"genre\"))\n    + geom_ribbon(alpha=0.9)\n    + scale_fill_manual(values=IMPRINT_PALETTE)\n    + scale_x_continuous(breaks=list(range(0, 24, 6)), labels=[\"Jan '23\", \"Jul '23\", \"Jan '24\", \"Jul '24\"])\n    + labs(x=\"Month\", y=\"\", title=title, fill=\"Genre\")\n    + theme_minimal()\n    + anyplot_theme\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}