{"spec_id":"network-hierarchical","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nnetwork-hierarchical: Hierarchical Network Graph with Tree Layout\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport plotly.graph_objects as go\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette for hierarchy levels (position 1 is brand, then positions 2-4)\nLEVEL_COLORS = {\n    0: \"#009E73\",  # Position 1 - brand green (CEO)\n    1: \"#C475FD\",  # Position 2 - vermillion (VPs)\n    2: \"#4467A3\",  # Position 3 - blue (Directors)\n    3: \"#BD8233\",  # Position 4 - reddish purple (Leads)\n}\n\n# Data: Organizational chart with 22 employees across 4 levels - asymmetric structure\nnodes = {\n    # Level 0 - CEO\n    \"CEO\": {\"label\": \"CEO\", \"level\": 0, \"parent\": None},\n    # Level 1 - VPs (3 VPs, different subtree sizes)\n    \"VP_Eng\": {\"label\": \"VP Engineering\", \"level\": 1, \"parent\": \"CEO\"},\n    \"VP_Sales\": {\"label\": \"VP Sales\", \"level\": 1, \"parent\": \"CEO\"},\n    \"VP_Ops\": {\"label\": \"VP Operations\", \"level\": 1, \"parent\": \"CEO\"},\n    # Level 2 - Directors (asymmetric: 3 under Eng, 2 under Sales, 2 under Ops)\n    \"Dir_FE\": {\"label\": \"Frontend\", \"level\": 2, \"parent\": \"VP_Eng\"},\n    \"Dir_BE\": {\"label\": \"Backend\", \"level\": 2, \"parent\": \"VP_Eng\"},\n    \"Dir_QA\": {\"label\": \"QA\", \"level\": 2, \"parent\": \"VP_Eng\"},\n    \"Dir_NA\": {\"label\": \"Americas\", \"level\": 2, \"parent\": \"VP_Sales\"},\n    \"Dir_EU\": {\"label\": \"EMEA\", \"level\": 2, \"parent\": \"VP_Sales\"},\n    \"Dir_HR\": {\"label\": \"HR\", \"level\": 2, \"parent\": \"VP_Ops\"},\n    \"Dir_Fin\": {\"label\": \"Finance\", \"level\": 2, \"parent\": \"VP_Ops\"},\n    # Level 3 - Leads (asymmetric: 2 under FE, 1 under BE, 1 under QA, 2 under NA, 1 under EU, 1 under HR, 1 under Fin)\n    \"Mgr_React\": {\"label\": \"React Lead\", \"level\": 3, \"parent\": \"Dir_FE\"},\n    \"Mgr_Vue\": {\"label\": \"Vue Lead\", \"level\": 3, \"parent\": \"Dir_FE\"},\n    \"Mgr_API\": {\"label\": \"API Lead\", \"level\": 3, \"parent\": \"Dir_BE\"},\n    \"Mgr_Test\": {\"label\": \"Test Lead\", \"level\": 3, \"parent\": \"Dir_QA\"},\n    \"Mgr_East\": {\"label\": \"East Sales\", \"level\": 3, \"parent\": \"Dir_NA\"},\n    \"Mgr_West\": {\"label\": \"West Sales\", \"level\": 3, \"parent\": \"Dir_NA\"},\n    \"Mgr_UK\": {\"label\": \"UK Sales\", \"level\": 3, \"parent\": \"Dir_EU\"},\n    \"Mgr_Recruit\": {\"label\": \"Recruiting\", \"level\": 3, \"parent\": \"Dir_HR\"},\n    \"Mgr_Acct\": {\"label\": \"Accounting\", \"level\": 3, \"parent\": \"Dir_Fin\"},\n}\n\n# Build edges and children lookup\nedges = []\nchildren = {node_id: [] for node_id in nodes}\nfor node_id, data in nodes.items():\n    if data[\"parent\"]:\n        edges.append((data[\"parent\"], node_id))\n        children[data[\"parent\"]].append(node_id)\n\n# Calculate positions using bottom-up tree layout\npositions = {}\nleaf_spacing = 1.6\nlevel_height = 2.8\n\n# Get all leaf nodes (level 3) and assign sequential x positions\nleaf_nodes = [n for n in nodes if nodes[n][\"level\"] == 3]\nfor i, node_id in enumerate(leaf_nodes):\n    x = (i - (len(leaf_nodes) - 1) / 2) * leaf_spacing\n    positions[node_id] = (x, -3 * level_height)\n\n# Level 2: center each director over its children\nfor node_id in [n for n in nodes if nodes[n][\"level\"] == 2]:\n    child_xs = [positions[c][0] for c in children[node_id]]\n    center_x = sum(child_xs) / len(child_xs) if child_xs else 0\n    positions[node_id] = (center_x, -2 * level_height)\n\n# Level 1: center each VP over its children\nfor node_id in [n for n in nodes if nodes[n][\"level\"] == 1]:\n    child_xs = [positions[c][0] for c in children[node_id]]\n    center_x = sum(child_xs) / len(child_xs) if child_xs else 0\n    positions[node_id] = (center_x, -1 * level_height)\n\n# Level 0: center CEO over VPs\nceo_children = children[\"CEO\"]\ncenter_x = sum(positions[c][0] for c in ceo_children) / len(ceo_children)\npositions[\"CEO\"] = (center_x, 0)\n\n# Create edge traces with orthogonal routing\nedge_x = []\nedge_y = []\nfor parent_id, child_id in edges:\n    x0, y0 = positions[parent_id]\n    x1, y1 = positions[child_id]\n    mid_y = (y0 + y1) / 2\n    edge_x.extend([x0, x0, x1, x1, None])\n    edge_y.extend([y0, mid_y, mid_y, y1, None])\n\nedge_trace = go.Scatter(\n    x=edge_x, y=edge_y, mode=\"lines\", line={\"width\": 3.5, \"color\": INK_SOFT}, hoverinfo=\"none\", showlegend=False\n)\n\n# Create node trace\nnode_x = [positions[n][0] for n in nodes]\nnode_y = [positions[n][1] for n in nodes]\nnode_labels = [nodes[n][\"label\"] for n in nodes]\nnode_colors = [LEVEL_COLORS[nodes[n][\"level\"]] for n in nodes]\nnode_levels = [nodes[n][\"level\"] for n in nodes]\n\n# Enhanced hover text with level information\nhover_texts = [f\"{nodes[n]['label']}<br>Level {nodes[n]['level']}\" for n in nodes]\n\nnode_trace = go.Scatter(\n    x=node_x,\n    y=node_y,\n    mode=\"markers+text\",\n    marker={\"size\": 48, \"color\": node_colors, \"line\": {\"width\": 2.5, \"color\": PAGE_BG}},\n    text=node_labels,\n    textposition=\"bottom center\",\n    textfont={\"size\": 16, \"color\": INK},\n    hoverinfo=\"text\",\n    hovertext=hover_texts,\n    showlegend=False,\n)\n\n# Create figure\nfig = go.Figure(data=[edge_trace, node_trace])\n\n# Update layout with theme-adaptive styling\nfig.update_layout(\n    title={\n        \"text\": \"network-hierarchical · plotly · anyplot.ai\", \"font\": {\"size\": 28, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"\n    },\n    showlegend=False,\n    xaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False, \"title\": \"\", \"linecolor\": INK_SOFT},\n    yaxis={\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"showticklabels\": False,\n        \"title\": \"\",\n        \"scaleanchor\": \"x\",\n        \"scaleratio\": 1,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    margin={\"l\": 50, \"r\": 50, \"t\": 100, \"b\": 50},\n    height=900,\n    font={\"color\": INK},\n)\n\n# Save as PNG and HTML with theme suffix\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}