{"spec_id":"gauge-activity-rings","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\ngauge-activity-rings: Activity Rings Progress Chart\nLibrary: bokeh 3.9.1 | Python 3.13.13\nQuality: 87/100 | Created: 2026-06-14\n\"\"\"\n\nimport os\nimport sys\n\n\n# Running as `python bokeh.py` adds this file's directory to sys.path[0],\n# shadowing the installed bokeh package. Strip it before importing.\n_this_dir = os.path.dirname(os.path.abspath(__file__))\n_orig_path = sys.path.copy()\nsys.path = [p for p in sys.path if os.path.abspath(p) != _this_dir]\n\nimport math\nimport time\nfrom pathlib import Path\n\nfrom bokeh.io import output_file, save\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\nsys.path = _orig_path\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — positions 1-3 (theme-independent data colors)\nCOLORS = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data: daily fitness goal tracking\nMETRICS = [\"Move\", \"Exercise\", \"Stand\"]\nVALUES = [420, 28, 9]\nGOALS = [600, 30, 12]\nUNITS = [\"kcal\", \"min\", \"hr\"]\nFRACTIONS = [v / g for v, g in zip(VALUES, GOALS, strict=False)]\n\n# Ring geometry — outer to inner, centered at (CX, CY)\nCX, CY = 0.0, 0.10\nOUTER_RADII = [0.80, 0.56, 0.32]\nRING_WIDTH = 0.18\nINNER_RADII = [r - RING_WIDTH for r in OUTER_RADII]\nMID_RADII = [(o + i) / 2 for o, i in zip(OUTER_RADII, INNER_RADII, strict=False)]\nCAP_R = 0.05  # radius of rounded end-cap circles — kept < ring gap (0.06) to avoid bleed\n\ntitle = \"gauge-activity-rings · python · bokeh · anyplot.ai\"\nW, H = 2400, 2400\n\n# Figure — square canvas, axes hidden, match_aspect ensures circles are circular\np = figure(\n    width=W,\n    height=H,\n    title=title,\n    toolbar_location=None,\n    x_range=(-1.4, 1.4),\n    y_range=(-1.4, 1.4),\n    match_aspect=True,\n    min_border_bottom=60,\n    min_border_left=60,\n    min_border_top=100,\n    min_border_right=60,\n)\n\np.axis.visible = False\np.grid.visible = False\np.outline_line_color = None\np.outline_line_width = 0\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.title.text_color = INK\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\n\n# Draw rings outer → inner\nfor color, frac, outer_r, inner_r, mid_r in zip(COLORS, FRACTIONS, OUTER_RADII, INNER_RADII, MID_RADII, strict=False):\n    # Faint background track: full circle at low opacity\n    p.annular_wedge(\n        x=CX,\n        y=CY,\n        inner_radius=inner_r,\n        outer_radius=outer_r,\n        start_angle=0,\n        end_angle=2 * math.pi,\n        color=color,\n        alpha=0.18 if THEME == \"dark\" else 0.13,\n        line_color=None,\n    )\n\n    display_frac = min(frac, 1.0)\n    if display_frac > 0.001:\n        # Arc sweeps clockwise from 12 o'clock (π/2) to end angle\n        start_a = math.pi / 2\n        end_a = start_a - display_frac * 2 * math.pi\n\n        p.annular_wedge(\n            x=CX,\n            y=CY,\n            inner_radius=inner_r,\n            outer_radius=outer_r,\n            start_angle=start_a,\n            end_angle=end_a,\n            direction=\"clock\",\n            color=color,\n            alpha=1.0,\n            line_color=None,\n        )\n\n        # Rounded end caps: circles placed at the midline of the ring band\n        p.circle(\n            x=CX + mid_r * math.cos(start_a),\n            y=CY + mid_r * math.sin(start_a),\n            radius=CAP_R,\n            color=color,\n            alpha=1.0,\n            line_color=None,\n        )\n        p.circle(\n            x=CX + mid_r * math.cos(end_a),\n            y=CY + mid_r * math.sin(end_a),\n            radius=CAP_R,\n            color=color,\n            alpha=1.0,\n            line_color=None,\n        )\n\n# Center text: average completion across all rings\navg_pct = int(sum(FRACTIONS) / len(FRACTIONS) * 100)\np.text(\n    x=[CX],\n    y=[CY + 0.05],\n    text=[f\"{avg_pct}%\"],\n    text_color=INK,\n    text_font_size=\"62pt\",\n    text_font_style=\"bold\",\n    text_align=\"center\",\n    text_baseline=\"middle\",\n)\np.text(\n    x=[CX],\n    y=[CY - 0.08],\n    text=[\"avg complete\"],\n    text_color=INK_MUTED,\n    text_font_size=\"28pt\",\n    text_align=\"center\",\n    text_baseline=\"middle\",\n)\n\n# Legend below the rings: dot · name · value/goal · percentage\nleg_y0 = CY - OUTER_RADII[0] - 0.22\ngap = 0.19\ndot_r = 0.048\nlx_left = -0.78\n\nfor i, (metric, val, goal, unit, color, frac) in enumerate(\n    zip(METRICS, VALUES, GOALS, UNITS, COLORS, FRACTIONS, strict=False)\n):\n    ly = leg_y0 - i * gap\n\n    p.circle(x=lx_left, y=ly, radius=dot_r, color=color, alpha=1.0, line_color=None)\n\n    p.text(\n        x=[lx_left + 0.12],\n        y=[ly],\n        text=[metric],\n        text_color=INK,\n        text_font_size=\"34pt\",\n        text_font_style=\"bold\",\n        text_align=\"left\",\n        text_baseline=\"middle\",\n    )\n    p.text(\n        x=[lx_left + 0.52],\n        y=[ly],\n        text=[f\"{val} / {goal} {unit}\"],\n        text_color=INK_SOFT,\n        text_font_size=\"30pt\",\n        text_align=\"left\",\n        text_baseline=\"middle\",\n    )\n    p.text(\n        x=[0.70],\n        y=[ly],\n        text=[f\"{int(frac * 100)}%\"],\n        text_color=color,\n        text_font_size=\"36pt\",\n        text_font_style=\"bold\",\n        text_align=\"right\",\n        text_baseline=\"middle\",\n    )\n\n# Save interactive HTML artifact\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot via headless Chrome (Selenium 4 — auto-resolves driver)\n# Window height needs +143px to compensate for Chrome's internal viewport offset,\n# so the viewport (screenshot) comes out at exactly W×H = 2400×2400.\nSW, SH = W, H + 143\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={SW},{SH}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(SW, SH)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}