{"spec_id":"polar-scatter","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\npolar-scatter: Polar Scatter Plot\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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 (categorical)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Generate synthetic wind measurement data\nnp.random.seed(42)\nn_points = 120\n\n# Simulate wind direction with prevailing winds from SW and NE\n# Use mixture of von Mises distributions for realistic directional clustering\ndirection_probs = np.array([0.05, 0.15, 0.08, 0.05, 0.05, 0.12, 0.30, 0.20])  # N, NE, E, SE, S, SW, W, NW\ndirection_centers = np.array([0, 45, 90, 135, 180, 225, 270, 315])\n\n# Sample primary direction centers\nchosen_sectors = np.random.choice(8, size=n_points, p=direction_probs / direction_probs.sum())\n# Add variance within each sector\ndirections = direction_centers[chosen_sectors] + np.random.uniform(-20, 20, n_points)\ndirections = directions % 360\n\n# Wind speed (m/s) - Weibull-like distribution, stronger from SW-W directions\nbase_speed = np.random.weibull(2.0, n_points) * 6 + 2\n# Increase speed for SW-W winds\ndirection_factor = 1 + 0.4 * np.sin(np.radians(directions - 240))\nspeeds = base_speed * direction_factor\nspeeds = np.clip(speeds, 1, 22)\n\n# Time of day for color encoding (morning: 6-12, afternoon: 12-18, evening: 18-24)\nhour = np.random.choice([6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], size=n_points)\ntime_of_day = np.where(hour < 12, \"Morning\", np.where(hour < 18, \"Afternoon\", \"Evening\"))\n\n# Create DataFrame\ndf = pd.DataFrame({\"direction\": directions, \"speed\": speeds, \"time_of_day\": time_of_day})\n\n# Order time of day for legend\ndf[\"time_of_day\"] = pd.Categorical(df[\"time_of_day\"], categories=[\"Morning\", \"Afternoon\", \"Evening\"], ordered=True)\n\n# Create polar scatter plot\n# coord_polar: start=0 means 12 o'clock (North), direction=1 is clockwise\nplot = (\n    ggplot(df, aes(x=\"direction\", y=\"speed\", color=\"time_of_day\"))\n    + geom_point(size=5, alpha=0.75)\n    + coord_polar(start=0, direction=1)\n    + scale_x_continuous(\n        breaks=[0, 45, 90, 135, 180, 225, 270, 315],\n        labels=[\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"],\n        limits=[0, 360],\n        expand=[0, 0],\n    )\n    + scale_y_continuous(limits=[0, None], expand=[0, 0.05])\n    + scale_color_manual(values=IMPRINT, name=\"Time of Day\")\n    + labs(title=\"polar-scatter · letsplot · anyplot.ai\", x=\"\", y=\"Wind Speed (m/s)\")\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK_SOFT, size=0.3),\n        panel_grid_minor=element_line(color=INK_SOFT, size=0.2),\n        plot_title=element_text(size=24, hjust=0.5, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_title_y=element_text(size=20, color=INK),\n        axis_line=element_line(color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n    + ggsize(1200, 1200)  # Square format for polar plot, scaled 3x = 3600x3600\n)\n\n# Save as PNG and HTML\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}