{"spec_id":"hive-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// hive-basic: Basic Hive Plot\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 85/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data: software module dependency network -------------------------------\n// Three axes group modules by architectural layer. Edges run strictly from a\n// higher layer to a lower one (interface -> utility -> core), so every edge\n// crosses axes — the standard hive-plot convention of never linking two nodes\n// on the same axis.\nconst AXES = [\n  {\n    key: \"core\",\n    label: \"core\",\n    modules: [\n      \"kernel\", \"scheduler\", \"memory-mgr\", \"io-driver\", \"syscall\",\n      \"bootloader\", \"filesystem\", \"network-stack\", \"process-mgr\", \"device-mgr\",\n    ],\n  },\n  {\n    key: \"utility\",\n    label: \"utility\",\n    modules: [\n      \"logger\", \"config\", \"string-utils\", \"math-utils\", \"json-parser\",\n      \"compressor\", \"validator\", \"cache\", \"serializer\", \"metrics\",\n    ],\n  },\n  {\n    key: \"interface\",\n    label: \"interface\",\n    modules: [\n      \"rest-api\", \"grpc-service\", \"cli\", \"websocket\", \"auth-gateway\",\n      \"admin-ui\", \"webhook\", \"graphql\", \"sdk-client\", \"plugin-api\",\n    ],\n  },\n];\n\n// Fixed-seed LCG — Math.random() is not reproducible.\nconst lcg = (seed) => {\n  let s = seed;\n  return () => {\n    s = (s * 1664525 + 1013904223) % 4294967296;\n    return s / 4294967296;\n  };\n};\nconst rand = lcg(42);\nconst randInt = (min, max) => min + Math.floor(rand() * (max - min + 1));\nconst sample = (arr, n) => {\n  const pool = [...arr];\n  const picked = [];\n  for (let i = 0; i < n && pool.length > 0; i++) {\n    picked.push(pool.splice(Math.floor(rand() * pool.length), 1)[0]);\n  }\n  return picked;\n};\n\nconst nodes = AXES.flatMap((axis) =>\n  axis.modules.map((name) => ({ id: `${axis.key}:${name}`, name, axis: axis.key }))\n);\nconst coreIds = AXES[0].modules.map((m) => `core:${m}`);\nconst utilityIds = AXES[1].modules.map((m) => `utility:${m}`);\nconst interfaceIds = AXES[2].modules.map((m) => `interface:${m}`);\n\nconst edges = [];\nfor (const id of utilityIds) {\n  for (const target of sample(coreIds, randInt(1, 3))) edges.push({ source: id, target });\n}\nfor (const id of interfaceIds) {\n  const pool = [...coreIds, ...utilityIds];\n  for (const target of sample(pool, randInt(2, 4))) edges.push({ source: id, target });\n}\n\nconst degree = new Map(nodes.map((n) => [n.id, 0]));\nfor (const e of edges) {\n  degree.set(e.source, degree.get(e.source) + 1);\n  degree.set(e.target, degree.get(e.target) + 1);\n}\n\n// --- Layout -------------------------------------------------------------------\n// Geometry tuned for the 1200x1200 square mount: the \"core\" axis points up,\n// the other two point down-left/down-right, so the triangle is taller above\n// its center than below — cy is pulled down from the mount's midpoint, but\n// only slightly, so the single upward axis still starts close under the\n// subtitle instead of leaving a dead gap across the top of the canvas.\nconst cx = width / 2;\nconst cy = 760;\nconst innerR = 55;\nconst outerR = 580;\n\nconst axisAngle = (i) => -Math.PI / 2 + i * ((2 * Math.PI) / 3);\nconst axisColor = d3.scaleOrdinal().domain(AXES.map((a) => a.key)).range(t.palette);\nconst radiusScale = d3.scaleSqrt().domain([1, d3.max([...degree.values()])]).range([6, 13]);\n\nconst nodePos = new Map();\nAXES.forEach((axis, i) => {\n  const angle = axisAngle(i);\n  const axisNodes = nodes\n    .filter((n) => n.axis === axis.key)\n    .sort((a, b) => degree.get(a.id) - degree.get(b.id));\n  const place = d3.scaleLinear().domain([0, axisNodes.length - 1]).range([innerR, outerR]);\n  axisNodes.forEach((n, rank) => {\n    const r = place(rank);\n    nodePos.set(n.id, {\n      x: cx + r * Math.cos(angle),\n      y: cy + r * Math.sin(angle),\n      r,\n      angle,\n      color: axisColor(axis.key),\n    });\n  });\n});\n\n// --- SVG mount ------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\n\n// --- Edges: quadratic curves bulging toward the center (hive-plot bundling) -\nconst edgeLayer = svg.append(\"g\").attr(\"fill\", \"none\");\nedgeLayer\n  .selectAll(\"path\")\n  .data(edges)\n  .join(\"path\")\n  .attr(\"d\", (e) => {\n    const s = nodePos.get(e.source);\n    const t2 = nodePos.get(e.target);\n    const ux = Math.cos(s.angle) + Math.cos(t2.angle);\n    const uy = Math.sin(s.angle) + Math.sin(t2.angle);\n    const len = Math.hypot(ux, uy) || 1;\n    const controlR = ((s.r + t2.r) / 2) * 0.25;\n    const qx = cx + (ux / len) * controlR;\n    const qy = cy + (uy / len) * controlR;\n    return `M${s.x},${s.y} Q${qx},${qy} ${t2.x},${t2.y}`;\n  })\n  .attr(\"stroke\", (e) => nodePos.get(e.source).color)\n  .attr(\"stroke-width\", 1.1)\n  .attr(\"stroke-opacity\", 0.22);\n\n// --- Axes -------------------------------------------------------------------\nconst axisLayer = svg.append(\"g\");\nAXES.forEach((axis, i) => {\n  const angle = axisAngle(i);\n  const x1 = cx + innerR * Math.cos(angle);\n  const y1 = cy + innerR * Math.sin(angle);\n  const x2 = cx + outerR * Math.cos(angle);\n  const y2 = cy + outerR * Math.sin(angle);\n  axisLayer\n    .append(\"line\")\n    .attr(\"x1\", x1).attr(\"y1\", y1).attr(\"x2\", x2).attr(\"y2\", y2)\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1.5);\n\n  const lx = cx + (outerR + 34) * Math.cos(angle);\n  const ly = cy + (outerR + 34) * Math.sin(angle);\n  axisLayer\n    .append(\"text\")\n    .attr(\"x\", lx).attr(\"y\", ly)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"dominant-baseline\", \"middle\")\n    .attr(\"fill\", axisColor(axis.key))\n    .style(\"font-size\", \"18px\")\n    .style(\"font-weight\", \"600\")\n    .text(axis.label);\n});\n\n// --- Nodes --------------------------------------------------------------------\nsvg\n  .append(\"g\")\n  .selectAll(\"circle\")\n  .data(nodes)\n  .join(\"circle\")\n  .attr(\"cx\", (n) => nodePos.get(n.id).x)\n  .attr(\"cy\", (n) => nodePos.get(n.id).y)\n  .attr(\"r\", (n) => radiusScale(degree.get(n.id)))\n  .attr(\"fill\", (n) => nodePos.get(n.id).color)\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\n// --- Title ------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"26px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"hive-basic · javascript · d3 · anyplot.ai\");\n\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 88)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Software module dependency network — node radius encodes degree\");\n"}