{"spec_id":"windbarb-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nwindbarb-basic: Wind Barb Plot for Meteorological Data\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 81/100 | Updated: 2026-05-19\n\"\"\"\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Data - Surface wind observations from a grid of weather stations\nnp.random.seed(42)\n\n# Create a grid of observation points (simulating weather station network)\nx_grid = np.arange(0, 10, 1)\ny_grid = np.arange(0, 6, 1)\nX, Y = np.meshgrid(x_grid, y_grid)\nX = X.flatten()\nY = Y.flatten()\n\n# Generate wind components (u: east-west, v: north-south) in knots\n# Create a realistic wind pattern with some spatial coherence\nbase_u = 15 + 10 * np.sin(X * 0.5)\nbase_v = 5 + 8 * np.cos(Y * 0.8)\nU = base_u + np.random.uniform(-5, 5, size=X.shape)\nV = base_v + np.random.uniform(-5, 5, size=Y.shape)\n\n# Include some calm winds (< 2.5 knots) for demonstration\ncalm_indices = [0, 25, 45]\nU[calm_indices] = np.random.uniform(-1, 1, size=len(calm_indices))\nV[calm_indices] = np.random.uniform(-1, 1, size=len(calm_indices))\n\n# Include strong winds with pennants (50+ knots) for variety\nstrong_indices = [12, 38, 55]\nU[strong_indices] = 40 + np.random.uniform(0, 15, size=len(strong_indices))\nV[strong_indices] = 30 + np.random.uniform(0, 10, size=len(strong_indices))\n\n\n# Wind barb parameters\nbarb_length = 0.35\nbarb_width = 0.14\n\n# Collect all wind barb line segments\nall_barb_x = []\nall_barb_y = []\n\n# Draw each wind barb inline\nfor i in range(len(X)):\n    x0, y0 = X[i], Y[i]\n    u, v = U[i], V[i]\n    speed = np.sqrt(u**2 + v**2)\n\n    # Calm wind (< 2.5 knots) - draw open circle\n    if speed < 2.5:\n        theta = np.linspace(0, 2 * np.pi, 20)\n        circle_r = barb_length * 0.25\n        cx = x0 + circle_r * np.cos(theta)\n        cy = y0 + circle_r * np.sin(theta)\n        all_barb_x.extend(list(cx) + [None])\n        all_barb_y.extend(list(cy) + [None])\n        continue\n\n    # Calculate direction (wind comes FROM this direction)\n    wind_dir = np.arctan2(-v, -u)\n\n    # Staff endpoint (pointing into the wind direction)\n    x_end = x0 + barb_length * np.cos(wind_dir)\n    y_end = y0 + barb_length * np.sin(wind_dir)\n\n    # Staff line\n    all_barb_x.extend([x0, x_end, None])\n    all_barb_y.extend([y0, y_end, None])\n\n    # Perpendicular direction for barbs (to the left of staff)\n    perp_dir = wind_dir + np.pi / 2\n\n    # Calculate number of pennants, full barbs, and half barbs\n    speed_knots = speed\n    pennants = int(speed_knots // 50)\n    remaining = speed_knots % 50\n    full_barbs = int(remaining // 10)\n    half_barbs = 1 if (remaining % 10) >= 5 else 0\n\n    # Position along the staff (from tip towards base)\n    spacing = barb_length * 0.12\n    current_pos = 0.95\n\n    # Draw pennants (triangular flags for 50 knots)\n    for _ in range(pennants):\n        px1 = x0 + current_pos * barb_length * np.cos(wind_dir)\n        py1 = y0 + current_pos * barb_length * np.sin(wind_dir)\n        current_pos -= 0.15\n        px2 = x0 + current_pos * barb_length * np.cos(wind_dir)\n        py2 = y0 + current_pos * barb_length * np.sin(wind_dir)\n        px_tip = px1 + barb_width * 1.2 * np.cos(perp_dir)\n        py_tip = py1 + barb_width * 1.2 * np.sin(perp_dir)\n        all_barb_x.extend([px1, px_tip, px2, px1, None])\n        all_barb_y.extend([py1, py_tip, py2, py1, None])\n        current_pos -= 0.05\n\n    # Draw full barbs (10 knots each)\n    for _ in range(full_barbs):\n        bx1 = x0 + current_pos * barb_length * np.cos(wind_dir)\n        by1 = y0 + current_pos * barb_length * np.sin(wind_dir)\n        bx2 = bx1 + barb_width * np.cos(perp_dir)\n        by2 = by1 + barb_width * np.sin(perp_dir)\n        all_barb_x.extend([bx1, bx2, None])\n        all_barb_y.extend([by1, by2, None])\n        current_pos -= spacing / barb_length\n\n    # Draw half barb (5 knots)\n    if half_barbs:\n        bx1 = x0 + current_pos * barb_length * np.cos(wind_dir)\n        by1 = y0 + current_pos * barb_length * np.sin(wind_dir)\n        bx2 = bx1 + barb_width * 0.5 * np.cos(perp_dir)\n        by2 = by1 + barb_width * 0.5 * np.sin(perp_dir)\n        all_barb_x.extend([bx1, bx2, None])\n        all_barb_y.extend([by1, by2, None])\n\n\n# Create figure\nfig = go.Figure()\n\n# Add wind barbs as a single scatter trace with lines (thicker for visibility)\nfig.add_trace(\n    go.Scatter(\n        x=all_barb_x,\n        y=all_barb_y,\n        mode=\"lines\",\n        line={\"color\": \"#306998\", \"width\": 5},\n        name=\"Wind Barbs\",\n        hoverinfo=\"skip\",\n        showlegend=False,\n    )\n)\n\n# Add station markers at observation points\nfig.add_trace(\n    go.Scatter(\n        x=X,\n        y=Y,\n        mode=\"markers\",\n        marker={\"size\": 10, \"color\": \"#306998\", \"symbol\": \"circle\"},\n        name=\"Weather Stations\",\n        hovertemplate=\"Station (%{x}, %{y})<br>U: %{customdata[0]:.1f} kt<br>V: %{customdata[1]:.1f} kt<br>Speed: %{customdata[2]:.1f} kt<extra></extra>\",\n        customdata=np.column_stack([U, V, np.sqrt(U**2 + V**2)]),\n    )\n)\n\n# Layout with tighter axis range and improved legend positioning\nfig.update_layout(\n    title={\"text\": \"windbarb-basic · plotly · pyplots.ai\", \"font\": {\"size\": 28}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Longitude (°E)\", \"font\": {\"size\": 22}},\n        \"tickfont\": {\"size\": 18},\n        \"range\": [-0.5, 9.8],\n        \"dtick\": 1,\n        \"gridcolor\": \"rgba(0,0,0,0.1)\",\n        \"gridwidth\": 1,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Latitude (°N)\", \"font\": {\"size\": 22}},\n        \"tickfont\": {\"size\": 18},\n        \"range\": [-0.5, 5.8],\n        \"dtick\": 1,\n        \"gridcolor\": \"rgba(0,0,0,0.1)\",\n        \"gridwidth\": 1,\n        \"scaleanchor\": \"x\",\n        \"scaleratio\": 1,\n    },\n    template=\"plotly_white\",\n    showlegend=True,\n    legend={\n        \"font\": {\"size\": 18},\n        \"x\": 0.02,\n        \"y\": 0.98,\n        \"bgcolor\": \"rgba(255,255,255,0.9)\",\n        \"bordercolor\": \"#306998\",\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 100, \"r\": 80, \"t\": 100, \"b\": 100},\n    annotations=[\n        {\n            \"x\": 0.02,\n            \"y\": 0.02,\n            \"xref\": \"paper\",\n            \"yref\": \"paper\",\n            \"text\": \"<b>Wind Barb Key</b><br>○ Calm (&lt;2.5 kt)<br>╲ Half barb = 5 kt<br>╲╲ Full barb = 10 kt<br>▲ Pennant = 50 kt\",\n            \"showarrow\": False,\n            \"font\": {\"size\": 16, \"family\": \"monospace\"},\n            \"align\": \"left\",\n            \"bgcolor\": \"rgba(255,255,255,0.95)\",\n            \"bordercolor\": \"#306998\",\n            \"borderwidth\": 2,\n            \"borderpad\": 10,\n            \"xanchor\": \"left\",\n            \"yanchor\": \"bottom\",\n        }\n    ],\n)\n\n# Save as PNG (4800x2700)\nfig.write_image(\"plot.png\", width=1600, height=900, scale=3)\n\n# Save as HTML for interactivity\nfig.write_html(\"plot.html\", include_plotlyjs=True, full_html=True)\n"}