{"spec_id":"streamgraph-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# streamgraph-basic: Basic Stream Graph\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 84/100 | Created: 2026-08-05\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green, ALWAYS first series (Imprint palette)\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n]\n\n# Data — monthly streaming hours (millions) by music genre, 2024-2025\ngenres = [\"Pop\", \"Hip-Hop\", \"Rock\", \"Electronic\"]\nn_genres = length(genres)\nn_months = 24\n\nbase_level = [420.0, 260.0, 340.0, 180.0]\ntrend      = [3.5, 6.5, -4.0, 9.0]\nwave_amp   = [55.0, 35.0, 45.0, 65.0]\nwave_phase = [0.4, 2.1, 1.0, 3.4]\n\nraw_values = zeros(Float64, n_months, n_genres)\nfor g in 1:n_genres\n    for t in 1:n_months\n        seasonal = wave_amp[g] * sin(2π * t / 12 + wave_phase[g])\n        noise = randn() * base_level[g] * 0.05\n        raw_values[t, g] = max(0.0, base_level[g] + trend[g] * t + seasonal + noise)\n    end\nend\n\n# Smooth interpolation (Catmull-Rom spline) so each genre's layer flows\n# organically between the monthly samples instead of showing sharp knots.\nsteps_per_segment = 12\nn_smooth = (n_months - 1) * steps_per_segment + 1\nx_smooth = collect(range(1, n_months; length = n_smooth))\nsmooth_values = zeros(Float64, n_smooth, n_genres)\n\nfor g in 1:n_genres\n    idx = 1\n    for i in 1:(n_months - 1)\n        p0 = raw_values[max(i - 1, 1), g]\n        p1 = raw_values[i, g]\n        p2 = raw_values[i + 1, g]\n        p3 = raw_values[min(i + 2, n_months), g]\n        last_step = i == n_months - 1 ? steps_per_segment : steps_per_segment - 1\n        for s in 0:last_step\n            u = s / steps_per_segment\n            u2 = u * u\n            u3 = u2 * u\n            val = 0.5 * ((2p1) + (-p0 + p2) * u +\n                         (2p0 - 5p1 + 4p2 - p3) * u2 +\n                         (-p0 + 3p1 - 3p2 + p3) * u3)\n            smooth_values[idx, g] = max(val, 0.0)\n            idx += 1\n        end\n    end\nend\n\n# Symmetric (\"silhouette\") baseline centered on zero — the defining streamgraph trait\ntotals = vec(sum(smooth_values; dims = 2))\nbaseline = -totals ./ 2\ncum = cumsum(smooth_values; dims = 2)\nlayer_lower = zeros(Float64, n_smooth, n_genres)\nlayer_upper = zeros(Float64, n_smooth, n_genres)\nfor g in 1:n_genres\n    layer_lower[:, g] = baseline .+ (g == 1 ? zeros(n_smooth) : cum[:, g - 1])\n    layer_upper[:, g] = baseline .+ cum[:, g]\nend\n\n# Plot\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"streamgraph-basic · julia · makie · anyplot.ai\",\n    titlesize          = 30,\n    titlecolor         = INK,\n    titlefont          = :bold,\n    xlabel             = \"Month\",\n    xlabelsize         = 16,\n    xlabelcolor        = INK,\n    ylabel             = \"Streaming Hours (millions)\",\n    ylabelsize         = 16,\n    ylabelcolor        = INK,\n    xticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinevisible   = false,\n    bottomspinecolor   = INK_SOFT,\n    yticksvisible      = false,\n    yticklabelsvisible = false,\n    xgridvisible       = false,\n    ygridvisible       = false,\n)\n\nfor g in 1:n_genres\n    band!(ax, x_smooth, layer_lower[:, g], layer_upper[:, g];\n        color = IMPRINT_PALETTE[g])\nend\n\n# Thin background-colored strokes between adjacent bands sharpen layer\n# separation where two saturated fills touch directly (style guide's\n# optional outline pattern, applied here as inter-band edging).\nfor g in 1:(n_genres - 1)\n    lines!(ax, x_smooth, layer_upper[:, g]; color = PAGE_BG, linewidth = 1.5)\nend\n\nmonth_names = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n               \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nxtick_positions = [1, 7, 13, 19, 24]\nxtick_labels = String[]\nfor m in xtick_positions\n    month_idx = mod1(m, 12)\n    year = 2024 + div(m - 1, 12)\n    push!(xtick_labels, \"$(month_names[month_idx]) $(year)\")\nend\nax.xticks = (xtick_positions, xtick_labels)\nxlims!(ax, 1 - 0.8, n_months + 0.8)\n\nLegend(fig[1, 2],\n    [PolyElement(color = IMPRINT_PALETTE[g]) for g in 1:n_genres],\n    genres;\n    framevisible = false,\n    labelcolor   = INK_SOFT,\n    labelsize    = 13,\n)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}