{"spec_id":"pie-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// pie-basic: Basic Pie Chart\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-02\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Data — global cloud-infrastructure market share (illustrative 2025 figures).\n// Real categories in descending share, \"Others\" pinned at the end as the\n// catch-all bucket.\nconst labels = [\n  \"AWS\",\n  \"Microsoft Azure\",\n  \"Google Cloud\",\n  \"Alibaba Cloud\",\n  \"Oracle Cloud\",\n  \"Others\",\n];\nconst shares = [31, 25, 11, 4, 3, 26];\n\n// --- Color helpers ----------------------------------------------------------\nconst hexToRgb = (h) => [\n  parseInt(h.slice(1, 3), 16),\n  parseInt(h.slice(3, 5), 16),\n  parseInt(h.slice(5, 7), 16),\n];\nconst blendRgb = (hex, bgHex, w) => {\n  const [r, g, b] = hexToRgb(hex);\n  const [br, bg, bb] = hexToRgb(bgHex);\n  return [\n    Math.round(r * w + br * (1 - w)),\n    Math.round(g * w + bg * (1 - w)),\n    Math.round(b * w + bb * (1 - w)),\n  ];\n};\nconst relLuminance = ([r, g, b]) => {\n  const norm = [r, g, b].map((c) => {\n    const s = c / 255;\n    return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);\n  });\n  return 0.2126 * norm[0] + 0.7152 * norm[1] + 0.0722 * norm[2];\n};\n\n// Imprint palette positions 1→6 — first slice (the AWS leader) is brand green.\n// The \"Others\" bucket is blended 60% toward the page background so it reads as\n// \"rest\", letting the named categories carry the narrative.\nconst sliceRgb = shares.map((_, i) => {\n  const hex = t.palette[i % t.palette.length];\n  return labels[i] === \"Others\" ? blendRgb(hex, t.pageBg, 0.6) : hexToRgb(hex);\n});\nconst sliceColors = sliceRgb.map(([r, g, b]) => `rgb(${r}, ${g}, ${b})`);\n\n// Per-slice label color: white on dark wedges, dark ink on light wedges.\n// Threshold 0.3 routes lavender + cyan (and the bg-blended Others in light\n// theme) to dark ink — the borderline white-on-light-slice contrast called out\n// in the previous review.\nconst labelColors = sliceRgb.map((rgb) =>\n  relLuminance(rgb) > 0.3 ? \"#1A1A17\" : \"#FAF8F1\",\n);\n\n// Spec invites a slight explode on the largest (or smallest) slice. Pulling\n// the leader out by a constant pixel offset draws the eye without distorting\n// angle.\nconst maxIdx = shares.indexOf(Math.max(...shares));\nconst sliceOffsets = shares.map((_, i) => (i === maxIdx ? 30 : 0));\n\nconst total = shares.reduce((a, b) => a + b, 0);\n\n// Custom plugin — percent label at each slice's geometric midpoint. Slivers\n// (< 5%) defer to the legend, which spells out the percentage anyway, so the\n// narrow 3% / 4% wedges don't have to crowd their own digits.\nconst percentLabels = {\n  id: \"percentLabels\",\n  afterDatasetsDraw(chart) {\n    const { ctx } = chart;\n    const meta = chart.getDatasetMeta(0);\n    ctx.save();\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.font =\n      '600 26px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n    meta.data.forEach((arc, i) => {\n      const pct = (shares[i] / total) * 100;\n      if (pct < 5) return;\n      const mid = (arc.startAngle + arc.endAngle) / 2;\n      const r = (arc.innerRadius + arc.outerRadius) / 2;\n      const x = arc.x + Math.cos(mid) * r;\n      const y = arc.y + Math.sin(mid) * r;\n      ctx.fillStyle = labelColors[i];\n      ctx.fillText(`${pct.toFixed(0)}%`, x, y);\n    });\n    ctx.restore();\n  },\n};\n\n// Mount\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\nnew Chart(canvas, {\n  type: \"pie\",\n  data: {\n    labels,\n    datasets: [\n      {\n        label: \"Market share\",\n        data: shares,\n        backgroundColor: sliceColors,\n        borderColor: t.pageBg,\n        borderWidth: 3,\n        offset: sliceOffsets,\n        hoverOffset: 14,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 24, right: 24, bottom: 24, left: 24 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"pie-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 34, weight: \"500\" },\n        padding: { top: 4, bottom: 36 },\n      },\n      legend: {\n        position: \"right\",\n        align: \"center\",\n        labels: {\n          color: t.ink,\n          font: { size: 20 },\n          boxWidth: 26,\n          boxHeight: 20,\n          padding: 20,\n          generateLabels: (chart) => {\n            const data = chart.data;\n            const arr = data.datasets[0].data;\n            return data.labels.map((label, i) => ({\n              text: `${label} — ${arr[i]}%`,\n              fillStyle: data.datasets[0].backgroundColor[i],\n              strokeStyle: data.datasets[0].backgroundColor[i],\n              lineWidth: 0,\n              index: i,\n            }));\n          },\n        },\n      },\n      tooltip: {\n        backgroundColor: t.elevatedBg,\n        titleColor: t.ink,\n        bodyColor: t.inkSoft,\n        borderColor: t.grid,\n        borderWidth: 1,\n        padding: 12,\n        titleFont: { size: 14, weight: \"600\" },\n        bodyFont: { size: 13 },\n        displayColors: false,\n        callbacks: {\n          label: (ctx) => `${ctx.parsed}% of global spend`,\n        },\n      },\n    },\n  },\n  plugins: [percentLabels],\n});\n"}