{"spec_id":"streamgraph-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nstreamgraph-basic: Basic Stream Graph\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom scipy.interpolate import make_interp_spline\n\n\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 = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — monthly streaming hours by music genre over 2 years\nnp.random.seed(42)\n\nmonths = pd.date_range(\"2023-01\", periods=24, freq=\"ME\")\ngenres = [\"Pop\", \"Rock\", \"Hip-Hop\", \"Electronic\", \"Classical\", \"Jazz\"]\n\ndata = {}\nfor i, genre in enumerate(genres):\n    base = [40, 35, 50, 30, 15, 12][i]\n    trend = np.linspace(0, [10, -5, 15, 8, 2, 5][i], 24)\n    seasonal = 5 * np.sin(np.linspace(0, 4 * np.pi, 24) + i)\n    noise = np.random.randn(24) * 3\n    data[genre] = np.maximum(base + trend + seasonal + noise, 5)\n\ndf = pd.DataFrame(data, index=months)\n\n# Streamgraph: centered baseline\nvalues = df.values\ncumsum = np.cumsum(values, axis=1)\ntotal = cumsum[:, -1]\nbaseline = -total / 2\n\nlowers = np.column_stack([baseline + cumsum[:, i] - values[:, i] for i in range(len(genres))])\nuppers = np.column_stack([baseline + cumsum[:, i] for i in range(len(genres))])\n\n# Smooth spline interpolation for flowing curves\nx_numeric = np.arange(len(months), dtype=float)\nx_smooth = np.linspace(0, len(months) - 1, 400)\n\n# Plot — landscape 3200×1800 px (figsize=(8, 4.5) × dpi=400, no bbox_inches='tight')\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Store splines to reuse for trend lines and annotations\nsplines = []\nfor i in range(len(genres)):\n    spl_lower = make_interp_spline(x_numeric, lowers[:, i], k=3)\n    spl_upper = make_interp_spline(x_numeric, uppers[:, i], k=3)\n    splines.append((spl_lower, spl_upper))\n    ax.fill_between(\n        x_smooth,\n        spl_lower(x_smooth),\n        spl_upper(x_smooth),\n        label=genres[i],\n        color=IMPRINT[i],\n        alpha=0.85,\n        edgecolor=PAGE_BG,\n        linewidth=0.5,\n    )\n\n# Seaborn-native center-line trend highlights for Hip-Hop and Rock\n# sns.lineplot adds a genuine seaborn plotting element over the streams.\n# Drawn in INK_SOFT (not the stream's own color) so the dashed overlay stays\n# visible against its same-hue band instead of blending in.\nfor gname, linestyle in [(\"Hip-Hop\", (0, (6, 3))), (\"Rock\", (0, (6, 3)))]:\n    gi = genres.index(gname)\n    spl_lo, spl_up = splines[gi]\n    center_vals = (spl_lo(x_smooth) + spl_up(x_smooth)) / 2\n    center_df = pd.DataFrame({\"x\": x_smooth, \"y\": center_vals})\n    sns.lineplot(\n        data=center_df,\n        x=\"x\",\n        y=\"y\",\n        ax=ax,\n        color=INK_SOFT,\n        linewidth=1.5,\n        linestyle=linestyle,\n        alpha=0.85,\n        legend=False,\n    )\n\n# Data storytelling: annotate the two dominant narrative threads\nhip_hop_idx = genres.index(\"Hip-Hop\")\nrock_idx = genres.index(\"Rock\")\n\n# Hip-Hop center near month 20 (growth is visible by then)\nhh_x = 20\nhh_center = (splines[hip_hop_idx][0](hh_x) + splines[hip_hop_idx][1](hh_x)) / 2\nax.annotate(\n    \"Hip-Hop\\nrising ↑\",\n    xy=(hh_x, hh_center),\n    xytext=(hh_x - 4.5, hh_center + 38),\n    fontsize=11,\n    fontweight=\"bold\",\n    color=IMPRINT[hip_hop_idx],\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 1.2},\n    bbox={\"boxstyle\": \"round,pad=0.35\", \"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.85},\n)\n\n# Rock center near month 18 (decline well established)\nrk_x = 18\nrk_center = (splines[rock_idx][0](rk_x) + splines[rock_idx][1](rk_x)) / 2\nax.annotate(\n    \"Rock\\ndeclining ↓\",\n    xy=(rk_x, rk_center),\n    xytext=(rk_x - 5.5, rk_center - 42),\n    fontsize=11,\n    fontweight=\"bold\",\n    color=IMPRINT[rock_idx],\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 1.2},\n    bbox={\"boxstyle\": \"round,pad=0.35\", \"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.85},\n)\n\n# Style\ntick_positions = [0, 4, 8, 12, 16, 20, 23]\ntick_labels = [months[i].strftime(\"%b '%y\") for i in tick_positions]\nax.set_xticks(tick_positions)\nax.set_xticklabels(tick_labels, fontsize=8, color=INK_SOFT)\n\nax.set_xlim(0, len(months) - 1)\nax.set_yticks([])\nax.set_ylabel(\"\")\nax.set_xlabel(\"Month (2023–2024)\", fontsize=10, color=INK)\nax.set_title(\"streamgraph-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\n\nax.legend(\n    loc=\"center left\",\n    bbox_to_anchor=(1.01, 0.5),\n    fontsize=8,\n    title=\"Genre\",\n    title_fontsize=8,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    framealpha=0.9,\n)\n\nsns.despine(ax=ax, left=True, bottom=False)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nfig.subplots_adjust(left=0.06, right=0.85, top=0.90, bottom=0.14)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}