{"spec_id":"network-hierarchical","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nnetwork-hierarchical: Hierarchical Network Graph with Tree Layout\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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 levels\nLEVEL_COLORS = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Organizational chart with 25 employees across 4 levels\nnp.random.seed(42)\n\n# Define hierarchical structure: CEO -> VPs -> Directors -> Managers\nnodes = [\n    # Level 0 - CEO\n    {\"id\": 0, \"label\": \"CEO\", \"level\": 0, \"parent\": None},\n    # Level 1 - VPs (4 reports)\n    {\"id\": 1, \"label\": \"VP Eng\", \"level\": 1, \"parent\": 0},\n    {\"id\": 2, \"label\": \"VP Sales\", \"level\": 1, \"parent\": 0},\n    {\"id\": 3, \"label\": \"VP Mkt\", \"level\": 1, \"parent\": 0},\n    {\"id\": 4, \"label\": \"VP Ops\", \"level\": 1, \"parent\": 0},\n    # Level 2 - Directors (8 total, 2 per VP)\n    {\"id\": 5, \"label\": \"Frontend\", \"level\": 2, \"parent\": 1},\n    {\"id\": 6, \"label\": \"Backend\", \"level\": 2, \"parent\": 1},\n    {\"id\": 7, \"label\": \"East\", \"level\": 2, \"parent\": 2},\n    {\"id\": 8, \"label\": \"West\", \"level\": 2, \"parent\": 2},\n    {\"id\": 9, \"label\": \"Digital\", \"level\": 2, \"parent\": 3},\n    {\"id\": 10, \"label\": \"Brand\", \"level\": 2, \"parent\": 3},\n    {\"id\": 11, \"label\": \"Logistics\", \"level\": 2, \"parent\": 4},\n    {\"id\": 12, \"label\": \"Facilities\", \"level\": 2, \"parent\": 4},\n    # Level 3 - Managers/Team Leads (12 total)\n    {\"id\": 13, \"label\": \"UI\", \"level\": 3, \"parent\": 5},\n    {\"id\": 14, \"label\": \"UX\", \"level\": 3, \"parent\": 5},\n    {\"id\": 15, \"label\": \"API\", \"level\": 3, \"parent\": 6},\n    {\"id\": 16, \"label\": \"NE\", \"level\": 3, \"parent\": 7},\n    {\"id\": 17, \"label\": \"SE\", \"level\": 3, \"parent\": 7},\n    {\"id\": 18, \"label\": \"NW\", \"level\": 3, \"parent\": 8},\n    {\"id\": 19, \"label\": \"Social\", \"level\": 3, \"parent\": 9},\n    {\"id\": 20, \"label\": \"Content\", \"level\": 3, \"parent\": 9},\n    {\"id\": 21, \"label\": \"PR\", \"level\": 3, \"parent\": 10},\n    {\"id\": 22, \"label\": \"Design\", \"level\": 3, \"parent\": 10},\n    {\"id\": 23, \"label\": \"Supply\", \"level\": 3, \"parent\": 11},\n    {\"id\": 24, \"label\": \"Office\", \"level\": 3, \"parent\": 12},\n]\n\n# Build children map\nchildren = {n[\"id\"]: [] for n in nodes}\nfor n in nodes:\n    if n[\"parent\"] is not None:\n        children[n[\"parent\"]].append(n[\"id\"])\n\n# Compute subtree widths iteratively (bottom-up)\nsubtree_width = {}\nfor level in [3, 2, 1, 0]:\n    for n in nodes:\n        if n[\"level\"] == level:\n            nid = n[\"id\"]\n            if not children[nid]:\n                subtree_width[nid] = 1\n            else:\n                subtree_width[nid] = sum(subtree_width[c] for c in children[nid])\n\n# Assign positions using BFS (level by level)\nnode_positions = {}\n# Start with root\nnode_positions[0] = (subtree_width[0] / 2, 0)\nqueue = [0]\nranges = {0: (0, subtree_width[0])}\n\nwhile queue:\n    nid = queue.pop(0)\n    x_start, x_end = ranges[nid]\n    x = (x_start + x_end) / 2\n    level = next(n[\"level\"] for n in nodes if n[\"id\"] == nid)\n    y = -level  # Negative so root is at top\n    node_positions[nid] = (x, y)\n\n    kids = children[nid]\n    if kids:\n        total_w = sum(subtree_width[c] for c in kids)\n        current_x = x_start\n        for child in kids:\n            child_w = subtree_width[child]\n            child_end = current_x + (x_end - x_start) * child_w / total_w\n            ranges[child] = (current_x, child_end)\n            queue.append(child)\n            current_x = child_end\n\n# Create nodes DataFrame with positions\nnodes_df = pd.DataFrame(nodes)\nnodes_df[\"x\"] = nodes_df[\"id\"].map(lambda i: node_positions[i][0])\nnodes_df[\"y\"] = nodes_df[\"id\"].map(lambda i: node_positions[i][1])\n\n# Create edges DataFrame with line segments (two points per edge for mark_line)\nedges_data = []\nedge_id = 0\nfor n in nodes:\n    if n[\"parent\"] is not None:\n        parent_id = n[\"parent\"]\n        child_id = n[\"id\"]\n        # Each edge has two points: parent and child\n        edges_data.append({\"edge_id\": edge_id, \"x\": node_positions[parent_id][0], \"y\": node_positions[parent_id][1]})\n        edges_data.append({\"edge_id\": edge_id, \"x\": node_positions[child_id][0], \"y\": node_positions[child_id][1]})\n        edge_id += 1\nedges_df = pd.DataFrame(edges_data)\n\n# Create edge layer - lines connecting nodes using mark_line with detail encoding\nedge_layer = (\n    alt.Chart(edges_df)\n    .mark_line(strokeWidth=3, opacity=0.4, color=INK_SOFT)\n    .encode(x=alt.X(\"x:Q\", axis=None), y=alt.Y(\"y:Q\", axis=None), detail=\"edge_id:N\")\n)\n\n# Create node layer - circles for each employee\nnode_layer = (\n    alt.Chart(nodes_df)\n    .mark_circle(size=1200, stroke=INK, strokeWidth=3)\n    .encode(\n        x=alt.X(\"x:Q\", axis=None),\n        y=alt.Y(\"y:Q\", axis=None),\n        color=alt.Color(\n            \"level:N\",\n            scale=alt.Scale(domain=[0, 1, 2, 3], range=LEVEL_COLORS),\n            legend=alt.Legend(\n                title=\"Level\",\n                labelFontSize=16,\n                titleFontSize=18,\n                symbolSize=300,\n                labelExpr=\"datum.value == 0 ? 'Executive' : datum.value == 1 ? 'VP' : datum.value == 2 ? 'Director' : 'Manager'\",\n            ),\n        ),\n        tooltip=[\"label:N\", \"level:O\"],\n    )\n)\n\n# Create label layer - text labels for nodes (adjusted vertical spacing to prevent overlap)\nlabel_layer = (\n    alt.Chart(nodes_df)\n    .mark_text(dy=-35, fontSize=18, fontWeight=\"bold\")\n    .encode(x=alt.X(\"x:Q\"), y=alt.Y(\"y:Q\"), text=\"label:N\", color=alt.value(INK))\n)\n\n# Combine layers\nchart = (\n    alt.layer(edge_layer, node_layer, label_layer)\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\n            \"network-hierarchical · altair · anyplot.ai\",\n            fontSize=28,\n            anchor=\"middle\",\n            subtitle=\"Organizational Chart: 25 employees across 4 management levels\",\n            subtitleFontSize=18,\n            subtitleColor=INK_SOFT,\n        ),\n        background=PAGE_BG,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_legend(\n        orient=\"right\", padding=20, fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK\n    )\n)\n\n# Save as PNG and HTML\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}