{"spec_id":"polar-bar","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\npolar-bar: Polar Bar Chart (Wind Rose)\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_polygon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_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\"\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\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data - Wind direction frequencies (8 compass directions)\ndirections = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\ndirection_angles = [0, 45, 90, 135, 180, 225, 270, 315]  # Degrees from North, clockwise\nfrequencies = [15, 8, 12, 5, 18, 22, 10, 7]\n\n# Create polygons for each bar (wedge shape)\nbar_half_width = 18  # degrees, slightly less than 22.5 for visual gap\nbar_rows = []\nbar_id = 0\n\nfor direction, angle, freq in zip(directions, direction_angles, frequencies, strict=True):\n    start_angle = angle - bar_half_width\n    end_angle = angle + bar_half_width\n\n    # Center point\n    points = [(0, 0)]\n\n    # Arc points at the outer radius\n    arc_angles = np.linspace(start_angle, end_angle, 10)\n    for a in arc_angles:\n        # Convert from compass (N=0, CW) to math (E=0, CCW)\n        theta = np.radians(90 - a)\n        x = freq * np.cos(theta)\n        y = freq * np.sin(theta)\n        points.append((x, y))\n\n    # Close back to center\n    points.append((0, 0))\n\n    # Add all points to dataframe\n    for i, (x, y) in enumerate(points):\n        bar_rows.append({\"x\": x, \"y\": y, \"direction\": direction, \"bar_id\": bar_id, \"order\": i})\n\n    bar_id += 1\n\nbar_df = pd.DataFrame(bar_rows)\n\n# Create circular gridlines (concentric circles at magnitude intervals)\ngrid_rows = []\ngrid_angles = np.linspace(0, 2 * np.pi, 101)\nmax_radius = max(frequencies) + 5\ngrid_radii = [5, 10, 15, 20, 25]\n\nfor radius in grid_radii:\n    if radius <= max_radius:\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 (8 compass directions)\nspoke_rows = []\nfor deg in direction_angles:\n    angle = np.radians(90 - deg)\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\nlabel_rows = []\nlabel_radius = max_radius + 5\nfor deg, lbl in zip(direction_angles, directions, 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 (frequency values) - positioned along NNE axis\nradius_labels = []\nlabel_angle = np.radians(90 - 22.5)  # NNE direction\nfor r in [5, 10, 15, 20]:\n    if r <= max_radius:\n        radius_labels.append({\"label\": f\"{r}%\", \"x\": r * np.cos(label_angle) + 1.5, \"y\": r * np.sin(label_angle)})\n\nradius_label_df = pd.DataFrame(radius_labels)\n\n# Color palette - first direction uses brand green, rest alternate through Okabe-Ito\ncolors = {\n    \"N\": IMPRINT[0],\n    \"NE\": IMPRINT[1],\n    \"E\": IMPRINT[2],\n    \"SE\": IMPRINT[3],\n    \"S\": IMPRINT[4],\n    \"SW\": IMPRINT[5],\n    \"W\": IMPRINT[6],\n    \"NW\": IMPRINT[0],\n}\n\n# Plot\nplot = (\n    ggplot()\n    # Circular gridlines (frequency 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    # Bar wedges (wind rose bars)\n    + geom_polygon(\n        aes(x=\"x\", y=\"y\", group=\"bar_id\", fill=\"direction\"),\n        data=bar_df,\n        color=INK_SOFT,\n        size=0.5,\n        alpha=0.85,\n        show_legend=False,\n    )\n    # Compass direction labels\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=label_df, size=16, color=INK, fontweight=\"bold\")\n    # Frequency labels\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=radius_label_df, size=10, color=INK_SOFT, ha=\"left\")\n    # Custom colors for directions\n    + scale_fill_manual(values=colors)\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    # Title\n    + labs(title=\"Wind Direction Frequency (%) · polar-bar · plotnine · anyplot.ai\")\n    # Theme with theme-adaptive colors\n    + theme(\n        figure_size=(12, 12),\n        plot_title=element_text(size=24, ha=\"center\", color=INK),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}