{"spec_id":"windbarb-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# windbarb-basic: Wind Barb Plot for Meteorological Data\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\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 BRAND    = colorant\"#009E73\"  # Imprint palette position 1 — always first series\n\n# --- Data: surface wind observations on a grid around a local vortex ---------\n# Domain and radius are mesocyclone-scale (~km), not a synoptic low.\nstation_x = Float64[]\nstation_y = Float64[]\nfor grid_x in range(0.0, 10.0; length = 8), grid_y in range(0.0, 6.0; length = 6)\n    push!(station_x, grid_x + 0.12 * randn())\n    push!(station_y, grid_y + 0.10 * randn())\nend\n\ncenter_x, center_y = 5.0, 3.0\nradius_max, speed_max = 1.6, 52.0  # Rankine-vortex radius / peak tangential speed (knots)\n\nwind_u = Float64[]\nwind_v = Float64[]\nfor i in eachindex(station_x)\n    dx = station_x[i] - center_x\n    dy = station_y[i] - center_y\n    r  = hypot(dx, dy) + 1e-6\n    tangent_x, tangent_y = -dy / r, dx / r  # cyclonic (counterclockwise) circulation\n    speed = r <= radius_max ? speed_max * (r / radius_max) : speed_max * (radius_max / r)\n    speed = max(speed + 1.5 * randn(), 0.0)\n    push!(wind_u, speed * tangent_x)\n    push!(wind_v, speed * tangent_y)\nend\n\n# Guarantee the full barb vocabulary is on display: the station nearest the\n# eye reads as calm, and the station nearest the peak-wind ring reads gale-force.\nstation_radius = hypot.(station_x .- center_x, station_y .- center_y)\neye_index = argmin(station_radius)\nwind_u[eye_index], wind_v[eye_index] = 0.6, -0.4\ngale_index = argmin(abs.(station_radius .- radius_max))\ngale_scale = 55.0 / hypot(wind_u[gale_index], wind_v[gale_index])\nwind_u[gale_index] *= gale_scale\nwind_v[gale_index] *= gale_scale\n\n# --- Wind barb geometry (staff, feathers, pennants, calm circles) -----------\nstaff_len    = 0.55\nfull_len     = staff_len * 0.5\nhalf_len     = full_len * 0.5\ntick_spacing = staff_len * 0.18\nbarb_offset  = 3π / 4  # feather angle relative to the staff — back and to the left\n\nstaff_points  = Point2f[]\nstaff_colors  = RGBAf[]\nbarb_points   = Point2f[]\nbarb_colors   = RGBAf[]\npennant_polys = Vector{Point2f}[]\npennant_colors = RGBAf[]\ncalm_x        = Float64[]\ncalm_y        = Float64[]\n\n# Speed-keyed alpha: stronger stations render more opaque, weaker ones more\n# translucent, giving the swirl a visible intensity gradient without adding\n# a second data hue.\nbarb_alpha(speed) = clamp(0.5 + 0.5 * (speed / speed_max), 0.5, 1.0)\nbrand_alpha(alpha) = RGBAf(BRAND.r, BRAND.g, BRAND.b, alpha)\n\nfor i in eachindex(station_x)\n    speed = hypot(wind_u[i], wind_v[i])\n    x0, y0 = station_x[i], station_y[i]\n\n    if speed < 2.5\n        push!(calm_x, x0)\n        push!(calm_y, y0)\n        continue\n    end\n\n    station_color = brand_alpha(barb_alpha(speed))\n\n    heading = atan(-wind_v[i], -wind_u[i])  # staff points FROM which the wind blows\n    tip_x = x0 + staff_len * cos(heading)\n    tip_y = y0 + staff_len * sin(heading)\n    push!(staff_points, Point2f(x0, y0), Point2f(tip_x, tip_y))\n    push!(staff_colors, station_color, station_color)\n\n    rounded_int = round(Int, speed / 5) * 5\n    n_pennants  = rounded_int ÷ 50\n    remainder1  = rounded_int % 50\n    n_full      = remainder1 ÷ 10\n    remainder2  = remainder1 % 10\n    n_half      = remainder2 >= 5 ? 1 : 0\n\n    step = 0\n    for _ in 1:n_pennants\n        base_x = tip_x - step * tick_spacing * cos(heading)\n        base_y = tip_y - step * tick_spacing * sin(heading)\n        far_x  = tip_x - (step + 1) * tick_spacing * cos(heading)\n        far_y  = tip_y - (step + 1) * tick_spacing * sin(heading)\n        apex_x = base_x + full_len * cos(heading + barb_offset)\n        apex_y = base_y + full_len * sin(heading + barb_offset)\n        push!(pennant_polys, [Point2f(base_x, base_y), Point2f(far_x, far_y), Point2f(apex_x, apex_y)])\n        push!(pennant_colors, station_color)\n        step += 1\n    end\n    for _ in 1:n_full\n        base_x = tip_x - step * tick_spacing * cos(heading)\n        base_y = tip_y - step * tick_spacing * sin(heading)\n        end_x  = base_x + full_len * cos(heading + barb_offset)\n        end_y  = base_y + full_len * sin(heading + barb_offset)\n        push!(barb_points, Point2f(base_x, base_y), Point2f(end_x, end_y))\n        push!(barb_colors, station_color, station_color)\n        step += 1\n    end\n    for _ in 1:n_half\n        base_x = tip_x - step * tick_spacing * cos(heading)\n        base_y = tip_y - step * tick_spacing * sin(heading)\n        end_x  = base_x + half_len * cos(heading + barb_offset)\n        end_y  = base_y + half_len * sin(heading + barb_offset)\n        push!(barb_points, Point2f(base_x, base_y), Point2f(end_x, end_y))\n        push!(barb_colors, station_color, station_color)\n        step += 1\n    end\nend\n\n# --- Plot ---------------------------------------------------------------------\nfig = Figure(resolution = (1600, 900), fontsize = 14, backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"windbarb-basic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Distance east of vortex center (km)\",\n    ylabel             = \"Distance north of vortex center (km)\",\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    ygridvisible       = false,\n)\n\n# Faint, non-data reference ring + crosshair anchoring the vortex center so\n# the cyclonic swirl reads immediately instead of requiring inference from\n# barb orientation alone (chrome ink, not a second data hue).\nring_theta = range(0, 2π; length = 100)\nlines!(ax, center_x .+ radius_max .* cos.(ring_theta), center_y .+ radius_max .* sin.(ring_theta);\n       color = INK_SOFT, linestyle = :dash, linewidth = 1.5, alpha = 0.35)\ncross_len = 0.3\nlinesegments!(ax, [Point2f(center_x - cross_len, center_y), Point2f(center_x + cross_len, center_y),\n                    Point2f(center_x, center_y - cross_len), Point2f(center_x, center_y + cross_len)];\n              color = INK_SOFT, linewidth = 1.5, alpha = 0.55)\n\nlinesegments!(ax, staff_points; color = staff_colors, linewidth = 2.2)\nlinesegments!(ax, barb_points; color = barb_colors, linewidth = 2.2)\nif !isempty(pennant_polys)\n    poly!(ax, pennant_polys; color = pennant_colors, strokewidth = 0)\nend\nif !isempty(calm_x)\n    scatter!(ax, calm_x, calm_y; marker = :circle, markersize = 18,\n             color = :transparent, strokecolor = BRAND, strokewidth = 2.2)\nend\n\n# --- Save -----------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}