{"spec_id":"venn-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// venn-basic: Venn Diagram\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-09\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Skills reported by 250 surveyed software engineers, grouped by language.\nconst sets = [\n  { key: \"A\", name: \"Python\", size: 145, color: t.palette[0] },\n  { key: \"B\", name: \"JavaScript\", size: 120, color: t.palette[1] },\n  { key: \"C\", name: \"SQL\", size: 95, color: t.palette[2] },\n];\nconst pair = { AB: 55, AC: 40, BC: 35 };\nconst triple = 20;\n\nconst only = {\n  A: sets[0].size - pair.AB - pair.AC + triple,\n  B: sets[1].size - pair.AB - pair.BC + triple,\n  C: sets[2].size - pair.AC - pair.BC + triple,\n  AB: pair.AB - triple,\n  AC: pair.AC - triple,\n  BC: pair.BC - triple,\n  ABC: triple,\n};\n\n// --- Geometry: symmetric three-circle Venn layout ---------------------------\nconst cx = width / 2;\nconst titleClearance = height * 0.092;\nconst legendClearance = height * 0.1;\nconst rMax = Math.min(width, height) * 0.265; // radius of the largest set\n// Area-proportional radii: r ~ sqrt(size), per the spec's \"area proportional\n// to size when possible\" note.\nconst sizeScale = d3.scaleSqrt().domain([0, d3.max(sets, (d) => d.size)]).range([0, rMax]);\nconst rad = Object.fromEntries(sets.map((d) => [d.key, sizeScale(d.size)]));\nconst R = rMax * 0.68; // circumradius of the triangle formed by the 3 centers\nconst cy = titleClearance + R + rMax; // pins the top circle's top edge below the title\n\nconst centroid = { x: cx, y: cy };\nconst centers = {\n  C: { x: cx, y: cy - R }, // top\n  A: { x: cx - R * Math.sin(Math.PI / 3), y: cy + R * Math.cos(Math.PI / 3) }, // bottom-left\n  B: { x: cx + R * Math.sin(Math.PI / 3), y: cy + R * Math.cos(Math.PI / 3) }, // bottom-right\n};\n\nfunction pushFrom(p, from, dist) {\n  const dx = p.x - from.x;\n  const dy = p.y - from.y;\n  const len = Math.hypot(dx, dy) || 1;\n  return { x: p.x + (dx / len) * dist, y: p.y + (dy / len) * dist };\n}\nfunction midpoint(p, q) {\n  return { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 };\n}\n\nconst labelPos = {\n  A: pushFrom(centers.A, centroid, rad.A * 0.55),\n  B: pushFrom(centers.B, centroid, rad.B * 0.55),\n  C: pushFrom(centers.C, centroid, rad.C * 0.55),\n  AB: pushFrom(midpoint(centers.A, centers.B), centers.C, ((rad.A + rad.B) / 2) * 0.32),\n  AC: pushFrom(midpoint(centers.A, centers.C), centers.B, ((rad.A + rad.C) / 2) * 0.32),\n  BC: pushFrom(midpoint(centers.B, centers.C), centers.A, ((rad.B + rad.C) / 2) * 0.32),\n  ABC: centroid,\n};\n\n// --- SVG mount ----------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\n\n// --- Circles --------------------------------------------------------------\n// Stroke weight also scales with set size, giving the dominant set (Python) a\n// visibly heavier outline as a deliberate focal point.\nconst strokeScale = d3.scaleLinear().domain(d3.extent(sets, (d) => d.size)).range([2, 4]);\n\nsvg\n  .selectAll(\"circle\")\n  .data(sets)\n  .join(\"circle\")\n  .attr(\"cx\", (d) => centers[d.key].x)\n  .attr(\"cy\", (d) => centers[d.key].y)\n  .attr(\"r\", (d) => rad[d.key])\n  .attr(\"fill\", (d) => d.color)\n  .attr(\"fill-opacity\", 0.6)\n  .attr(\"stroke\", (d) => d.color)\n  .attr(\"stroke-width\", (d) => strokeScale(d.size))\n  .attr(\"stroke-opacity\", 0.95);\n\n// --- Region counts (halo-stroked text reads over any fill) -----------------\nconst regions = [\n  { key: \"A\", value: only.A },\n  { key: \"B\", value: only.B },\n  { key: \"C\", value: only.C },\n  { key: \"AB\", value: only.AB },\n  { key: \"AC\", value: only.AC },\n  { key: \"BC\", value: only.BC },\n  { key: \"ABC\", value: only.ABC },\n];\n\n// The triple overlap (all three sets) is the most interesting relationship in\n// a Venn diagram, so its count is rendered larger as a deliberate focal point.\nsvg\n  .selectAll(\"text.region\")\n  .data(regions)\n  .join(\"text\")\n  .attr(\"class\", \"region\")\n  .attr(\"x\", (d) => labelPos[d.key].x)\n  .attr(\"y\", (d) => labelPos[d.key].y)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"dominant-baseline\", \"middle\")\n  .style(\"font-size\", (d) => (d.key === \"ABC\" ? \"32px\" : \"26px\"))\n  .style(\"font-weight\", (d) => (d.key === \"ABC\" ? \"700\" : \"600\"))\n  .style(\"paint-order\", \"stroke\")\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", (d) => (d.key === \"ABC\" ? 6 : 5))\n  .attr(\"fill\", t.ink)\n  .text((d) => d.value);\n\n// --- Legend (set name + total size) -----------------------------------------\nconst legendY = height - legendClearance * 0.55;\nconst legendItemWidth = width / sets.length;\n\nconst legend = svg\n  .selectAll(\"g.legend-item\")\n  .data(sets)\n  .join(\"g\")\n  .attr(\"class\", \"legend-item\")\n  .attr(\n    \"transform\",\n    (d, i) => `translate(${legendItemWidth * i + legendItemWidth / 2}, ${legendY})`\n  );\n\nlegend\n  .append(\"rect\")\n  .attr(\"x\", -9)\n  .attr(\"y\", -14)\n  .attr(\"width\", 18)\n  .attr(\"height\", 18)\n  .attr(\"rx\", 3)\n  .attr(\"fill\", (d) => d.color);\n\nlegend\n  .append(\"text\")\n  .attr(\"x\", 0)\n  .attr(\"y\", 24)\n  .attr(\"text-anchor\", \"middle\")\n  .style(\"font-size\", \"17px\")\n  .style(\"font-weight\", \"500\")\n  .attr(\"fill\", t.ink)\n  .text((d) => `${d.name} (${d.size})`);\n\n// --- Title --------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", titleClearance * 0.55)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"26px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"venn-basic · javascript · d3 · anyplot.ai\");\n"}