{"spec_id":"rose-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nrose-basic: Basic Rose Chart\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove this script's directory from sys.path to prevent bokeh.py from\n# shadowing the installed bokeh package when Python adds the script dir.\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _script_dir]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool\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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"\n\n# Data - Monthly rainfall (mm) showing seasonal patterns\nmonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nvalues = [85, 70, 65, 45, 30, 20, 15, 25, 40, 60, 75, 90]\nmax_val = max(values)\nn = len(months)\nangle_width = 2 * np.pi / n\n\n# Calculate wedge angles (equal slices, January centered at top/north)\nstart_angles = np.array([np.pi / 2 + angle_width / 2 - i * angle_width for i in range(n)])\nend_angles = start_angles - angle_width\ncenter_angles = (start_angles + end_angles) / 2\n\n# Normalize values to radius (max value = 1.0)\nradii = [v / max_val for v in values]\n\n# Brand green with alpha varying by rainfall intensity\nalphas = [0.35 + 0.65 * (v / max_val) for v in values]\n\nsource = ColumnDataSource(\n    data={\n        \"start_angle\": start_angles,\n        \"end_angle\": end_angles,\n        \"radius\": radii,\n        \"alphas\": alphas,\n        \"month\": months,\n        \"value\": values,\n    }\n)\n\n# Square canvas — rose charts are radially symmetric with no preferred\n# horizontal axis, so a 2400x2400 square uses the frame better than a\n# 16:9 landscape (which leaves unused margins beside the circle).\nW = H = 2400\n\n# Create figure\np = figure(\n    width=W,\n    height=H,\n    title=\"rose-basic · python · bokeh · anyplot.ai\",\n    x_range=(-1.6, 1.6),\n    y_range=(-1.6, 1.6),\n    tools=\"\",\n    toolbar_location=None,\n)\n\n# Draw wedges (rose petals) with a hover tooltip — a genuinely bokeh-\n# distinctive feature the static PNG can't show but the HTML artifact can.\nwedge_renderer = p.wedge(\n    x=0,\n    y=0,\n    radius=\"radius\",\n    start_angle=\"end_angle\",\n    end_angle=\"start_angle\",\n    source=source,\n    fill_color=BRAND,\n    fill_alpha=\"alphas\",\n    line_color=PAGE_BG,\n    line_width=2,\n    hover_fill_alpha=1.0,\n    hover_line_color=INK,\n)\np.add_tools(HoverTool(renderers=[wedge_renderer], tooltips=[(\"Month\", \"@month\"), (\"Rainfall\", \"@value mm\")]))\n\n# Radial gridlines (concentric circles)\ntheta = np.linspace(0, 2 * np.pi, 200)\nfor r in [0.25, 0.5, 0.75, 1.0]:\n    p.line(r * np.cos(theta), r * np.sin(theta), line_color=INK, line_alpha=0.22, line_width=1.5, line_dash=\"dashed\")\n\n# Radial divider lines from center\nfor i in range(n):\n    angle = np.pi / 2 - i * angle_width\n    p.line([0, 1.05 * np.cos(angle)], [0, 1.05 * np.sin(angle)], line_color=INK, line_alpha=0.18, line_width=1)\n\n# Month labels centered outside each wedge\nlabel_radius = 1.2\nfor i, month in enumerate(months):\n    angle = center_angles[i]\n    p.text(\n        x=[label_radius * np.cos(angle)],\n        y=[label_radius * np.sin(angle)],\n        text=[month],\n        text_align=\"center\",\n        text_baseline=\"middle\",\n        text_font_size=\"42pt\",\n        text_color=INK,\n    )\n\n# Rainfall scale labels (actual mm values, right side)\nfor r in [0.25, 0.5, 0.75, 1.0]:\n    val_label = f\"{int(r * max_val + 0.5)} mm\"\n    p.text(x=[1.28], y=[r], text=[val_label], text_font_size=\"34pt\", text_color=INK_SOFT, text_align=\"left\")\n\n# Title and chrome\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\np.title.text_color = INK\np.title.text_font_style = \"normal\"\n\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\n# Hide axes (not needed for rose chart)\np.axis.visible = False\np.grid.visible = False\n\n# Write the interactive HTML (also a required catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot it with headless Chrome — export_png's chromedriver probe is\n# unreliable in this environment, so render the saved HTML directly instead.\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()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves\n# a phantom title-bar height even headless — pin the viewport exactly via CDP.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}