{"spec_id":"network-hierarchical","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// network-hierarchical: Hierarchical Network Graph with Tree Layout\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: organizational chart, 4 levels (CEO -> VP -> Director -> Manager) ---\n// Branching is intentionally irregular (3 directors under VP Engineering vs. 2\n// elsewhere, 3 managers under Dir. Frontend vs. 1-2 elsewhere) to show the span-\n// of-control variation a hierarchical layout is meant to expose.\nconst org = {\n  id: \"ceo\",\n  name: \"CEO\",\n  children: [\n    {\n      id: \"vp-eng\",\n      name: \"VP Engineering\",\n      children: [\n        {\n          id: \"dir-fe\",\n          name: \"Dir. Frontend\",\n          children: [\n            { id: \"mgr-a\", name: \"A. Kim\" },\n            { id: \"mgr-b\", name: \"B. Diaz\" },\n            { id: \"mgr-c\", name: \"C. Chen\" },\n          ],\n        },\n        {\n          id: \"dir-be\",\n          name: \"Dir. Backend\",\n          children: [\n            { id: \"mgr-d\", name: \"D. Patel\" },\n            { id: \"mgr-e\", name: \"E. Novak\" },\n          ],\n        },\n        {\n          id: \"dir-plat\",\n          name: \"Dir. Platform\",\n          children: [{ id: \"mgr-f\", name: \"F. Silva\" }],\n        },\n      ],\n    },\n    {\n      id: \"vp-sales\",\n      name: \"VP Sales\",\n      children: [\n        {\n          id: \"dir-ent\",\n          name: \"Dir. Enterprise\",\n          children: [\n            { id: \"mgr-g\", name: \"G. Osei\" },\n            { id: \"mgr-h\", name: \"H. Reyes\" },\n          ],\n        },\n        {\n          id: \"dir-smb\",\n          name: \"Dir. SMB\",\n          children: [{ id: \"mgr-i\", name: \"I. Haddad\" }],\n        },\n      ],\n    },\n    {\n      id: \"vp-mkt\",\n      name: \"VP Marketing\",\n      children: [\n        {\n          id: \"dir-brand\",\n          name: \"Dir. Brand\",\n          children: [{ id: \"mgr-j\", name: \"J. Costa\" }],\n        },\n        {\n          id: \"dir-growth\",\n          name: \"Dir. Growth\",\n          children: [{ id: \"mgr-k\", name: \"K. Ibrahim\" }],\n        },\n      ],\n    },\n    {\n      id: \"vp-fin\",\n      name: \"VP Finance\",\n      children: [\n        {\n          id: \"dir-acct\",\n          name: \"Dir. Accounting\",\n          children: [{ id: \"mgr-l\", name: \"L. Fischer\" }],\n        },\n        {\n          id: \"dir-treas\",\n          name: \"Dir. Treasury\",\n          children: [{ id: \"mgr-m\", name: \"M. Nakamura\" }],\n        },\n      ],\n    },\n  ],\n};\n\n// Flatten the tree into per-level nodes with a tidy-tree x layout (leaves get\n// sequential x, internal nodes sit above the mean x of their children) plus\n// parent-child edges for the connector overlay drawn in chart.events.render.\nconst LEVEL_NAMES = [\"CEO\", \"VP\", \"Director\", \"Manager\"];\nconst nodesByLevel = [[], [], [], []];\nconst edges = [];\nlet nextLeafX = 0;\n\nfunction layout(node, level) {\n  let x;\n  if (!node.children) {\n    x = nextLeafX;\n    nextLeafX += 1;\n  } else {\n    const childXs = node.children.map((child) => {\n      edges.push([node.id, child.id]);\n      return layout(child, level + 1);\n    });\n    x = childXs.reduce((a, b) => a + b, 0) / childXs.length;\n  }\n  nodesByLevel[level].push({ id: node.id, name: node.name, x, y: level });\n  return x;\n}\nlayout(org, 0);\n\nconst nodeById = {};\nnodesByLevel.forEach((level) => level.forEach((n) => { nodeById[n.id] = n; }));\n\n// Data-storytelling emphasis: find the Director with the most direct Manager\n// reports (the span-of-control outlier) and highlight its node + connectors.\nconst directorSpan = {};\nedges.forEach(([parentId, childId]) => {\n  if (nodeById[parentId].y === 2 && nodeById[childId].y === 3) {\n    directorSpan[parentId] = (directorSpan[parentId] || 0) + 1;\n  }\n});\nconst largestTeamId = Object.keys(directorSpan).reduce(\n  (best, id) => (directorSpan[id] > (directorSpan[best] || 0) ? id : best),\n  null,\n);\n\n// Marker radius shrinks from root to leaves, reinforcing the depth encoding\n// that color and shape already provide.\nconst LEVEL_RADII = [12, 10, 9, 8];\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      // Core Highcharts has no built-in tree/network series (those live in the\n      // unloaded networkgraph/treegraph modules), so parent-child connectors\n      // are drawn by hand as SVG paths between each pair's pixel coordinates.\n      render() {\n        const chart = this;\n        if (chart.edgeGroup) chart.edgeGroup.destroy();\n        chart.edgeGroup = chart.renderer.g(\"edges\").attr({ zIndex: 2 }).add();\n        edges.forEach(([parentId, childId]) => {\n          const p = nodeById[parentId];\n          const c = nodeById[childId];\n          const x1 = chart.xAxis[0].toPixels(p.x);\n          const y1 = chart.yAxis[0].toPixels(p.y);\n          const x2 = chart.xAxis[0].toPixels(c.x);\n          const y2 = chart.yAxis[0].toPixels(c.y);\n          // The largest-team director's connectors render bolder, flagging the\n          // span-of-control outlier rather than a purely structural read.\n          const highlighted = parentId === largestTeamId;\n          chart.renderer\n            .path([\"M\", x1, y1, \"L\", x2, y2])\n            .attr({\n              stroke: t.inkSoft,\n              \"stroke-width\": highlighted ? 2.5 : 1.5,\n              opacity: highlighted ? 0.7 : 0.45,\n            })\n            .add(chart.edgeGroup);\n        });\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"network-hierarchical · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: { visible: false, min: -0.6, max: nextLeafX - 0.4 },\n  yAxis: { visible: false, reversed: true, min: -0.4, max: 3.5 },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: { enabled: false },\n  plotOptions: {\n    series: { animation: false },\n    scatter: {\n      marker: { lineColor: t.pageBg, lineWidth: 1.5 },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.name}\",\n        verticalAlign: \"top\",\n        y: 16,\n        crop: false,\n        overflow: \"allow\",\n        style: { color: t.inkSoft, fontSize: \"13px\", fontWeight: \"normal\", textOutline: \"none\" },\n      },\n    },\n  },\n  series: LEVEL_NAMES.map((name, level) => ({\n    name,\n    color: t.palette[level],\n    marker: { radius: LEVEL_RADII[level] },\n    // The Manager row is the most crowded level and the one that blurs first at\n    // mobile thumbnail scale, so its labels get a larger, semibold font on top\n    // of the vertical stagger that already prevents horizontal collisions.\n    dataLabels:\n      level === 3 ? { style: { fontSize: \"15px\", fontWeight: \"600\" } } : undefined,\n    data: nodesByLevel[level].map((n, i) => ({\n      x: n.x,\n      y: n.y,\n      name: n.name,\n      dataLabels: level === 3 ? { y: i % 2 === 0 ? 16 : 34 } : undefined,\n      // Call out the span-of-control outlier with a slightly larger, bolder\n      // marker to match the bolder connector lines drawn in chart.events.render.\n      marker: n.id === largestTeamId ? { radius: LEVEL_RADII[level] + 3, lineWidth: 2.5 } : undefined,\n    })),\n  })),\n});\n"}