{"spec_id":"polar-bar","library":"makie","language":"julia","code":"# anyplot.ai\n# polar-bar: Polar Bar Chart (Wind Rose)\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 87/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -------------------------------------------------------\nTHEME = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nINK = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nIMPRINT_PALETTE = [colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\"]\n\n# --- Data -----------------------------------------------------------------\n# Meteorological station: hours per year a wind was observed, split by\n# compass direction and speed band. Prevailing wind blows from the SW-W\n# quadrant, a common mid-latitude pattern.\ndirections = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\nn_directions = length(directions)\nbearings_deg = collect(0:45:315)\nbearings_rad = deg2rad.(bearings_deg)\n\nspeed_bins = [\"Light (0-10 kn)\", \"Moderate (10-20 kn)\", \"Strong (20-30 kn)\"]\nn_bins = length(speed_bins)\n\nprevailing_bearing = 247.5\nbase_hours = 60.0 .+ 40.0 .* cosd.(bearings_deg .- prevailing_bearing)\ntotal_hours = round.(Int, base_hours .+ rand(n_directions) .* 15.0)\n\nlight_hours = round.(Int, total_hours .* (0.55 .+ 0.05 .* rand(n_directions)))\nmoderate_hours = round.(Int, total_hours .* (0.30 .+ 0.05 .* rand(n_directions)))\nstrong_hours = max.(total_hours .- light_hours .- moderate_hours, 3)\n\nhours_by_bin = hcat(light_hours, moderate_hours, strong_hours)  # n_directions x n_bins\n\n# --- Plot -------------------------------------------------------------------\nfig = Figure(size = (1200, 1200), fontsize = 14, backgroundcolor = PAGE_BG)\n\nrow_totals = vec(sum(hours_by_bin, dims = 2))\nr_grid_max = maximum(row_totals)\nr_ticks = round.(Int, range(0, r_grid_max, length = 5))[2:end]\nr_display_max = r_grid_max * 1.2\n\nsector_rad = 2pi / n_directions\nbar_half_width = sector_rad * 0.34  # < sector/2 so adjacent direction wedges stay visibly separated\n\n# PolarAxis (native since Makie 0.19) supplies the compass ticks, radial\n# grid/ticks, and spine directly instead of hand-rolled sin/cos geometry.\n# theta_0 = -pi/2, direction = -1 puts N at 12 o'clock and advances\n# clockwise through E/S/W, matching a real compass rose.\nax = PolarAxis(\n    fig[1, 1];\n    theta_0 = -pi / 2,\n    direction = -1,\n    title = \"polar-bar · julia · makie · anyplot.ai\",\n    titlesize = 20,\n    titlecolor = INK,\n    backgroundcolor = PAGE_BG,\n    thetaticks = (bearings_rad, directions),\n    thetaticklabelsize = 16,\n    thetaticklabelcolor = INK,\n    thetagridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    rticks = r_ticks,\n    rtickformat = vs -> [\"$(round(Int, v))h\" for v in vs],\n    rtickangle = sector_rad / 2,  # centered in the N-NE gap, clear of both bars\n    rticklabelsize = 13,\n    rticklabelcolor = INK_SOFT,\n    rgridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    spinecolor = INK_SOFT,\n    rlimits = (0, r_display_max),\n)\n\n# Stacked wedges — each direction is n_bins bands radiating from the center.\n# `band!` (unlike `poly!`) respects PolarAxis's per-vertex transform, so\n# sampling each band across an arc gives it a true curved outer edge instead\n# of a straight-edged polygon.\nn_arc = 24\nfor i in 1:n_directions\n    center = bearings_rad[i]\n    ts = collect(range(center - bar_half_width, center + bar_half_width, length = n_arc))\n    r0 = 0.0\n    for b in 1:n_bins\n        r1 = r0 + hours_by_bin[i, b]\n        band!(ax, ts, fill(r0, n_arc), fill(r1, n_arc); color = IMPRINT_PALETTE[b])\n        if b < n_bins\n            lines!(ax, ts, fill(r1, n_arc); color = PAGE_BG, linewidth = 1.5)\n        end\n        r0 = r1\n    end\nend\n\n# Manual legend: PolarAxis does not support `axislegend`/an overlaid `Legend`\n# block (its content lives in a different scene graph than a plain Axis), so\n# the swatches are drawn directly on the polar axis in (theta, r) coordinates,\n# placed in the NE/E gap where the prevailing-wind data leaves the bars short.\nlegend_theta = deg2rad(100)\nlegend_radii = r_display_max .* [0.62, 0.50, 0.38]\nfor (b, r) in enumerate(legend_radii)\n    scatter!(ax, [legend_theta], [r]; marker = :rect, markersize = 18, color = IMPRINT_PALETTE[b])\n    text!(ax, legend_theta, r; text = speed_bins[b], color = INK, fontsize = 13,\n          align = (:left, :center), offset = (14, 0))\nend\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}