{"spec_id":"windbarb-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nwindbarb-basic: Wind Barb Plot for Meteorological Data\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 82/100 | Updated: 2026-05-19\n\"\"\"\n\nimport numpy as np\nfrom bokeh.io import export_png\nfrom bokeh.models import ColumnDataSource, Label\nfrom bokeh.plotting import figure\n\n\n# Data - Grid of wind observations\nnp.random.seed(42)\n\n# Create a 6x5 grid of observation points\nx_grid = np.linspace(0, 10, 6)\ny_grid = np.linspace(0, 8, 5)\nx, y = np.meshgrid(x_grid, y_grid)\nx = x.flatten()\ny = y.flatten()\n\n# Wind components (u=east-west, v=north-south) in knots\n# Create varied wind patterns to demonstrate different barb configurations\nu = np.random.uniform(-30, 30, len(x))\nv = np.random.uniform(-30, 30, len(x))\n\n# Barb geometry parameters\nbarb_length = 0.6\nbarb_spacing = 0.08\nbarb_len = 0.15\npennant_len = 0.12\n\n# Collect all barb geometry\nall_lines_x = []\nall_lines_y = []\nall_pennants_x = []\nall_pennants_y = []\ncalm_x = []\ncalm_y = []\n\n# Generate wind barb geometry for each observation point\nfor i in range(len(x)):\n    x_pos, y_pos = x[i], y[i]\n    u_comp, v_comp = u[i], v[i]\n    spd = np.sqrt(u_comp**2 + v_comp**2)\n\n    # Calm wind - just mark for circle indicator\n    if spd < 2.5:\n        calm_x.append(x_pos)\n        calm_y.append(y_pos)\n        continue\n\n    # Wind direction FROM which wind blows (opposite of velocity vector)\n    angle = np.arctan2(-v_comp, -u_comp)\n\n    # Staff endpoint\n    staff_dx = barb_length * np.cos(angle)\n    staff_dy = barb_length * np.sin(angle)\n    end_x = x_pos + staff_dx\n    end_y = y_pos + staff_dy\n\n    # Add staff line\n    all_lines_x.append([x_pos, end_x])\n    all_lines_y.append([y_pos, end_y])\n\n    # Calculate barbs - decompose speed into pennants, long barbs, half barbs\n    remaining_speed = spd\n    pennants = int(remaining_speed // 50)\n    remaining_speed -= pennants * 50\n    long_barbs = int(remaining_speed // 10)\n    remaining_speed -= long_barbs * 10\n    half_barbs = 1 if remaining_speed >= 5 else 0\n\n    # Perpendicular direction (barbs on left side looking from origin)\n    perp_angle = angle + np.pi / 2\n\n    # Position along staff (start from outer end)\n    pos = 0.0\n\n    # Draw pennants (triangular flags for 50 knots)\n    for _ in range(pennants):\n        base_x = end_x - pos * np.cos(angle)\n        base_y = end_y - pos * np.sin(angle)\n        tip_x = base_x + barb_len * np.cos(perp_angle)\n        tip_y = base_y + barb_len * np.sin(perp_angle)\n        back_x = end_x - (pos + pennant_len) * np.cos(angle)\n        back_y = end_y - (pos + pennant_len) * np.sin(angle)\n        all_pennants_x.append([base_x, tip_x, back_x])\n        all_pennants_y.append([base_y, tip_y, back_y])\n        pos += pennant_len + 0.02\n\n    # Draw long barbs (10 knots each)\n    for _ in range(long_barbs):\n        base_x = end_x - pos * np.cos(angle)\n        base_y = end_y - pos * np.sin(angle)\n        tip_x = base_x + barb_len * np.cos(perp_angle)\n        tip_y = base_y + barb_len * np.sin(perp_angle)\n        all_lines_x.append([base_x, tip_x])\n        all_lines_y.append([base_y, tip_y])\n        pos += barb_spacing\n\n    # Draw half barb (5 knots) - shorter\n    if half_barbs:\n        base_x = end_x - pos * np.cos(angle)\n        base_y = end_y - pos * np.sin(angle)\n        tip_x = base_x + (barb_len * 0.5) * np.cos(perp_angle)\n        tip_y = base_y + (barb_len * 0.5) * np.sin(perp_angle)\n        all_lines_x.append([base_x, tip_x])\n        all_lines_y.append([base_y, tip_y])\n\n# Create figure\np = figure(\n    width=4800,\n    height=2700,\n    title=\"windbarb-basic · bokeh · pyplots.ai\",\n    x_axis_label=\"X Position (grid units)\",\n    y_axis_label=\"Y Position (grid units)\",\n    x_range=(-1.5, 11.5),\n    y_range=(-1.5, 9.5),\n)\n\n# Style the figure\np.title.text_font_size = \"28pt\"\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\n\n# Grid styling\np.grid.grid_line_alpha = 0.3\np.grid.grid_line_dash = [6, 4]\n\n# Draw wind barbs using multi_line\nbarb_source = ColumnDataSource(data={\"xs\": all_lines_x, \"ys\": all_lines_y})\np.multi_line(xs=\"xs\", ys=\"ys\", source=barb_source, line_width=3, line_color=\"#306998\")\n\n# Draw pennants (filled triangles)\nif all_pennants_x:\n    pennant_source = ColumnDataSource(data={\"xs\": all_pennants_x, \"ys\": all_pennants_y})\n    p.patches(xs=\"xs\", ys=\"ys\", source=pennant_source, fill_color=\"#306998\", line_color=\"#306998\", line_width=2)\n\n# Draw calm wind indicators (open circles)\nif calm_x:\n    calm_source = ColumnDataSource(data={\"x\": calm_x, \"y\": calm_y})\n    p.scatter(x=\"x\", y=\"y\", source=calm_source, size=20, fill_color=\"white\", line_color=\"#306998\", line_width=3)\n\n# Draw observation points\nobs_source = ColumnDataSource(data={\"x\": x, \"y\": y})\np.scatter(x=\"x\", y=\"y\", source=obs_source, size=12, color=\"#FFD43B\", line_color=\"#306998\", line_width=2)\n\n# Add legend annotation\nlegend_text = Label(\n    x=0.5,\n    y=-1.0,\n    text=\"Half barb = 5 kt  |  Full barb = 10 kt  |  Pennant (flag) = 50 kt\",\n    text_font_size=\"18pt\",\n    text_color=\"#444444\",\n)\np.add_layout(legend_text)\n\n# Save\nexport_png(p, filename=\"plot.png\")\n"}