{"spec_id":"windrose-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nwindrose-basic: Wind Rose Chart\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-07\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\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\"\n\n# Okabe-Ito palette for speed ranges (cool to warm progression)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Configure seaborn\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK_SOFT,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data\nnp.random.seed(42)\nn_obs = 8760\n\n# Simulate prevailing winds with realistic distribution\ndirection_weights = np.zeros(360)\ndirection_weights[200:240] = 3.0\ndirection_weights[30:60] = 1.5\ndirection_weights[260:290] = 1.0\ndirection_weights += 0.2\ndirection_weights /= direction_weights.sum()\n\ndirections = np.random.choice(360, size=n_obs, p=direction_weights)\ndirections = (directions + np.random.uniform(-10, 10, n_obs)) % 360\n\n# Wind speeds by direction\nspeeds = np.zeros(n_obs)\nfor i, d in enumerate(directions):\n    if 200 <= d <= 240:\n        speeds[i] = np.random.weibull(2.2) * 8 + 2\n    elif 30 <= d <= 60:\n        speeds[i] = np.random.weibull(2.0) * 6 + 1\n    else:\n        speeds[i] = np.random.weibull(1.8) * 4 + 0.5\nspeeds = np.clip(speeds, 0, 25)\n\n# 8-direction bins (N, NE, E, SE, S, SW, W, NW)\nn_dir_bins = 8\ndir_bins = np.linspace(0, 360, n_dir_bins + 1)\ndir_centers = (dir_bins[:-1] + dir_bins[1:]) / 2\ndir_width = 2 * np.pi / n_dir_bins\n\n# Speed bins\nspeed_bins = [0, 3, 6, 10, 15, 25]\nspeed_labels = [\"0-3 m/s\", \"3-6 m/s\", \"6-10 m/s\", \"10-15 m/s\", \"15+ m/s\"]\n\n# Calculate frequencies\nfrequencies = np.zeros((n_dir_bins, len(speed_labels)))\nfor i in range(n_dir_bins):\n    dir_min, dir_max = dir_bins[i], dir_bins[i + 1]\n    in_dir = (directions >= dir_min) & (directions < dir_max)\n\n    for j in range(len(speed_labels)):\n        speed_min = speed_bins[j]\n        speed_max = speed_bins[j + 1]\n        in_speed = (speeds >= speed_min) & (speeds < speed_max)\n        frequencies[i, j] = np.sum(in_dir & in_speed)\n\nfrequencies = frequencies / n_obs * 100\n\n# Plot\nfig = plt.figure(figsize=(12, 12), facecolor=PAGE_BG)\nax = fig.add_subplot(111, projection=\"polar\")\n\nax.set_facecolor(PAGE_BG)\nax.set_theta_zero_location(\"N\")\nax.set_theta_direction(-1)\n\ntheta = np.deg2rad(dir_centers)\n\n# Identify prevailing wind sectors (highest frequency) for visual emphasis\ntotal_freq = frequencies.sum(axis=1)\ndominant_threshold = np.percentile(total_freq, 75)\nis_dominant = total_freq > dominant_threshold\n\n# Plot stacked bars with Okabe-Ito palette\nbottoms = np.zeros(n_dir_bins)\nfor j, (label, color) in enumerate(zip(speed_labels, IMPRINT, strict=False)):\n    # Use full alpha for dominant sectors, reduced for weaker ones\n    alpha_per_sector = np.where(is_dominant, 0.90, 0.65)\n\n    # Plot all sectors in one call, then manually adjust alpha if possible\n    bars = ax.bar(\n        theta,\n        frequencies[:, j],\n        width=dir_width * 0.9,\n        bottom=bottoms,\n        color=color,\n        edgecolor=PAGE_BG,\n        linewidth=0.5,\n        label=label,\n        alpha=0.85,\n    )\n\n    # Adjust individual bar alpha for dominant directions\n    for bar, alpha_val in zip(bars, alpha_per_sector, strict=False):\n        bar.set_alpha(alpha_val)\n\n    bottoms += frequencies[:, j]\n\n# Style\nax.set_title(\"windrose-basic · seaborn · anyplot.ai\", fontsize=24, pad=20, fontweight=\"medium\", color=INK)\n\nmax_freq = np.ceil(bottoms.max() / 5) * 5\nax.set_ylim(0, max_freq)\nax.set_yticks(np.arange(0, max_freq + 1, 5))\nax.set_yticklabels([f\"{int(y)}%\" for y in np.arange(0, max_freq + 1, 5)], fontsize=14, color=INK_SOFT)\n\ndirection_labels = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\nax.set_xticks(np.deg2rad(np.arange(0, 360, 45)))\nax.set_xticklabels(direction_labels, fontsize=18, fontweight=\"medium\", color=INK)\n\n# Enhanced grid styling with subtle radial emphasis\nax.grid(True, alpha=0.12, linestyle=\"-\", linewidth=0.8, color=INK_SOFT)\nfor spine in ax.spines.values():\n    spine.set_color(INK_SOFT)\n    spine.set_linewidth(1.1)\n\nlegend = ax.legend(\n    title=\"Wind Speed\", loc=\"lower right\", bbox_to_anchor=(1.15, 0), fontsize=14, title_fontsize=16, framealpha=0.95\n)\nlegend.get_frame().set_facecolor(ELEVATED_BG)\nlegend.get_frame().set_edgecolor(INK_SOFT)\nlegend.get_title().set_color(INK)\nfor text in legend.get_texts():\n    text.set_color(INK)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}