{"spec_id":"windrose-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# windrose-basic: Wind Rose Chart\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-08-05\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -------------------------------------------------------------\nTHEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nINK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nGRID        = RGBAf(red(INK), green(INK), blue(INK), 0.15)\n\nIMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data ---------------------------------------------------------------------\n# Simulated year of hourly wind observations at a coastal weather station,\n# with a prevailing southwesterly flow plus a uniform background component.\nn_obs = 8000\nprevailing_deg = 225.0\n\nn_prevailing = round(Int, 0.40 * n_obs)\nn_background = n_obs - n_prevailing\ndirections = mod.(\n    vcat(\n        prevailing_deg .+ 45.0 .* randn(n_prevailing),\n        360.0 .* rand(n_background),\n    ),\n    360.0,\n)\n\nalignment = max.(cosd.(directions .- prevailing_deg), 0.0)\nspeeds = clamp.(\n    3.5 .+ 4.0 .* abs.(randn(n_obs)) .+ 5.0 .* alignment .* rand(n_obs),\n    0.2, 30.0,\n)\n\ndir_labels = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\nn_dirs = length(dir_labels)\nsector_width = 360.0 / n_dirs\ndir_bin = [Int(mod(round(d / sector_width), n_dirs)) + 1 for d in directions]\n\nspeed_labels = [\"0-5 m/s\", \"5-10 m/s\", \"10-15 m/s\", \"15+ m/s\"]\nn_speed = length(speed_labels)\nspeed_bin_of(s) = s < 5.0 ? 1 : (s < 10.0 ? 2 : (s < 15.0 ? 3 : 4))\n\nfreq = zeros(Float64, n_dirs, n_speed)\nfor (d, s) in zip(dir_bin, speeds)\n    freq[d, speed_bin_of(s)] += 1.0\nend\nfreq .= 100.0 .* freq ./ n_obs\n\n# --- Geometry helpers -----------------------------------------------------------\n# North at top, angles increasing clockwise (meteorological convention).\ncompass_to_screen(deg) = pi / 2 - deg2rad(deg)\n\nfunction wedge_points(center_deg, half_width_deg, r0, r1; n = 24)\n    a_hi = compass_to_screen(center_deg + half_width_deg)\n    a_lo = compass_to_screen(center_deg - half_width_deg)\n    outer = [Point2f(r1 * cos(a), r1 * sin(a)) for a in range(a_hi, a_lo, length = n)]\n    inner = [Point2f(r0 * cos(a), r0 * sin(a)) for a in range(a_lo, a_hi, length = n)]\n    return vcat(outer, inner)\nend\n\n# --- Plot -------------------------------------------------------------------\ntitle_str = \"windrose-basic · julia · makie · anyplot.ai\"\n\nfig = Figure(size = (1200, 1200), fontsize = 14, backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title = title_str,\n    titlesize = 27,\n    titlecolor = INK,\n    titlegap = 22,\n    backgroundcolor = PAGE_BG,\n    aspect = DataAspect(),\n)\nhidedecorations!(ax)\nhidespines!(ax)\n\nr_max_data = maximum(sum(freq, dims = 2))\nring_step = 5.0\nn_rings = ceil(Int, r_max_data / ring_step)\nr_axis_max = n_rings * ring_step\n\n# Radial grid rings (drawn first, beneath the data)\nfor k in 1:n_rings\n    r = k * ring_step\n    ring = [Point2f(r * cos(a), r * sin(a)) for a in range(0, 2pi, length = 200)]\n    lines!(ax, ring, color = GRID, linewidth = 1.2)\nend\n\n# Direction spokes\nfor d in 0:(n_dirs - 1)\n    a = compass_to_screen(d * sector_width)\n    lines!(\n        ax,\n        [Point2f(0, 0), Point2f(r_axis_max * cos(a), r_axis_max * sin(a))],\n        color = GRID, linewidth = 1.2,\n    )\nend\n\n# Stacked wedges — one per direction sector, speed bins stacked outward\ngap_deg = 4.0\nhalf_width = sector_width / 2 - gap_deg / 2\n\nfor d in 1:n_dirs\n    center = (d - 1) * sector_width\n    r_cum = 0.0\n    for s in 1:n_speed\n        r0 = r_cum\n        r1 = r_cum + freq[d, s]\n        if freq[d, s] > 0\n            poly!(\n                ax, wedge_points(center, half_width, r0, r1),\n                color = IMPRINT_PALETTE[s],\n                strokecolor = PAGE_BG, strokewidth = 1.5,\n            )\n        end\n        r_cum = r1\n    end\nend\n\n# Radial (frequency) tick labels along the shared boundary of whichever two\n# adjacent sectors carry the least combined data, centered exactly on that\n# boundary line so the text never drifts sideways into a wedge's fill.\nsector_totals = vec(sum(freq, dims = 2))\ngap_idx = argmin([sector_totals[d] + sector_totals[mod1(d + 1, n_dirs)] for d in 1:n_dirs])\ngap_angle = compass_to_screen((gap_idx - 0.5) * sector_width)\nfor k in 1:n_rings\n    r = k * ring_step\n    text!(\n        ax, r * cos(gap_angle), r * sin(gap_angle);\n        text = \"$(round(Int, r))%\",\n        fontsize = 12, color = INK_SOFT, align = (:center, :center),\n    )\nend\n\n# Compass direction labels around the outer ring\nfor d in 0:(n_dirs - 1)\n    a = compass_to_screen(d * sector_width)\n    label_r = r_axis_max * 1.12\n    text!(\n        ax, label_r * cos(a), label_r * sin(a);\n        text = dir_labels[d + 1], fontsize = 16, color = INK, align = (:center, :center),\n    )\nend\n\nlimits!(ax, -r_axis_max * 1.28, r_axis_max * 1.28, -r_axis_max * 1.28, r_axis_max * 1.28)\n\n# Legend floats inside the axis itself (not a separate grid column) so the\n# polar chart is centered on the full square canvas; it lands in the corner\n# with the least wedge data, putting otherwise-empty space to use.\nLegend(\n    fig[1, 1],\n    [PolyElement(color = IMPRINT_PALETTE[s]) for s in 1:n_speed],\n    speed_labels,\n    \"Wind Speed\";\n    tellwidth = false, tellheight = false, halign = :right, valign = :top,\n    margin = (0, 24, 0, 24),\n    titlecolor = INK, labelcolor = INK_SOFT,\n    titlesize = 15, labelsize = 13,\n    backgroundcolor = ELEVATED_BG, framevisible = false,\n    patchsize = (16, 16), rowgap = 6,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}