Wind Barb Plot for Meteorological Data — Makie.jl

A wind barb plot displays wind speed and direction at specific locations using standard meteorological barb notation. Each barb consists of a staff pointing in the direction from which the wind blows, with short barbs (5 knots), long barbs (10 knots), and triangular pennants (50 knots) attached to indicate speed. This internationally recognized symbology enables rapid interpretation of wind patterns across weather maps and atmospheric data visualizations.

Wind Barb Plot for Meteorological Data rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# windbarb-basic: Wind Barb Plot for Meteorological Data
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 90/100 | Created: 2026-09-02

using CairoMakie
using Colors
using Random

Random.seed!(42)

# --- Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
const THEME    = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG  = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK      = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const BRAND    = colorant"#009E73"  # Imprint palette position 1 — always first series

# --- Data: surface wind observations on a grid around a local vortex ---------
# Domain and radius are mesocyclone-scale (~km), not a synoptic low.
station_x = Float64[]
station_y = Float64[]
for grid_x in range(0.0, 10.0; length = 8), grid_y in range(0.0, 6.0; length = 6)
    push!(station_x, grid_x + 0.12 * randn())
    push!(station_y, grid_y + 0.10 * randn())
end

center_x, center_y = 5.0, 3.0
radius_max, speed_max = 1.6, 52.0  # Rankine-vortex radius / peak tangential speed (knots)

wind_u = Float64[]
wind_v = Float64[]
for i in eachindex(station_x)
    dx = station_x[i] - center_x
    dy = station_y[i] - center_y
    r  = hypot(dx, dy) + 1e-6
    tangent_x, tangent_y = -dy / r, dx / r  # cyclonic (counterclockwise) circulation
    speed = r <= radius_max ? speed_max * (r / radius_max) : speed_max * (radius_max / r)
    speed = max(speed + 1.5 * randn(), 0.0)
    push!(wind_u, speed * tangent_x)
    push!(wind_v, speed * tangent_y)
end

# Guarantee the full barb vocabulary is on display: the station nearest the
# eye reads as calm, and the station nearest the peak-wind ring reads gale-force.
station_radius = hypot.(station_x .- center_x, station_y .- center_y)
eye_index = argmin(station_radius)
wind_u[eye_index], wind_v[eye_index] = 0.6, -0.4
gale_index = argmin(abs.(station_radius .- radius_max))
gale_scale = 55.0 / hypot(wind_u[gale_index], wind_v[gale_index])
wind_u[gale_index] *= gale_scale
wind_v[gale_index] *= gale_scale

# --- Wind barb geometry (staff, feathers, pennants, calm circles) -----------
staff_len    = 0.55
full_len     = staff_len * 0.5
half_len     = full_len * 0.5
tick_spacing = staff_len * 0.18
barb_offset  = 3π / 4  # feather angle relative to the staff — back and to the left

staff_points  = Point2f[]
staff_colors  = RGBAf[]
barb_points   = Point2f[]
barb_colors   = RGBAf[]
pennant_polys = Vector{Point2f}[]
pennant_colors = RGBAf[]
calm_x        = Float64[]
calm_y        = Float64[]

# Speed-keyed alpha: stronger stations render more opaque, weaker ones more
# translucent, giving the swirl a visible intensity gradient without adding
# a second data hue.
barb_alpha(speed) = clamp(0.5 + 0.5 * (speed / speed_max), 0.5, 1.0)
brand_alpha(alpha) = RGBAf(BRAND.r, BRAND.g, BRAND.b, alpha)

