{"spec_id":"pictogram-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// pictogram-basic: Pictogram Chart (Isotype Visualization)\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-03\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Data: Top coffee-producing countries (million 60 kg bags per year, approx.)\nconst categories = [\"Brazil\", \"Vietnam\", \"Colombia\", \"Indonesia\", \"Ethiopia\"];\nconst values = [63, 29, 14, 10, 9];\nconst ICON_VALUE = 7; // one icon = 7 million 60 kg bags\n\nconst maxCols = Math.ceil(Math.max(...values) / ICON_VALUE);\n\n// Mount\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// Draw a fully filled circle icon\nfunction filledCircle(ctx, cx, cy, r, color) {\n  ctx.beginPath();\n  ctx.arc(cx, cy, r, 0, Math.PI * 2);\n  ctx.fillStyle = color;\n  ctx.fill();\n}\n\n// Draw a partially filled circle (left-to-right fill) with dim ghost behind it\nfunction partialCircle(ctx, cx, cy, r, color, fraction) {\n  ctx.save();\n  ctx.globalAlpha = 0.18;\n  filledCircle(ctx, cx, cy, r, color);\n  ctx.restore();\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(cx - r, cy - r - 1, r * 2 * fraction, r * 2 + 2);\n  ctx.clip();\n  filledCircle(ctx, cx, cy, r, color);\n  ctx.restore();\n}\n\n// Fill entire canvas with the page background before Chart.js renders\nconst bgPlugin = {\n  id: \"bg\",\n  beforeDraw(chart) {\n    const { ctx, width, height } = chart;\n    ctx.save();\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, width, height);\n    ctx.restore();\n  },\n};\n\n// Draw pictogram icons and footnote after Chart.js axes are rendered\nconst pictogramPlugin = {\n  id: \"pictogram\",\n  afterDraw(chart) {\n    const { ctx, scales, chartArea } = chart;\n    const meta = chart.getDatasetMeta(0);\n    const rowH = chartArea.height / categories.length;\n    const unitPx = scales.x.getPixelForValue(ICON_VALUE) - scales.x.getPixelForValue(0);\n    const r = Math.min(rowH * 0.28, unitPx * 0.38);\n\n    categories.forEach((_, i) => {\n      const cy = meta.data[i].y;\n      const color = t.palette[i % t.palette.length];\n      const full = Math.floor(values[i] / ICON_VALUE);\n      const frac = (values[i] % ICON_VALUE) / ICON_VALUE;\n\n      for (let j = 0; j < full; j++) {\n        const cx = scales.x.getPixelForValue((j + 0.5) * ICON_VALUE);\n        filledCircle(ctx, cx, cy, r, color);\n        ctx.beginPath();\n        ctx.arc(cx, cy, r, 0, Math.PI * 2);\n        ctx.strokeStyle = t.pageBg;\n        ctx.lineWidth = 2;\n        ctx.stroke();\n      }\n\n      if (frac > 0.01) {\n        const cx = scales.x.getPixelForValue((full + 0.5) * ICON_VALUE);\n        partialCircle(ctx, cx, cy, r, color, frac);\n      }\n    });\n\n    // Footnote explaining icon value and partial icons\n    ctx.save();\n    ctx.font = `16px sans-serif`;\n    ctx.fillStyle = t.inkSoft;\n    ctx.textAlign = \"left\";\n    ctx.textBaseline = \"bottom\";\n    ctx.fillText(\n      \"● = \" + ICON_VALUE + \" million 60 kg bags  ·  partial circle = fractional unit\",\n      chartArea.left,\n      chart.height - 14\n    );\n    ctx.restore();\n  },\n};\n\nnew Chart(canvas, {\n  type: \"bar\",\n  plugins: [bgPlugin, pictogramPlugin],\n  data: {\n    labels: categories,\n    datasets: [\n      {\n        data: values.map(() => 0),\n        backgroundColor: \"transparent\",\n        borderWidth: 0,\n        hoverBackgroundColor: \"transparent\",\n      },\n    ],\n  },\n  options: {\n    indexAxis: \"y\",\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"pictogram-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"bold\" },\n        padding: { top: 16, bottom: 20 },\n      },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 16 } },\n        grid: { display: false },\n        border: { display: false },\n      },\n      x: {\n        min: 0,\n        max: maxCols * ICON_VALUE + ICON_VALUE * 0.4,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: ICON_VALUE,\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n        title: {\n          display: true,\n          text: \"Production (million 60 kg bags)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n    layout: {\n      padding: { top: 10, bottom: 70, left: 10, right: 30 },\n    },\n  },\n});\n"}