{"spec_id":"line-retention-cohort","library":"makie","language":"julia","code":"# anyplot.ai\n# line-retention-cohort: User Retention Curve by Cohort\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-06-20\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 ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green (Jan 2025)\n    colorant\"#C475FD\",  # 2 — lavender (Feb 2025)\n    colorant\"#4467A3\",  # 3 — blue (Mar 2025)\n    colorant\"#BD8233\",  # 4 — ochre (Apr 2025)\n]\n\n# Data — monthly signup cohorts tracked weekly for 12 weeks\nconst weeks = Float64.(0:12)\nconst cohort_labels = [\n    \"Jan 2025 (n=1,248)\",\n    \"Feb 2025 (n=1,571)\",\n    \"Mar 2025 (n=2,034)\",\n    \"Apr 2025 (n=2,413)\",\n]\n\n# Long-tail retention model: a * exp(-b * t) + c, normalised so t=0 gives 100%\n# Parameters improve Jan→Apr, showing product retention gains over time\nconst a_vals = [55.0, 58.0, 62.0, 66.0]\nconst b_vals = [0.30, 0.28, 0.26, 0.24]\nconst c_vals = [13.0, 16.0, 18.0, 22.0]\n\nretention_matrix = Matrix{Float64}(undef, length(weeks), 4)\nfor i in 1:4\n    raw = a_vals[i] .* exp.(-b_vals[i] .* weeks) .+ c_vals[i]\n    retention_matrix[:, i] = raw ./ raw[1] .* 100.0\nend\n\n# Thicker lines for newer cohorts to emphasise improving retention\nconst line_widths = [2.0, 2.5, 3.0, 3.5]\n\n# Plot\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"line-retention-cohort · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Weeks Since Signup\",\n    ylabel             = \"Retention Rate (%)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n    yticks             = 0:20:100,\n    xticks             = 0:2:12,\n)\n\nax.ytickformat = vs -> [\"$(round(Int, v))%\" for v in vs]\n\nylims!(ax, 0, 108)\nxlims!(ax, 0, 12)\n\nfor i in 1:4\n    lines!(ax, weeks, retention_matrix[:, i];\n        color     = IMPRINT_PALETTE[i],\n        linewidth = line_widths[i],\n        label     = cohort_labels[i],\n    )\n    scatter!(ax, weeks, retention_matrix[:, i];\n        color       = IMPRINT_PALETTE[i],\n        markersize  = 10,\n        strokewidth = 1,\n        strokecolor = PAGE_BG,\n    )\nend\n\n# Reference line at 20% retention threshold\nhlines!(ax, [20.0];\n    color     = INK_MUTED,\n    linewidth = 1.5,\n    linestyle = :dash,\n)\ntext!(ax, \"20% target\";\n    position = (11.5, 22.0),\n    align    = (:right, :bottom),\n    fontsize = 12,\n    color    = INK_MUTED,\n)\n\naxislegend(ax;\n    title           = \"Signup Cohort\",\n    titlecolor      = INK,\n    titlesize       = 12,\n    position        = :rt,\n    backgroundcolor = ELEVATED_BG,\n    framecolor      = INK_SOFT,\n    framevisible    = true,\n    framewidth      = 0.8,\n    labelcolor      = INK,\n    labelsize       = 12,\n    rowgap          = 4,\n    padding         = (8, 8, 6, 6),\n    margin          = (4, 4, 4, 4),\n)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}