{"spec_id":"network-hierarchical","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nnetwork-hierarchical: Hierarchical Network Graph with Tree Layout\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    scale_color_manual,\n    scale_size_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nnp.random.seed(42)\n\n# Data: Small tech company organizational chart with 30 employees across 4 levels\nnodes = [\n    # Level 0 - CEO\n    {\"id\": 0, \"label\": \"CEO\", \"level\": 0, \"parent\": None},\n    # Level 1 - VPs\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 Ops\", \"level\": 1, \"parent\": 0},\n    # Level 2 - Managers under VP Engineering\n    {\"id\": 4, \"label\": \"Mgr FE\", \"level\": 2, \"parent\": 1},\n    {\"id\": 5, \"label\": \"Mgr BE\", \"level\": 2, \"parent\": 1},\n    {\"id\": 6, \"label\": \"Mgr QA\", \"level\": 2, \"parent\": 1},\n    # Level 2 - Managers under VP Sales\n    {\"id\": 7, \"label\": \"Mgr West\", \"level\": 2, \"parent\": 2},\n    {\"id\": 8, \"label\": \"Mgr East\", \"level\": 2, \"parent\": 2},\n    # Level 2 - Managers under VP Ops\n    {\"id\": 9, \"label\": \"Mgr HR\", \"level\": 2, \"parent\": 3},\n    {\"id\": 10, \"label\": \"Mgr IT\", \"level\": 2, \"parent\": 3},\n    {\"id\": 11, \"label\": \"Mgr Fin\", \"level\": 2, \"parent\": 3},\n    # Level 3 - Engineers under Mgr FE\n    {\"id\": 12, \"label\": \"Dev 1\", \"level\": 3, \"parent\": 4},\n    {\"id\": 13, \"label\": \"Dev 2\", \"level\": 3, \"parent\": 4},\n    {\"id\": 14, \"label\": \"Dev 3\", \"level\": 3, \"parent\": 4},\n    # Level 3 - Engineers under Mgr BE\n    {\"id\": 15, \"label\": \"Dev 4\", \"level\": 3, \"parent\": 5},\n    {\"id\": 16, \"label\": \"Dev 5\", \"level\": 3, \"parent\": 5},\n    {\"id\": 17, \"label\": \"Dev 6\", \"level\": 3, \"parent\": 5},\n    # Level 3 - QA under Mgr QA\n    {\"id\": 18, \"label\": \"QA 1\", \"level\": 3, \"parent\": 6},\n    {\"id\": 19, \"label\": \"QA 2\", \"level\": 3, \"parent\": 6},\n    # Level 3 - Sales reps under Mgr West\n    {\"id\": 20, \"label\": \"Rep 1\", \"level\": 3, \"parent\": 7},\n    {\"id\": 21, \"label\": \"Rep 2\", \"level\": 3, \"parent\": 7},\n    # Level 3 - Sales reps under Mgr East\n    {\"id\": 22, \"label\": \"Rep 3\", \"level\": 3, \"parent\": 8},\n    {\"id\": 23, \"label\": \"Rep 4\", \"level\": 3, \"parent\": 8},\n    {\"id\": 24, \"label\": \"Rep 5\", \"level\": 3, \"parent\": 8},\n    # Level 3 - Ops staff under HR\n    {\"id\": 25, \"label\": \"HR 1\", \"level\": 3, \"parent\": 9},\n    # Level 3 - IT staff\n    {\"id\": 26, \"label\": \"IT 1\", \"level\": 3, \"parent\": 10},\n    {\"id\": 27, \"label\": \"IT 2\", \"level\": 3, \"parent\": 10},\n    # Level 3 - Finance staff\n    {\"id\": 28, \"label\": \"Fin 1\", \"level\": 3, \"parent\": 11},\n    {\"id\": 29, \"label\": \"Fin 2\", \"level\": 3, \"parent\": 11},\n]\n\n# Build edges from parent-child relationships\nedges = [(node[\"parent\"], node[\"id\"]) for node in nodes if node[\"parent\"] is not None]\n\n# Build tree structure for layout calculation\nchildren = {node[\"id\"]: [] for node in nodes}\nfor parent, child in edges:\n    children[parent].append(child)\n\n# Calculate x positions using a tree layout algorithm\nx_pos = {}\ny_pos = {}\n\n\ndef count_leaves(node_id):\n    \"\"\"Count number of leaf nodes in subtree.\"\"\"\n    if not children[node_id]:\n        return 1\n    return sum(count_leaves(c) for c in children[node_id])\n\n\ndef assign_x_positions(node_id, left_bound, right_bound):\n    \"\"\"Assign x positions recursively, centering parents over children.\"\"\"\n    if not children[node_id]:\n        x_pos[node_id] = (left_bound + right_bound) / 2\n        return\n\n    child_leaves = [(c, count_leaves(c)) for c in children[node_id]]\n    total_leaves = sum(leaves for _, leaves in child_leaves)\n\n    current_left = left_bound\n    for child_id, num_leaves in child_leaves:\n        proportion = num_leaves / total_leaves\n        child_width = (right_bound - left_bound) * proportion\n        child_right = current_left + child_width\n        assign_x_positions(child_id, current_left, child_right)\n        current_left = child_right\n\n    child_x_values = [x_pos[c] for c in children[node_id]]\n    x_pos[node_id] = (min(child_x_values) + max(child_x_values)) / 2\n\n\n# Assign x positions starting from root\nassign_x_positions(0, 0, 1)\n\n# Assign y positions based on level (root at top)\nmax_level = max(node[\"level\"] for node in nodes)\nfor node in nodes:\n    y_pos[node[\"id\"]] = 1 - (node[\"level\"] / max_level)\n\n# Okabe-Ito palette: first level always #009E73, then follow canonical order\nokabe_ito = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\nlevel_names = [\"Executive\", \"VP\", \"Manager\", \"Staff\"]\n\n# Create edges dataframe\nedge_data = []\nfor parent, child in edges:\n    x0, y0 = x_pos[parent], y_pos[parent]\n    x1, y1 = x_pos[child], y_pos[child]\n    edge_data.append({\"x\": x0, \"y\": y0, \"xend\": x1, \"yend\": y1})\n\ndf_edges = pd.DataFrame(edge_data)\n\n# Create nodes dataframe\nnode_data = []\nfor node in nodes:\n    nid = node[\"id\"]\n    level = node[\"level\"]\n    size = 18 - level * 3\n    node_data.append(\n        {\n            \"x\": x_pos[nid],\n            \"y\": y_pos[nid],\n            \"label\": node[\"label\"],\n            \"level_name\": level_names[level],\n            \"size\": size,\n            \"label_y\": y_pos[nid] + 0.035,\n        }\n    )\n\ndf_nodes = pd.DataFrame(node_data)\n\n# Build the plot with theme-adaptive styling\nplot = (\n    ggplot()\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_edges, color=INK_SOFT, size=1.5, alpha=0.6)\n    + geom_point(aes(x=\"x\", y=\"y\", color=\"level_name\", size=\"size\"), data=df_nodes, stroke=2, alpha=0.95)\n    + geom_text(aes(x=\"x\", y=\"label_y\", label=\"label\"), data=df_nodes, size=8, color=INK, fontface=\"bold\")\n    + scale_color_manual(values=okabe_ito, name=\"Level\")\n    + scale_size_identity()\n    + scale_x_continuous(limits=(-0.05, 1.05))\n    + scale_y_continuous(limits=(-0.08, 1.12))\n    + labs(title=\"network-hierarchical · letsplot · anyplot.ai\")\n    + ggsize(1600, 900)\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=24, face=\"bold\", color=INK),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid=element_blank(),\n        legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT),\n        legend_text=element_text(size=14, color=INK_SOFT),\n        legend_title=element_text(size=16, face=\"bold\", color=INK),\n        legend_position=\"right\",\n    )\n)\n\n# Save as PNG (scale 3x to get 4800 x 2700 px)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\n\n# Save as HTML for interactivity\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}