{"spec_id":"windrose-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nwindrose-basic: Wind Rose Chart\nLibrary: plotly 6.9.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.colors import sample_colorscale\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Data - Daily-average wind measurements from a coastal monitoring mast, 3-year record\n# (long-term climatology, distinct from a single hourly-observation year)\nnp.random.seed(42)\nn_observations = 1095  # 3 years of daily readings\n\n# Simulate wind direction with prevailing westerly and southwesterly winds\ndirection_weights = np.array([0.05, 0.05, 0.08, 0.10, 0.12, 0.20, 0.25, 0.15])  # N, NE, E, SE, S, SW, W, NW\ndirections_base = np.array([0, 45, 90, 135, 180, 225, 270, 315])\ndirection_idx = np.random.choice(8, size=n_observations, p=direction_weights)\ndirections = directions_base[direction_idx] + np.random.uniform(-20, 20, n_observations)\ndirections = directions % 360\n\n# Simulate wind speeds with realistic distribution (Weibull-like)\nspeeds = np.random.weibull(2.0, n_observations) * 6  # Scale for realistic m/s values\n\n# Define direction bins (8 sectors, 45 degrees each)\ndir_bins = np.array([0, 45, 90, 135, 180, 225, 270, 315, 360])\ndir_labels = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\n\n# Define speed bins (m/s) - ordinal magnitude, so colored with the Imprint sequential\n# colormap (imprint_seq: brand green -> blue) rather than the categorical palette\nspeed_bins = [0, 3, 6, 9, 12, np.inf]\nspeed_labels = [\"0-3 m/s\", \"3-6 m/s\", \"6-9 m/s\", \"9-12 m/s\", \">12 m/s\"]\nimprint_seq = [[0.0, \"#009E73\"], [1.0, \"#4467A3\"]]\nspeed_colors = sample_colorscale(imprint_seq, [i / (len(speed_labels) - 1) for i in range(len(speed_labels))])\n\n# Bin the data\ndir_indices = np.digitize(directions, dir_bins[:-1]) - 1\ndir_indices = np.clip(dir_indices, 0, 7)\nspeed_indices = np.digitize(speeds, speed_bins[:-1]) - 1\n\n# Calculate frequencies for each direction and speed combination\nfrequencies = np.zeros((8, 5))\nfor d in range(8):\n    for s in range(5):\n        frequencies[d, s] = np.sum((dir_indices == d) & (speed_indices == s))\n\n# Convert to percentages\nfrequencies_pct = frequencies / n_observations * 100\n\n# Keep the rarest speed tier perceptible even at sub-1% frequency\nfrequencies_pct = np.where((frequencies_pct > 0) & (frequencies_pct < 0.5), 0.5, frequencies_pct)\n\n# Create wind rose using barpolar\nfig = go.Figure()\n\n# Add traces for each speed bin (stacked from inside to outside)\nfor s in range(5):\n    r_values = frequencies_pct[:, s]\n\n    fig.add_trace(\n        go.Barpolar(\n            r=r_values,\n            theta=dir_labels,\n            name=speed_labels[s],\n            marker_color=speed_colors[s],\n            marker_line_color=PAGE_BG,\n            marker_line_width=2,\n            opacity=0.92,\n            hovertemplate=f\"<b>%{{theta}}</b><br>{speed_labels[s]}: %{{r:.1f}}%<extra></extra>\",\n        )\n    )\n\n# Update layout for proper stacking and styling\nfig.update_layout(\n    autosize=False,\n    width=600,\n    height=600,\n    title={\n        \"text\": \"windrose-basic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 15, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.97,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    polar={\n        \"hole\": 0.06,\n        \"bargap\": 0.08,\n        \"radialaxis\": {\n            \"visible\": True,\n            \"showticklabels\": True,\n            \"tickfont\": {\"size\": 11, \"color\": INK_SOFT},\n            \"ticksuffix\": \"%\",\n            \"angle\": 112.5,\n            \"tickangle\": 112.5,\n            \"dtick\": 5,\n            \"range\": [0, 25],\n            \"title\": {\"text\": \"Frequency (%)\", \"font\": {\"size\": 13, \"color\": INK}},\n            \"gridcolor\": GRID,\n            \"linecolor\": INK_SOFT,\n        },\n        \"angularaxis\": {\n            \"tickfont\": {\"size\": 16, \"color\": INK},\n            \"direction\": \"clockwise\",\n            \"rotation\": 90,\n            \"categoryorder\": \"array\",\n            \"categoryarray\": [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"],\n            \"gridcolor\": GRID,\n            \"linecolor\": INK_SOFT,\n        },\n        \"bgcolor\": PAGE_BG,\n    },\n    legend={\n        \"title\": {\"text\": \"Wind Speed\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"x\": 0.5,\n        \"y\": -0.08,\n        \"xanchor\": \"center\",\n        \"yanchor\": \"top\",\n        \"orientation\": \"h\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    barmode=\"stack\",\n    margin={\"l\": 40, \"r\": 40, \"t\": 60, \"b\": 90},\n)\n\n# Save as PNG and HTML\nfig.write_image(f\"plot-{THEME}.png\", width=600, height=600, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}