{"spec_id":"pictogram-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// pictogram-basic: Pictogram Chart (Isotype Visualization)\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 92/100 | Created: 2026-06-03\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\nconst UNIT = 5; // each icon represents 5,000 tonnes\n\nconst data = [\n  { label: \"Apples\",       value: 35 },\n  { label: \"Oranges\",      value: 22 },\n  { label: \"Bananas\",      value: 48 },\n  { label: \"Grapes\",       value: 16 },\n  { label: \"Strawberries\", value: 28 },\n];\n\nconst margin = { top: 90, right: 70, bottom: 110, left: 190 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\n\nconst defs = svg.append(\"defs\");\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Layout\nconst rowH = ih / data.length;\nconst iconR = Math.min(rowH * 0.26, 26);\nconst iconSpacing = iconR * 2.6;\nconst maxIcons = Math.ceil(d3.max(data, (d) => d.value) / UNIT);\n\n// Row separators (subtle)\nfor (let i = 1; i < data.length; i++) {\n  g.append(\"line\")\n    .attr(\"x1\", -margin.left * 0.1).attr(\"y1\", i * rowH)\n    .attr(\"x2\", maxIcons * iconSpacing + iconR * 2.5).attr(\"y2\", i * rowH)\n    .attr(\"stroke\", t.grid).attr(\"stroke-width\", 1);\n}\n\n// Draw each category row\ndata.forEach((d, i) => {\n  const fullIcons = Math.floor(d.value / UNIT);\n  const frac = (d.value % UNIT) / UNIT;\n  const totalIcons = fullIcons + (frac > 0 ? 1 : 0);\n  const color = t.palette[i % t.palette.length];\n  const cy = i * rowH + rowH / 2;\n\n  // Category label\n  g.append(\"text\")\n    .attr(\"x\", -16)\n    .attr(\"y\", cy)\n    .attr(\"text-anchor\", \"end\")\n    .attr(\"dominant-baseline\", \"middle\")\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", \"18px\")\n    .style(\"font-weight\", \"500\")\n    .text(d.label);\n\n  // Draw icons\n  for (let j = 0; j < maxIcons; j++) {\n    const cx = j * iconSpacing + iconR;\n\n    if (j < fullIcons) {\n      // Full filled icon\n      g.append(\"circle\")\n        .attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", iconR)\n        .attr(\"fill\", color);\n    } else if (j === fullIcons && frac > 0) {\n      // Partial icon using clipPath (coordinates in SVG root space)\n      const clipId = `clip-${i}-${j}`;\n      defs.append(\"clipPath\").attr(\"id\", clipId)\n        .append(\"rect\")\n        .attr(\"x\", margin.left + cx - iconR)\n        .attr(\"y\", margin.top + cy - iconR)\n        .attr(\"width\", iconR * 2 * frac)\n        .attr(\"height\", iconR * 2);\n\n      // Empty circle outline for the partial slot\n      g.append(\"circle\")\n        .attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", iconR)\n        .attr(\"fill\", \"none\")\n        .attr(\"stroke\", color).attr(\"stroke-width\", 1.5)\n        .attr(\"opacity\", 0.35);\n\n      // Filled portion via clip\n      g.append(\"circle\")\n        .attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", iconR)\n        .attr(\"fill\", color)\n        .attr(\"clip-path\", `url(#${clipId})`);\n    } else {\n      // Empty slot — shows the maximum scale context\n      g.append(\"circle\")\n        .attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", iconR)\n        .attr(\"fill\", \"none\")\n        .attr(\"stroke\", t.inkSoft)\n        .attr(\"stroke-width\", 1)\n        .attr(\"opacity\", 0.22);\n    }\n  }\n\n  // Value label after icon row\n  const labelX = maxIcons * iconSpacing + iconR + 14;\n  g.append(\"text\")\n    .attr(\"x\", labelX)\n    .attr(\"y\", cy)\n    .attr(\"dominant-baseline\", \"middle\")\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", \"16px\")\n    .style(\"font-weight\", \"600\")\n    .text(`${d.value}k t`);\n});\n\n// Legend at bottom\nconst legendY = ih + 68;\nconst legendR = iconR * 0.72;\n\ng.append(\"circle\")\n  .attr(\"cx\", 0).attr(\"cy\", legendY)\n  .attr(\"r\", legendR)\n  .attr(\"fill\", t.inkSoft);\n\ng.append(\"text\")\n  .attr(\"x\", legendR + 10).attr(\"y\", legendY)\n  .attr(\"dominant-baseline\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text(`= ${UNIT},000 tonnes`);\n\n// Subtitle\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", margin.top - 14)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text(\"Annual Fruit Production (thousands of tonnes)\");\n\n// Title\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"pictogram-basic · javascript · d3 · anyplot.ai\");\n"}