{"spec_id":"polar-bar","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\npolar-bar: Polar Bar Chart (Wind Rose)\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label, Title\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1 — first series\n\n# Data - Wind frequency by direction (8 compass points)\nnp.random.seed(42)\ndirections = [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]\n# Simulating wind pattern: prevailing winds from SW and W\nfrequencies = np.array([12, 8, 10, 6, 9, 18, 22, 14])\n\n# Convert directions to angles (0° = North, clockwise)\n# In Bokeh, angles are counterclockwise from East, so we need to convert\nn_dirs = len(directions)\nangles = np.linspace(0, 2 * np.pi, n_dirs, endpoint=False)\n# Bokeh: 0 = East, pi/2 = North, counterclockwise\nstart_angles = np.pi / 2 - angles - np.pi / n_dirs\nend_angles = np.pi / 2 - angles + np.pi / n_dirs\n\n# Normalize frequencies for bar length\nmax_freq = frequencies.max()\nradii = frequencies / max_freq * 0.75  # Scale to 75% of plot radius\n\n# Colors - Use Okabe-Ito palette (first series is brand green)\nIMPRINT = [\n    \"#009E73\",  # Brand green\n    \"#C475FD\",  # Vermillion\n    \"#4467A3\",  # Blue\n    \"#BD8233\",  # Reddish purple\n    \"#AE3030\",  # Orange\n    \"#2ABCCD\",  # Sky blue\n    \"#954477\",  # Yellow\n]\ncolors = [IMPRINT[i % len(IMPRINT)] for i in range(n_dirs)]\n\n# Create figure (square for polar plot)\np = figure(width=3600, height=3600, x_range=(-1.05, 1.05), y_range=(-1.05, 1.05), tools=\"\", toolbar_location=None)\n\n# Theme-adaptive styling\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\n# Title styling\np.title = Title(text=\"polar-bar · bokeh · anyplot.ai\", text_font_size=\"28pt\", text_color=INK, align=\"center\")\n\n# Remove axes and grid for polar appearance\np.axis.visible = False\np.grid.visible = False\n\n# Draw concentric reference circles\ncircle_radii = [0.25, 0.50, 0.75]\nfor r in circle_radii:\n    p.circle(x=0, y=0, radius=r, fill_color=None, line_color=INK_SOFT, line_width=2, line_dash=\"dashed\", line_alpha=0.3)\n\n# Draw outer circle\np.circle(x=0, y=0, radius=0.85, fill_color=None, line_color=INK_SOFT, line_width=2, line_alpha=0.5)\n\n# Draw radial lines for each direction\nfor i in range(n_dirs):\n    angle = np.pi / 2 - angles[i]\n    x_end = np.cos(angle) * 0.85\n    y_end = np.sin(angle) * 0.85\n    p.line([0, x_end], [0, y_end], line_color=INK_SOFT, line_width=1.5, line_alpha=0.3)\n\n# Draw wedges (polar bars)\nsource = ColumnDataSource(\n    data={\n        \"x\": [0] * n_dirs,\n        \"y\": [0] * n_dirs,\n        \"radius\": radii.tolist(),\n        \"start_angle\": start_angles.tolist(),\n        \"end_angle\": end_angles.tolist(),\n        \"color\": colors,\n        \"direction\": directions,\n        \"frequency\": frequencies.tolist(),\n    }\n)\n\np.wedge(\n    x=\"x\",\n    y=\"y\",\n    radius=\"radius\",\n    start_angle=\"start_angle\",\n    end_angle=\"end_angle\",\n    fill_color=\"color\",\n    fill_alpha=0.85,\n    line_color=INK_SOFT,\n    line_width=3,\n    source=source,\n)\n\n# Add direction labels around the plot\nlabel_radius = 0.93\nfor i, direction in enumerate(directions):\n    angle = np.pi / 2 - angles[i]\n    x_label = np.cos(angle) * label_radius\n    y_label = np.sin(angle) * label_radius\n\n    label = Label(\n        x=x_label,\n        y=y_label,\n        text=direction,\n        text_font_size=\"22pt\",\n        text_color=INK,\n        text_align=\"center\",\n        text_baseline=\"middle\",\n    )\n    p.add_layout(label)\n\n# Add frequency scale labels at reference circles\nfor r in circle_radii:\n    freq_val = int(r / 0.75 * max_freq)\n    label = Label(\n        x=0.03,\n        y=r + 0.015,\n        text=f\"{freq_val}\",\n        text_font_size=\"16pt\",\n        text_color=INK_SOFT,\n        text_align=\"left\",\n        text_baseline=\"bottom\",\n    )\n    p.add_layout(label)\n\n# Save as HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome\nW, H = 3600, 3600\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}