{"spec_id":"gauge-activity-rings","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ngauge-activity-rings: Activity Rings Progress Chart\nLibrary: letsplot 4.10.1 | 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 lets_plot import *\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — positions 1-3 for the three rings\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — daily fitness tracker activity rings\nrings_data = [\n    {\"name\": \"Move\", \"value\": 420, \"goal\": 600, \"unit\": \"kcal\"},\n    {\"name\": \"Exercise\", \"value\": 25, \"goal\": 30, \"unit\": \"min\"},\n    {\"name\": \"Stand\", \"value\": 9, \"goal\": 12, \"unit\": \"hr\"},\n]\nring_color_map = {r[\"name\"]: IMPRINT_PALETTE[i] for i, r in enumerate(rings_data)}\n\n# Ring geometry — outer to inner\nradii = [2.5, 1.7, 0.9]\nn_pts = 300\n\n# Build arc path data\ntrack_rows, arc_rows = [], []\nfor ring, r in zip(rings_data, radii, strict=False):\n    frac = min(ring[\"value\"] / ring[\"goal\"], 1.0)\n    pct = ring[\"value\"] / ring[\"goal\"] * 100\n\n    # Background track (full circle, low opacity)\n    a_track = np.linspace(np.pi / 2, np.pi / 2 - 2 * np.pi, n_pts + 1)\n    for a in a_track:\n        track_rows.append(\n            {\"x\": r * np.cos(a), \"y\": r * np.sin(a), \"group\": f\"track_{ring['name']}\", \"name\": ring[\"name\"]}\n        )\n\n    # Progress arc sweeping clockwise from 12 o'clock\n    n_arc = max(4, int(n_pts * frac) + 1)\n    a_arc = np.linspace(np.pi / 2, np.pi / 2 - 2 * np.pi * frac, n_arc)\n    for a in a_arc:\n        arc_rows.append(\n            {\n                \"x\": r * np.cos(a),\n                \"y\": r * np.sin(a),\n                \"group\": f\"arc_{ring['name']}\",\n                \"name\": ring[\"name\"],\n                \"pct_label\": f\"{pct:.0f}%\",\n                \"progress\": f\"{ring['value']} / {ring['goal']} {ring['unit']}\",\n            }\n        )\n\ntrack_df = pd.DataFrame(track_rows)\narc_df = pd.DataFrame(arc_rows)\n\n# Labels below the rings — centered under each ring's approximate x position\nlabel_x = [-2.4, 0.0, 2.4]\nlabel_rows, sub_rows = [], []\nfor ring, xp in zip(rings_data, label_x, strict=False):\n    pct = ring[\"value\"] / ring[\"goal\"] * 100\n    label_rows.append(\n        {\n            \"x\": xp,\n            \"y\": -3.2,\n            \"label\": f\"{ring['name']}  {pct:.0f}%\",\n            \"name\": ring[\"name\"],\n            \"pct_label\": f\"{pct:.0f}%\",\n            \"progress\": f\"{ring['value']} / {ring['goal']} {ring['unit']}\",\n        }\n    )\n    sub_rows.append(\n        {\"x\": xp, \"y\": -3.7, \"label\": f\"{ring['value']} / {ring['goal']} {ring['unit']}\", \"name\": ring[\"name\"]}\n    )\n\nlabel_df = pd.DataFrame(label_rows)\nsub_df = pd.DataFrame(sub_rows)\n\n# Center annotation\ncenter_df = pd.DataFrame([{\"x\": 0, \"y\": 0.22, \"label\": \"Daily\"}, {\"x\": 0, \"y\": -0.30, \"label\": \"Activity\"}])\n\n# Title — scaled for length\ntitle = \"gauge-activity-rings · python · letsplot · anyplot.ai\"\nn_chars = len(title)\nratio = 67 / n_chars if n_chars > 67 else 1.0\ntitle_size = max(11, round(16 * ratio))\n\nanyplot_theme = theme_void() + theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_border=element_blank(),\n    plot_title=element_text(color=INK, size=title_size, hjust=0.5),\n    legend_position=\"none\",\n)\n\nplot = (\n    ggplot()\n    # Background tracks (full circles, faint)\n    + geom_path(\n        data=track_df, mapping=aes(x=\"x\", y=\"y\", group=\"group\", color=\"name\"), size=8, alpha=0.15, show_legend=False\n    )\n    # Progress arcs with rounded end caps for the iconic activity-ring look\n    + geom_path(\n        data=arc_df,\n        mapping=aes(x=\"x\", y=\"y\", group=\"group\", color=\"name\"),\n        size=8,\n        show_legend=False,\n        lineend=\"round\",\n        tooltips=layer_tooltips().line(\"@name\").line(\"Progress: @pct_label\").line(\"@progress\"),\n    )\n    # Ring name + percentage labels (with letsplot interactive tooltips)\n    + geom_text(\n        data=label_df,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\", color=\"name\"),\n        size=4.0,\n        hjust=0.5,\n        show_legend=False,\n        tooltips=layer_tooltips().line(\"@name\").line(\"@pct_label\").line(\"@progress\"),\n    )\n    # Value / goal sub-labels\n    + geom_text(\n        data=sub_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_MUTED, size=3.0, hjust=0.5, show_legend=False\n    )\n    # Center text\n    + geom_text(data=center_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_SOFT, size=4.0, show_legend=False)\n    + scale_color_manual(values=ring_color_map)\n    + scale_x_continuous(limits=[-3.5, 3.5])\n    + scale_y_continuous(limits=[-4.3, 3.3])\n    + coord_fixed()\n    + labs(title=title)\n    + ggsize(600, 600)\n    + anyplot_theme\n)\n\n# Save PNG (square canvas: 600 × 600 × scale=4 = 2400 × 2400 px)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}