for i in eachindex(station_x)
    speed = hypot(wind_u[i], wind_v[i])
    x0, y0 = station_x[i], station_y[i]

    if speed < 2.5
        push!(calm_x, x0)
        push!(calm_y, y0)
        continue
    end

    station_color = brand_alpha(barb_alpha(speed))

    heading = atan(-wind_v[i], -wind_u[i])  # staff points FROM which the wind blows
    tip_x = x0 + staff_len * cos(heading)
    tip_y = y0 + staff_len * sin(heading)
    push!(staff_points, Point2f(x0, y0), Point2f(tip_x, tip_y))
    push!(staff_colors, station_color, station_color)

    rounded_int = round(Int, speed / 5) * 5
    n_pennants  = rounded_int ÷ 50
    remainder1  = rounded_int % 50
    n_full      = remainder1 ÷ 10
    remainder2  = remainder1 % 10
    n_half      = remainder2 >= 5 ? 1 : 0

    step = 0
    for _ in 1:n_pennants
        base_x = tip_x - step * tick_spacing * cos(heading)
        base_y = tip_y - step * tick_spacing * sin(heading)
        far_x  = tip_x - (step + 1) * tick_spacing * cos(heading)
        far_y  = tip_y - (step + 1) * tick_spacing * sin(heading)
        apex_x = base_x + full_len * cos(heading + barb_offset)
        apex_y = base_y + full_len * sin(heading + barb_offset)
        push!(pennant_polys, [Point2f(base_x, base_y), Point2f(far_x, far_y), Point2f(apex_x, apex_y)])
        push!(pennant_colors, station_color)
        step += 1
    end
    for _ in 1:n_full
        base_x = tip_x - step * tick_spacing * cos(heading)
        base_y = tip_y - step * tick_spacing * sin(heading)
        end_x  = base_x + full_len * cos(heading + barb_offset)
        end_y  = base_y + full_len * sin(heading + barb_offset)
        push!(barb_points, Point2f(base_x, base_y), Point2f(end_x, end_y))
        push!(barb_colors, station_color, station_color)
        step += 1
    end
    for _ in 1:n_half
        base_x = tip_x - step * tick_spacing * cos(heading)
        base_y = tip_y - step * tick_spacing * sin(heading)
        end_x  = base_x + half_len * cos(heading + barb_offset)
        end_y  = base_y + half_len * sin(heading + barb_offset)
        push!(barb_points, Point2f(base_x, base_y), Point2f(end_x, end_y))
        push!(barb_colors, station_color, station_color)
        step += 1
    end
end

# --- Plot ---------------------------------------------------------------------
fig = Figure(resolution = (1600, 900), fontsize = 14, backgroundcolor = PAGE_BG)

ax = Axis(
    fig[1, 1];
    title              = "windbarb-basic · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Distance east of vortex center (km)",
    ylabel             = "Distance north of vortex center (km)",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridvisible       = false,
    ygridvisible       = false,
)

# Faint, non-data reference ring + crosshair anchoring the vortex center so
# the cyclonic swirl reads immediately instead of requiring inference from
# barb orientation alone (chrome ink, not a second data hue).
ring_theta = range(0, 2π; length = 100)
lines!(ax, center_x .+ radius_max .* cos.(ring_theta), center_y .+ radius_max .* sin.(ring_theta);
       color = INK_SOFT, linestyle = :dash, linewidth = 1.5, alpha = 0.35)
cross_len = 0.3
linesegments!(ax, [Point2f(center_x - cross_len, center_y), Point2f(center_x + cross_len, center_y),
                    Point2f(center_x, center_y - cross_len), Point2f(center_x, center_y + cross_len)];
              color = INK_SOFT, linewidth = 1.5, alpha = 0.55)

linesegments!(ax, staff_points; color = staff_colors, linewidth = 2.2)
linesegments!(ax, barb_points; color = barb_colors, linewidth = 2.2)
if !isempty(pennant_polys)
    poly!(ax, pennant_polys; color = pennant_colors, strokewidth = 0)
end
if !isempty(calm_x)
    scatter!(ax, calm_x, calm_y; marker = :circle, markersize = 18,
             color = :transparent, strokecolor = BRAND, strokewidth = 2.2)
end

# --- Save -----------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/windbarb-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "windbarb-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/windbarb-basic/julia/makie",
  "hub": "https://anyplot.ai/windbarb-basic",
  "code_json": "https://api.anyplot.ai/specs/windbarb-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/windbarb-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/windbarb-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/windbarb-basic/julia/makie/plot-dark.png",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Wind Barb Plot for Meteorological Data on anyplot.ai.

Other implementations