{"spec_id":"hive-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// hive-basic: Basic Hive Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// --- Data: a software module dependency network -----------------------------\n// Three axes group modules by type (core, utility, interface). Node position\n// along its axis encodes total degree (few dependencies near the center, many\n// near the rim) — this is what makes a hive plot reproducible: unlike a\n// force-directed layout, the same network always lands in the same place.\nlet seed = 20260905 % 2147483647;\nif (seed <= 0) seed += 2147483646;\nfunction rand() {\n  seed = (seed * 16807) % 2147483647;\n  return (seed - 1) / 2147483646;\n}\n\nconst CORE_NAMES = [\n  \"scheduler\", \"executor\", \"router\", \"dispatcher\", \"orchestrator\", \"planner\",\n  \"coordinator\", \"allocator\", \"balancer\", \"monitor\", \"resolver\", \"validator\",\n  \"session-mgr\", \"txn-mgr\",\n];\nconst UTILITY_NAMES = [\n  \"logging\", \"caching\", \"config\", \"metrics\", \"retry\", \"serializer\",\n  \"compression\", \"encryption\", \"pooling\", \"throttling\", \"tracing\",\n  \"formatting\", \"hashing\", \"timing\",\n];\nconst INTERFACE_NAMES = [\n  \"rest-api\", \"graphql-api\", \"grpc-api\", \"websocket-api\", \"cli\", \"admin-ui\",\n  \"webhook\", \"event-bus\", \"plugin-sdk\", \"sdk-js\", \"sdk-python\",\n  \"batch-import\", \"export-api\", \"health-check\",\n];\n\nconst AXES = [\n  { key: \"core\", label: \"Core\", prefix: \"core-\", names: CORE_NAMES, angle: -90 },\n  { key: \"utility\", label: \"Utility\", prefix: \"util-\", names: UTILITY_NAMES, angle: 30 },\n  { key: \"interface\", label: \"Interface\", prefix: \"iface-\", names: INTERFACE_NAMES, angle: 150 },\n];\n\nconst nodes = [];\nconst nodesByAxis = [[], [], []];\nAXES.forEach((axis, axisIdx) => {\n  axis.names.forEach((name) => {\n    const globalIdx = nodes.length;\n    nodes.push({ label: axis.prefix + name, axisIdx, degree: 0 });\n    nodesByAxis[axisIdx].push(globalIdx);\n  });\n});\n\nconst edges = [];\nfunction addEdge(sourceIdx, targetIdx, weight) {\n  edges.push({ source: sourceIdx, target: targetIdx, weight });\n  nodes[sourceIdx].degree += 1;\n  nodes[targetIdx].degree += 1;\n}\n\n// Core modules depend on 1-2 utility modules, and often expose 0-1 interfaces.\nnodesByAxis[0].forEach((coreIdx) => {\n  const utilCount = 1 + Math.floor(rand() * 2);\n  const chosen = new Set();\n  while (chosen.size < utilCount) {\n    chosen.add(nodesByAxis[1][Math.floor(rand() * nodesByAxis[1].length)]);\n  }\n  chosen.forEach((utilIdx) => addEdge(coreIdx, utilIdx, 1 + Math.floor(rand() * 5)));\n\n  if (rand() < 0.7) {\n    const ifaceIdx = nodesByAxis[2][Math.floor(rand() * nodesByAxis[2].length)];\n    addEdge(coreIdx, ifaceIdx, 1 + Math.floor(rand() * 5));\n  }\n});\n\n// Utility modules sometimes back an interface directly (e.g. a metrics endpoint).\nnodesByAxis[1].forEach((utilIdx) => {\n  if (rand() < 0.55) {\n    const ifaceIdx = nodesByAxis[2][Math.floor(rand() * nodesByAxis[2].length)];\n    addEdge(utilIdx, ifaceIdx, 1 + Math.floor(rand() * 5));\n  }\n});\n\n// --- Radial layout: rank-order nodes on each axis by degree ------------------\n// The three axes point up (Core) and down-left/down-right (Interface/Utility),\n// so the triangle's vertical and horizontal reach differ — size and center the\n// layout from the actual axis geometry rather than assuming a symmetric circle,\n// so the canvas margins above/below/left/right come out even.\nconst axisAngleRad = AXES.map((axis) => (axis.angle * Math.PI) / 180);\nconst sins = axisAngleRad.map(Math.sin);\nconst coss = axisAngleRad.map(Math.cos);\nconst topFactor = -Math.min(...sins, 0);\nconst bottomFactor = Math.max(...sins, 0);\nconst leftFactor = -Math.min(...coss, 0);\nconst rightFactor = Math.max(...coss, 0);\n\nconst titleClearance = 120;\nconst legendClearance = 140;\nconst sideMargin = 150;\nconst availableHeight = size.height - titleClearance - legendClearance;\n\nconst cx = size.width / 2;\nconst maxRadius = Math.min(\n  (size.width - 2 * sideMargin) / (leftFactor + rightFactor),\n  availableHeight / (topFactor + bottomFactor),\n);\nconst apexY = titleClearance + (availableHeight - (topFactor + bottomFactor) * maxRadius) / 2;\nconst cy = apexY + topFactor * maxRadius;\nconst innerRadius = maxRadius * 0.16;\n\nconst hubIndices = [];\nnodesByAxis.forEach((indices) => {\n  const ranked = [...indices].sort((a, b) => nodes[a].degree - nodes[b].degree);\n  ranked.forEach((globalIdx, rank) => {\n    const frac = ranked.length > 1 ? rank / (ranked.length - 1) : 0;\n    nodes[globalIdx].radius = innerRadius + frac * (maxRadius - innerRadius);\n  });\n  hubIndices.push(ranked[ranked.length - 1]);\n});\nAXES.forEach((axis, axisIdx) => {\n  nodesByAxis[axisIdx].forEach((globalIdx) => {\n    nodes[globalIdx].angle = axisAngleRad[axisIdx];\n  });\n});\n\nfunction toXY(radius, angle) {\n  return [cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)];\n}\nfunction circularMean(a, b) {\n  const y = (Math.sin(a) + Math.sin(b)) / 2;\n  const x = (Math.cos(a) + Math.cos(b)) / 2;\n  return Math.atan2(y, x);\n}\nfunction nodeRadius(node) {\n  return 7 + node.degree * 1.4;\n}\n\n// --- Render: axes, edges (bowed toward center), nodes ------------------------\nfunction renderAxis(params) {\n  const axis = AXES[params.dataIndex];\n  const angleRad = (axis.angle * Math.PI) / 180;\n  const [ex, ey] = toXY(maxRadius, angleRad);\n  const [lx, ly] = toXY(maxRadius + 60, angleRad);\n  const cosA = Math.cos(angleRad);\n  const sinA = Math.sin(angleRad);\n  return {\n    type: \"group\",\n    children: [\n      {\n        type: \"line\",\n        shape: { x1: cx, y1: cy, x2: ex, y2: ey },\n        style: { stroke: t.grid, lineWidth: 2.5 },\n      },\n      {\n        type: \"text\",\n        x: lx,\n        y: ly,\n        style: {\n          text: axis.label,\n          fill: t.ink,\n          fontSize: 17,\n          fontWeight: 600,\n          align: cosA > 0.3 ? \"left\" : cosA < -0.3 ? \"right\" : \"center\",\n          verticalAlign: sinA < -0.3 ? \"bottom\" : sinA > 0.3 ? \"top\" : \"middle\",\n        },\n      },\n    ],\n  };\n}\n\nfunction renderEdge(params) {\n  const edge = edges[params.dataIndex];\n  const source = nodes[edge.source];\n  const target = nodes[edge.target];\n  const [x1, y1] = toXY(source.radius, source.angle);\n  const [x2, y2] = toXY(target.radius, target.angle);\n  const midAngle = circularMean(source.angle, target.angle);\n  const midRadius = Math.min(source.radius, target.radius) * 0.35;\n  const [cpx, cpy] = toXY(midRadius, midAngle);\n  return {\n    type: \"bezierCurve\",\n    shape: { x1, y1, x2, y2, cpx1: cpx, cpy1: cpy },\n    style: {\n      stroke: t.palette[source.axisIdx],\n      fill: \"none\",\n      lineWidth: 1 + (edge.weight / 5) * 2,\n      opacity: 0.2 + (edge.weight / 5) * 0.35,\n    },\n  };\n}\n\nfunction renderNode(params) {\n  const node = nodes[params.dataIndex];\n  const [x, y] = toXY(node.radius, node.angle);\n  return {\n    type: \"circle\",\n    shape: { cx: x, cy: y, r: nodeRadius(node) },\n    style: { fill: t.palette[node.axisIdx], stroke: t.pageBg, lineWidth: 2 },\n  };\n}\n\n// Highlight the busiest (\"hub\") module on each axis with a thin halo ring —\n// sharpens the story of *which* node drives the axis's highest degree.\nfunction renderHub(params) {\n  const node = nodes[hubIndices[params.dataIndex]];\n  const [x, y] = toXY(node.radius, node.angle);\n  return {\n    type: \"circle\",\n    shape: { cx: x, cy: y, r: nodeRadius(node) + 5 },\n    style: {\n      fill: \"none\",\n      stroke: t.palette[node.axisIdx],\n      lineWidth: 1.5,\n      lineDash: [3, 3],\n      opacity: 0.8,\n    },\n  };\n}\n\n// --- Legend ------------------------------------------------------------------\nconst legendItemWidth = 260;\nconst legendY = size.height - 46;\nconst legendStartX = cx - (AXES.length * legendItemWidth) / 2;\nconst legend = AXES.map((axis, i) => ({\n  type: \"group\",\n  left: legendStartX + i * legendItemWidth,\n  top: legendY,\n  children: [\n    { type: \"circle\", shape: { cx: 8, cy: 8, r: 8 }, style: { fill: t.palette[i] } },\n    {\n      type: \"text\",\n      x: 24,\n      y: 8,\n      style: { text: `${axis.label} modules`, fill: t.inkSoft, fontSize: 17, verticalAlign: \"middle\" },\n    },\n  ],\n}));\n\n// --- Init + option ------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"hive-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  tooltip: {\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink },\n  },\n  graphic: legend,\n  series: [\n    {\n      type: \"custom\",\n      coordinateSystem: \"none\",\n      renderItem: renderAxis,\n      data: AXES.map((_, i) => i),\n      silent: true,\n      z: 1,\n    },\n    {\n      type: \"custom\",\n      coordinateSystem: \"none\",\n      renderItem: renderEdge,\n      data: edges.map((_, i) => i),\n      z: 2,\n      tooltip: {\n        formatter: (params) => {\n          const edge = edges[params.dataIndex];\n          return `${nodes[edge.source].label} → ${nodes[edge.target].label}<br/>Weight: ${edge.weight}`;\n        },\n      },\n    },\n    {\n      type: \"custom\",\n      coordinateSystem: \"none\",\n      renderItem: renderNode,\n      data: nodes.map((_, i) => i),\n      z: 3,\n      tooltip: {\n        formatter: (params) => {\n          const node = nodes[params.dataIndex];\n          return `${node.label}<br/>Category: ${AXES[node.axisIdx].label}<br/>Connections: ${node.degree}`;\n        },\n      },\n    },\n    {\n      type: \"custom\",\n      coordinateSystem: \"none\",\n      renderItem: renderHub,\n      data: hubIndices.map((_, i) => i),\n      z: 4,\n      silent: true,\n    },\n  ],\n});\n"}