{"spec_id":"spiral-timeseries","library":"makie","language":"julia","code":"# anyplot.ai\n# spiral-timeseries: Spiral Time Series Chart\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-09-09\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\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\"\nconst IMPRINT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\n\n# --- Data: daily retail sales over 4 years, one revolution per year --------\nn_years = 4\ndays_per_year = 365\nn_days = n_years * days_per_year\n\nday_index = 0:(n_days-1)\nday_of_year = mod.(day_index, days_per_year)\n\ntrend = 1.0 .+ 0.05 .* (day_index ./ days_per_year)\nweekly_pattern = 0.10 .* sin.(2π .* day_index ./ 7)\nholiday_peak = 0.65 .* exp.(-((day_of_year .- 350) .^ 2) ./ (2 * 16^2))\nback_to_school = 0.20 .* exp.(-((day_of_year .- 240) .^ 2) ./ (2 * 20^2))\nnoise = 3.0 .* randn(n_days)\n\ndaily_sales = 40.0 .* trend .* (1.0 .+ holiday_peak .+ back_to_school .+ weekly_pattern) .+ noise\ndaily_sales = max.(daily_sales, 5.0)\n\n# --- Spiral geometry (Archimedean: constant radial spacing per revolution) -\ntheta = 2π .* day_index ./ days_per_year        # continuously increasing angle\nr_inner = 0.35\nr_growth_per_cycle = 0.62 / n_years\nradius = r_inner .+ r_growth_per_cycle .* (theta ./ 2π)\n\nphi = theta .+ π / 2                            # rotate so each cycle starts at the top\nx = radius .* cos.(phi)\ny = radius .* sin.(phi)\n\n# --- Figure -------------------------------------------------------------------\nfig = Figure(resolution=(1200, 1200), fontsize=14, backgroundcolor=PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title=\"spiral-timeseries · julia · makie · anyplot.ai\",\n    titlesize=20,\n    titlecolor=INK,\n    backgroundcolor=PAGE_BG,\n    aspect=DataAspect(),\n)\nhidedecorations!(ax)\nhidespines!(ax)\n\n# Radial grid lines mark month boundaries within each yearly cycle\nr_outer = maximum(radius) + 0.05\nmonth_days = round.(Int, range(0, days_per_year, length=13))[1:12]\nfor d in month_days\n    ang = 2π * d / days_per_year + π / 2\n    lines!(\n        ax,\n        [r_inner * cos(ang), r_outer * cos(ang)],\n        [r_inner * sin(ang), r_outer * sin(ang)];\n        color=(INK_SOFT, 0.25),\n        linewidth=1,\n    )\nend\n\n# Spiral line — color encodes daily sales magnitude. The range is clamped to\n# the 5th-95th percentile (rather than full min/max) so day-to-day variation\n# stays visible instead of being washed out by the single yearly holiday spike.\ncolor_range = (quantile(daily_sales, 0.05), quantile(daily_sales, 0.95))\nlines!(\n    ax, x, y;\n    color=daily_sales,\n    colormap=IMPRINT_SEQ,\n    colorrange=color_range,\n    linewidth=3.5,\n)\n\n# Label the start of each yearly cycle for orientation\nfor cycle in 0:(n_years-1)\n    label_radius = r_inner + r_growth_per_cycle * cycle - 0.05\n    text!(\n        ax,\n        0.0,\n        label_radius;\n        text=\"Year $(cycle + 1)\",\n        color=INK_SOFT,\n        fontsize=13,\n        align=(:center, :center),\n    )\nend\n\nColorbar(\n    fig[1, 2],\n    limits=color_range,\n    colormap=IMPRINT_SEQ,\n    label=\"Daily sales (USD thousands)\",\n    labelcolor=INK,\n    ticklabelcolor=INK_SOFT,\n    width=18,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit=2)\n"}