{"spec_id":"polar-scatter","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\npolar-scatter: Polar Scatter Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 95/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n\n\n# Work around naming conflict with plotnine.py script and plotnine package\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nif script_dir in sys.path:\n    sys.path.remove(script_dir)\nif \"\" in sys.path:\n    sys.path.remove(\"\")\nif \".\" in sys.path:\n    sys.path.remove(\".\")\n\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Wind measurements with prevailing directions\nnp.random.seed(42)\nn_points = 120\n\n# Wind often has prevailing directions - simulate with mixture\n# Prevailing wind from SW (~225°) and secondary from NE (~45°)\nangles_sw = np.random.normal(225, 30, n_points // 2)  # Southwest prevailing\nangles_ne = np.random.normal(45, 25, n_points // 2)  # Northeast secondary\nangles = np.concatenate([angles_sw, angles_ne])\nangles = angles % 360  # Wrap to 0-360\n\n# Wind speeds (m/s) - higher speeds tend with prevailing directions\nspeeds_sw = np.abs(np.random.normal(12, 4, n_points // 2))\nspeeds_ne = np.abs(np.random.normal(8, 3, n_points // 2))\nspeeds = np.concatenate([speeds_sw, speeds_ne])\nspeeds = np.clip(speeds, 1, 25)\n\n# Time of day categories for color encoding\ntime_categories = np.random.choice([\"Morning\", \"Afternoon\", \"Evening\"], n_points, p=[0.35, 0.40, 0.25])\n\n# Convert polar to Cartesian\n# Convention: 0° at top (North), clockwise\ntheta_rad = np.radians(90 - angles)  # Convert to math convention (0° = East, CCW)\nx = speeds * np.cos(theta_rad)\ny = speeds * np.sin(theta_rad)\n\ndf = pd.DataFrame({\"angle\": angles, \"speed\": speeds, \"time\": time_categories, \"x\": x, \"y\": y})\n\n# Create circular gridlines (radial circles at speed intervals)\ngrid_rows = []\ngrid_angles = np.linspace(0, 2 * np.pi, 101)\nmax_radius = 25\nfor radius in [5, 10, 15, 20, 25]:\n    for angle in grid_angles:\n        grid_rows.append({\"x\": radius * np.cos(angle), \"y\": radius * np.sin(angle), \"radius\": radius})\n\ngrid_df = pd.DataFrame(grid_rows)\n\n# Create radial spokes (compass directions)\nspoke_rows = []\ncompass_dirs = [0, 45, 90, 135, 180, 225, 270, 315]  # N, NE, E, SE, S, SW, W, NW\nfor deg in compass_dirs:\n    angle = np.radians(90 - deg)  # Convert to math convention\n    spoke_rows.append(\n        {\"x1\": 0, \"y1\": 0, \"x2\": (max_radius + 2) * np.cos(angle), \"y2\": (max_radius + 2) * np.sin(angle)}\n    )\n\nspoke_df = pd.DataFrame(spoke_rows)\n\n# Create compass direction labels\ncompass_labels = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\nlabel_rows = []\nlabel_radius = max_radius + 5\nfor deg, lbl in zip(compass_dirs, compass_labels, strict=True):\n    angle = np.radians(90 - deg)\n    label_rows.append({\"label\": lbl, \"x\": label_radius * np.cos(angle), \"y\": label_radius * np.sin(angle)})\n\nlabel_df = pd.DataFrame(label_rows)\n\n# Create radius labels (wind speed m/s) - positioned along NNE axis for clarity\nradius_labels = []\nlabel_angle = np.radians(90 - 22.5)  # NNE direction\nfor r in [5, 10, 15, 20]:\n    radius_labels.append({\"label\": f\"{r}\", \"x\": r * np.cos(label_angle) + 1, \"y\": r * np.sin(label_angle)})\n\n# Add \"m/s\" unit at outermost radius\nradius_labels.append({\"label\": \"25 m/s\", \"x\": 25 * np.cos(label_angle) + 1, \"y\": 25 * np.sin(label_angle)})\n\nradius_label_df = pd.DataFrame(radius_labels)\n\n# Color palette for time of day (Okabe-Ito colors)\ncolors = {\n    \"Morning\": IMPRINT[0],  # #009E73 brand green\n    \"Afternoon\": IMPRINT[1],  # #C475FD vermillion\n    \"Evening\": IMPRINT[2],  # #4467A3 blue\n}\n\n# Plot\nplot = (\n    ggplot()\n    # Circular gridlines (speed circles)\n    + geom_path(\n        aes(x=\"x\", y=\"y\", group=\"radius\"), data=grid_df, color=INK_SOFT, size=0.5, alpha=0.15, linetype=\"dashed\"\n    )\n    # Radial spokes (direction lines)\n    + geom_segment(aes(x=\"x1\", y=\"y1\", xend=\"x2\", yend=\"y2\"), data=spoke_df, color=INK_SOFT, size=0.5, alpha=0.15)\n    # Wind data points with color by time of day\n    + geom_point(aes(x=\"x\", y=\"y\", color=\"time\"), data=df, size=5, alpha=0.75)\n    # Compass direction labels\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=label_df, size=16, color=INK, fontweight=\"bold\")\n    # Speed labels (m/s)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=radius_label_df, size=10, color=INK_SOFT)\n    # Custom colors for time of day\n    + scale_color_manual(values=colors, name=\"Time of Day\")\n    # Equal coordinate system for proper circles\n    + coord_fixed(ratio=1)\n    # Axis scaling with padding\n    + scale_x_continuous(limits=(-35, 35))\n    + scale_y_continuous(limits=(-35, 35))\n    # Labels\n    + labs(title=\"polar-scatter · plotnine · anyplot.ai\")\n    # Theme with proper backgrounds and colors\n    + theme(\n        figure_size=(12, 12),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_border=element_blank(),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        plot_title=element_text(size=24, ha=\"center\", color=INK),\n        legend_title=element_text(size=16, color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, width=12, height=12)\n"}