{"spec_id":"venn-labeled-items","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items\n// Library: chartjs 4.4.7 | JavaScript 22.23.0\n// Quality: 85/100 | Created: 2026-06-25\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data ------------------------------------------------------------------\nconst circles = [\n  { name: \"Hyped Online\",     color: t.palette[0] },  // #009E73 — Imprint palette pos 1\n  { name: \"Actually Useful\",  color: t.palette[1] },  // #C475FD — Imprint palette pos 2\n  { name: \"Secretly Beloved\", color: t.palette[2] },  // #4467A3 — Imprint palette pos 3\n];\n\nconst items = [\n  { label: \"NFTs\",           zone: \"A\"   },\n  { label: \"Metaverse\",      zone: \"A\"   },\n  { label: \"Spreadsheets\",   zone: \"B\"   },\n  { label: \"Google Maps\",    zone: \"B\"   },\n  { label: \"RSS Feeds\",      zone: \"C\"   },\n  { label: \"Fax Machines\",   zone: \"C\"   },\n  { label: \"ChatGPT\",        zone: \"AB\"  },\n  { label: \"Slack\",          zone: \"AB\"  },\n  { label: \"TikTok\",         zone: \"AC\"  },\n  { label: \"Vinyl Records\",  zone: \"AC\"  },\n  { label: \"Wikipedia\",      zone: \"BC\"  },\n  { label: \"Duct Tape\",      zone: \"BC\"  },\n  { label: \"The Internet\",   zone: \"ABC\" },\n  { label: \"Coffee\",         zone: \"ABC\" },\n];\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Venn drawing plugin ---------------------------------------------------\nconst vennPlugin = {\n  id: \"vennLabeled\",\n  afterDraw(chart) {\n    const ctx = chart.ctx;\n    const W = chart.width;\n    const H = chart.height;\n\n    // Background\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, W, H);\n\n    // Layout geometry\n    const cx = W * 0.5;\n    const cy = H * 0.50;\n    const r  = Math.min(W, H) * 0.32;\n    const d  = r * 0.58;\n\n    // Circle centers — equilateral-triangle arrangement\n    const A = { x: cx - d * Math.cos(Math.PI / 6), y: cy - d * 0.55 };\n    const B = { x: cx + d * Math.cos(Math.PI / 6), y: cy - d * 0.55 };\n    const C = { x: cx,                               y: cy + d * 0.80 };\n    const ctrs = [A, B, C];\n\n    // Semi-transparent circle fills\n    for (let i = 0; i < 3; i++) {\n      ctx.save();\n      ctx.globalAlpha = 0.21;\n      ctx.beginPath();\n      ctx.arc(ctrs[i].x, ctrs[i].y, r, 0, 2 * Math.PI);\n      ctx.fillStyle = circles[i].color;\n      ctx.fill();\n      ctx.restore();\n    }\n\n    // Circle outlines\n    for (let i = 0; i < 3; i++) {\n      ctx.save();\n      ctx.globalAlpha = 0.60;\n      ctx.beginPath();\n      ctx.arc(ctrs[i].x, ctrs[i].y, r, 0, 2 * Math.PI);\n      ctx.strokeStyle = circles[i].color;\n      ctx.lineWidth = 2.5;\n      ctx.stroke();\n      ctx.restore();\n    }\n\n    // Zone centers — verified to lie inside the correct regions\n    const z = {\n      A:   { x: A.x - r * 0.50, y: A.y - r * 0.20 },\n      B:   { x: B.x + r * 0.50, y: B.y - r * 0.20 },\n      C:   { x: cx,              y: C.y + r * 0.40  },\n      AB:  { x: cx,              y: cy - d * 1.10   },\n      AC:  { x: cx - d * 0.75,  y: cy + d * 0.30   },\n      BC:  { x: cx + d * 0.75,  y: cy + d * 0.30   },\n      ABC: { x: cx,              y: cy              },\n    };\n\n    // Category name labels (outside each circle)\n    const catFontSize = Math.round(r * 0.13);\n    ctx.font = `bold ${catFontSize}px Georgia, serif`;\n    ctx.textBaseline = \"middle\";\n    const catPos = [\n      { x: A.x - r * 0.95, y: A.y - r * 0.68, align: \"center\" },\n      { x: B.x + r * 0.95, y: B.y - r * 0.68, align: \"center\" },\n      { x: cx,              y: C.y + r * 1.05,  align: \"center\" },\n    ];\n    for (let i = 0; i < 3; i++) {\n      ctx.fillStyle = circles[i].color;\n      ctx.textAlign = catPos[i].align;\n      ctx.fillText(circles[i].name, catPos[i].x, catPos[i].y);\n    }\n\n    // Group items by zone\n    const byZone = {};\n    for (const item of items) {\n      (byZone[item.zone] = byZone[item.zone] || []).push(item.label);\n    }\n\n    // Item labels inside each zone\n    const itemFontSize = Math.round(r * 0.095);\n    const lineH = itemFontSize * 1.65;\n    ctx.font = `${itemFontSize}px Georgia, serif`;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillStyle = t.inkSoft;\n\n    for (const [zone, labels] of Object.entries(byZone)) {\n      const base = z[zone];\n      if (!base) continue;\n      const startY = base.y - ((labels.length - 1) * lineH) / 2;\n      for (let i = 0; i < labels.length; i++) {\n        ctx.fillText(labels[i], base.x, startY + i * lineH);\n      }\n    }\n\n    // Title (scaled for length)\n    const titleText = \"Tech Zeitgeist · venn-labeled-items · javascript · chartjs · anyplot.ai\";\n    const titleSize = Math.max(12, Math.round(22 * 67 / titleText.length));\n    ctx.font = `bold ${titleSize}px Georgia, serif`;\n    ctx.fillStyle = t.ink;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"top\";\n    ctx.fillText(titleText, W / 2, H * 0.022);\n\n    // Subtitle\n    const subtitleSize = Math.max(10, Math.round(titleSize * 0.72));\n    ctx.font = `italic ${subtitleSize}px Georgia, serif`;\n    ctx.fillStyle = t.inkSoft;\n    ctx.fillText(\"Where does it live — hype, utility, or quiet devotion?\", W / 2, H * 0.022 + titleSize * 1.5);\n  },\n};\n\n// --- Chart (blank scatter used as canvas container + custom Venn plugin) ---\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: { datasets: [] },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: 0 },\n    plugins: {\n      legend: { display: false },\n      title:  { display: false },\n    },\n    scales: {\n      x: { display: false },\n      y: { display: false },\n    },\n  },\n  plugins: [vennPlugin],\n});\n"}