{"spec_id":"gauge-activity-rings","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ngauge-activity-rings: Activity Rings Progress Chart\nLibrary: plotnine 0.15.7 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-14\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_equal,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_void,\n)\n\n\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# Imprint palette — positions 1-3 for Move / Exercise / Stand\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Fitness tracker: Move / Exercise / Stand goals (spec example data)\nring_configs = [\n    {\"metric\": \"Move\", \"value\": 420, \"goal\": 600, \"radius\": 3.0},\n    {\"metric\": \"Exercise\", \"value\": 25, \"goal\": 30, \"radius\": 2.0},\n    {\"metric\": \"Stand\", \"value\": 9, \"goal\": 12, \"radius\": 1.0},\n]\n\n# Track alpha — slightly more visible on dark surfaces\nTRACK_ALPHA = 0.20 if THEME == \"light\" else 0.32\n\n# Decompose background color once for track blending\n_bg = [int(PAGE_BG.lstrip(\"#\")[j : j + 2], 16) for j in (0, 2, 4)]\n\n# Build arc point data\n# Clockwise from top: x = r*sin(theta), y = r*cos(theta), theta in [0, 2pi)\nN_SMOOTH = 400\n\ntrack_rows = []\narc_rows = []\ncolor_dict = {}\nlegend_breaks = []\nlegend_labels = []\nann_rows = []\n\nfor i, ring in enumerate(ring_configs):\n    progress = min(ring[\"value\"] / ring[\"goal\"], 1.0)\n    r = ring[\"radius\"]\n    color = IMPRINT_PALETTE[i]\n\n    # Blend ring color onto background for the faint track arc\n    _fg = [int(color.lstrip(\"#\")[j : j + 2], 16) for j in (0, 2, 4)]\n    track_color = \"#{:02X}{:02X}{:02X}\".format(\n        *[int(_fg[j] * TRACK_ALPHA + _bg[j] * (1 - TRACK_ALPHA)) for j in range(3)]\n    )\n\n    track_key = f\"{ring['metric']}_track\"\n    arc_key = f\"{ring['metric']}_arc\"\n\n    # Full-circle background track\n    thetas = np.linspace(0, 2 * np.pi, N_SMOOTH + 1)\n    track_rows.append(pd.DataFrame({\"x\": r * np.sin(thetas), \"y\": r * np.cos(thetas), \"group\": track_key}))\n\n    # Progress arc from 12 o'clock, sweeping clockwise\n    n_pts = max(3, int(N_SMOOTH * progress))\n    arc_thetas = np.linspace(0, progress * 2 * np.pi, n_pts)\n    arc_rows.append(pd.DataFrame({\"x\": r * np.sin(arc_thetas), \"y\": r * np.cos(arc_thetas), \"group\": arc_key}))\n\n    color_dict[track_key] = track_color\n    color_dict[arc_key] = color\n\n    legend_breaks.append(arc_key)\n    pct = round(ring[\"value\"] / ring[\"goal\"] * 100)\n    legend_labels.append(f\"{ring['metric']}   {ring['value']} / {ring['goal']}   ({pct}%)\")\n\n    # Endpoint annotation: % label placed just outside the arc tip\n    theta_end = progress * 2 * np.pi\n    r_lbl = r + 0.55\n    ann_rows.append({\"x\": r_lbl * np.sin(theta_end), \"y\": r_lbl * np.cos(theta_end), \"label\": f\"{pct}%\"})\n\n# Center headline: overall average completion\navg_pct = round(sum(min(r[\"value\"] / r[\"goal\"], 1.0) for r in ring_configs) / len(ring_configs) * 100)\ndf_center = pd.DataFrame({\"x\": [0.0, 0.0], \"y\": [0.22, -0.42], \"label\": [f\"{avg_pct}%\", \"avg\"]})\ndf_ann = pd.DataFrame(ann_rows)\n\n# Tracks drawn first (underneath), arcs drawn on top\ndf_tracks = pd.concat(track_rows, ignore_index=True)\ndf_arcs = pd.concat(arc_rows, ignore_index=True)\ndf = pd.concat([df_tracks, df_arcs], ignore_index=True)\n\nall_groups = list(color_dict.keys())\ndf[\"group\"] = pd.Categorical(df[\"group\"], categories=all_groups)\n\ntitle = \"gauge-activity-rings · python · plotnine · anyplot.ai\"\n\nplot = (\n    ggplot(df, aes(x=\"x\", y=\"y\", group=\"group\", color=\"group\"))\n    + geom_path(data=df_tracks, size=9, lineend=\"round\")\n    + geom_path(data=df_arcs, size=9, lineend=\"round\")\n    + geom_text(mapping=aes(x=\"x\", y=\"y\", label=\"label\"), data=df_center, color=INK, size=4.5, inherit_aes=False)\n    + geom_text(mapping=aes(x=\"x\", y=\"y\", label=\"label\"), data=df_ann, color=INK_SOFT, size=2.8, inherit_aes=False)\n    + coord_equal(xlim=(-4.0, 4.0), ylim=(-4.0, 4.0))\n    + scale_color_manual(values=color_dict, breaks=legend_breaks, labels=legend_labels, name=\"\")\n    + labs(title=title, x=None, y=None)\n    + theme_void()\n    + theme(\n        figure_size=(6, 6),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(color=INK, size=12, hjust=0.5),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(color=INK_SOFT, size=8),\n        legend_position=\"bottom\",\n        legend_title=element_blank(),\n        legend_key=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}