{"spec_id":"network-hierarchical","library":"d3","language":"javascript","code":"// anyplot.ai\n// network-hierarchical: Hierarchical Network Graph with Tree Layout\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data: a repository directory tree, root -> file leaves at varying depths\nconst projectTree = {\n  name: \"project/\",\n  children: [\n    { name: \"README.md\" },\n    { name: \"package.json\" },\n    {\n      name: \"src/\",\n      children: [\n        { name: \"components/\", children: [{ name: \"Button.tsx\" }, { name: \"Card.tsx\" }] },\n        { name: \"utils/\", children: [{ name: \"format.ts\" }] },\n        {\n          name: \"api/\",\n          children: [{ name: \"client.ts\" }, { name: \"v2/\", children: [{ name: \"endpoints.ts\" }] }],\n        },\n        { name: \"hooks/\", children: [{ name: \"useAuth.ts\" }] },\n      ],\n    },\n    {\n      name: \"tests/\",\n      children: [\n        { name: \"unit/\", children: [{ name: \"button.test.ts\" }] },\n        { name: \"integration/\", children: [{ name: \"api.test.ts\" }] },\n      ],\n    },\n    {\n      name: \"docs/\",\n      children: [\n        { name: \"quickstart.md\" },\n        { name: \"reference/\", children: [{ name: \"api.md\" }] },\n      ],\n    },\n    { name: \"config/\", children: [{ name: \"env/\", children: [{ name: \".env.example\" }] }] },\n  ],\n};\n\n// --- Layout: radial tree — angle spreads siblings around a full circle,\n// radius directly encodes depth, giving leaf labels far more room than a\n// linear column and making organizational depth read at a glance ----------\nconst root = d3.hierarchy(projectTree);\nconst maxDepth = d3.max(root.descendants(), (d) => d.depth);\n\nconst titleZone = 190; // title + horizontal legend row\nconst bottomMargin = 30;\nconst availableHeight = height - titleZone - bottomMargin;\nconst labelPadding = 140; // room for outward-pointing leaf labels\nconst maxRadius = Math.min(width, availableHeight) / 2 - labelPadding;\nconst cx = width / 2;\nconst cy = titleZone + availableHeight / 2;\n\nconst treeLayout = d3\n  .tree()\n  .size([2 * Math.PI, maxRadius])\n  .separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth);\ntreeLayout(root);\n\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${cx},${cy})`);\n\n// --- Edges: radial parent-child links, no arrows ----------------------------\nconst linkGen = d3.linkRadial().angle((d) => d.x).radius((d) => d.y);\n\ng.selectAll(\"path.link\")\n  .data(root.links())\n  .join(\"path\")\n  .attr(\"class\", \"link\")\n  .attr(\"d\", linkGen)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1.5);\n\n// --- Nodes: one categorical color per depth level (root always palette[0]) --\nconst depthColor = (depth) => t.palette[Math.min(depth, t.palette.length - 1)];\nconst depthRadius = (depth) => [12, 9.5, 8, 6.5, 5.5][Math.min(depth, 4)];\n\nconst node = g\n  .selectAll(\"g.node\")\n  .data(root.descendants())\n  .join(\"g\")\n  .attr(\"class\", \"node\")\n  .attr(\"transform\", (d) => `rotate(${(d.x * 180) / Math.PI - 90}) translate(${d.y},0)`);\n\nnode\n  .append(\"circle\")\n  .attr(\"r\", (d) => depthRadius(d.depth))\n  .attr(\"fill\", (d) => depthColor(d.depth))\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\nnode\n  .append(\"text\")\n  .attr(\"x\", (d) => (d.x < Math.PI ? 1 : -1) * (depthRadius(d.depth) + 8))\n  .attr(\"dy\", \"0.32em\")\n  .attr(\"text-anchor\", (d) => (d.x < Math.PI ? \"start\" : \"end\"))\n  .attr(\"transform\", (d) => (d.x >= Math.PI ? \"rotate(180)\" : null))\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", (d) => (d.depth === 0 ? \"17px\" : \"14px\"))\n  .style(\"font-weight\", (d) => (d.depth === 0 ? \"600\" : \"400\"))\n  .text((d) => d.data.name);\n\n// --- Legend: depth level meaning, one row under the title -------------------\nconst legendData = d3.range(maxDepth + 1).map((depth) => ({\n  depth,\n  label: depth === 0 ? \"Root\" : `Depth ${depth}`,\n}));\nconst legendItemWidth = 140;\nconst legendY = 120;\nconst legendStartX = width / 2 - (legendData.length * legendItemWidth) / 2 + legendItemWidth / 2;\n\nconst legend = svg.append(\"g\");\nlegendData.forEach((d, i) => {\n  const gx = legendStartX + i * legendItemWidth;\n  legend\n    .append(\"circle\")\n    .attr(\"cx\", gx)\n    .attr(\"cy\", legendY)\n    .attr(\"r\", 7)\n    .attr(\"fill\", depthColor(d.depth))\n    .attr(\"stroke\", t.pageBg)\n    .attr(\"stroke-width\", 1.5);\n  legend\n    .append(\"text\")\n    .attr(\"x\", gx + 14)\n    .attr(\"y\", legendY)\n    .attr(\"dy\", \"0.32em\")\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(d.label);\n});\n\n// --- Title --------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"network-hierarchical · javascript · d3 · anyplot.ai\");\n"}