{"spec_id":"gauge-activity-rings","library":"d3","language":"javascript","code":"// anyplot.ai\n// gauge-activity-rings: Activity Rings Progress Chart\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-14\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data: daily fitness goals (3-ring canonical layout) --------------------\nconst rings = [\n  { metric: \"Move\",     value: 420, goal: 600, unit: \"kcal\" },\n  { metric: \"Exercise\", value: 25,  goal: 30,  unit: \"min\"  },\n  { metric: \"Stand\",    value: 9,   goal: 12,  unit: \"hr\"   },\n];\nconst colors = [t.palette[0], t.palette[1], t.palette[2]];\n\n// --- Layout -----------------------------------------------------------------\nconst cx     = width / 2;\nconst cy     = Math.round(height * 0.48);\nconst strokeW = Math.round(width * 0.05);    // 60px track width at 1200 CSS px\nconst step    = Math.round(width * 0.0708);  // 85px center-to-center ring spacing\nconst outerR  = Math.round(width * 0.25);    // 300px outermost ring radius\nconst radii   = rings.map((_, i) => outerR - i * step);\n\nconst START = -Math.PI / 2; // 12 o'clock position\n\n// --- SVG mount --------------------------------------------------------------\nconst svg = d3.select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height);\n\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${cx},${cy})`);\n\n// SVG arc path for a stroke-based circular arc (supports stroke-linecap: round)\nconst arcPath = (r, fraction) => {\n  const f = Math.min(fraction, 0.9999);\n  const angle = f * 2 * Math.PI;\n  const x1 = r * Math.cos(START);\n  const y1 = r * Math.sin(START);\n  const x2 = r * Math.cos(START + angle);\n  const y2 = r * Math.sin(START + angle);\n  return `M ${x1},${y1} A ${r},${r} 0 ${angle > Math.PI ? 1 : 0},1 ${x2},${y2}`;\n};\n\n// --- Draw rings (outer → inner) ---------------------------------------------\nrings.forEach((d, i) => {\n  const r = radii[i];\n  const fraction = d.value / d.goal;\n  const color = colors[i];\n\n  // Full-circle background track\n  g.append(\"circle\")\n    .attr(\"r\", r)\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke\", color)\n    .attr(\"stroke-width\", strokeW)\n    .attr(\"opacity\", 0.18);\n\n  // Progress arc with rounded linecaps (iconic activity-ring look)\n  g.append(\"path\")\n    .attr(\"d\", arcPath(r, fraction))\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke\", color)\n    .attr(\"stroke-width\", strokeW)\n    .attr(\"stroke-linecap\", \"round\");\n});\n\n// --- Center label: average completion ---------------------------------------\nconst avgPct  = Math.round(rings.reduce((s, d) => s + d.value / d.goal, 0) / rings.length * 100);\nconst pctSize = Math.round(width * 0.038);  // 46px\nconst subSize = Math.round(width * 0.014);  // 17px\n\ng.append(\"text\")\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"y\", Math.round(pctSize * 0.36))\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", `${pctSize}px`)\n  .style(\"font-weight\", \"700\")\n  .text(`${avgPct}%`);\n\ng.append(\"text\")\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"y\", Math.round(pctSize * 0.36 + subSize + 9))\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", `${subSize}px`)\n  .text(\"complete\");\n\n// --- Legend below rings -----------------------------------------------------\nconst legendY  = Math.round(cy + outerR + strokeW / 2 + height * 0.04);\nconst colW     = Math.round(width / 3);\nconst nameSize = Math.round(width * 0.017);  // 20px\nconst valSize  = Math.round(width * 0.013);  // 16px\nconst dotR     = Math.round(width * 0.008);  // 10px\n\nrings.forEach((d, i) => {\n  const lx   = colW * i + colW / 2;\n  const pct  = Math.round(d.value / d.goal * 100);\n  const color = colors[i];\n\n  svg.append(\"circle\")\n    .attr(\"cx\", lx - Math.round(nameSize * 4.5))\n    .attr(\"cy\", legendY)\n    .attr(\"r\", dotR)\n    .attr(\"fill\", color);\n\n  svg.append(\"text\")\n    .attr(\"x\", lx)\n    .attr(\"y\", legendY + 5)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", `${nameSize}px`)\n    .style(\"font-weight\", \"600\")\n    .text(`${d.metric}  ·  ${pct}%`);\n\n  svg.append(\"text\")\n    .attr(\"x\", lx)\n    .attr(\"y\", legendY + nameSize + 14)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", `${valSize}px`)\n    .text(`${d.value} / ${d.goal} ${d.unit}`);\n});\n\n// --- Title ------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"gauge-activity-rings · javascript · d3 · anyplot.ai\");\n"}