{"spec_id":"circlepacking-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ncirclepacking-basic: Circle Packing Chart\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as patches\nimport matplotlib.pyplot as plt\nimport numpy as np\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"\n\n# Set random seed for reproducibility\nnp.random.seed(42)\n\n# Hierarchical data: Company departments with team sizes\nhierarchy_data = [\n    (\"Company\", None, 0),\n    (\"Engineering\", \"Company\", 1),\n    (\"Product\", \"Company\", 1),\n    (\"Operations\", \"Company\", 1),\n    (\"Sales\", \"Company\", 1),\n    (\"Frontend\", \"Engineering\", 25),\n    (\"Backend\", \"Engineering\", 35),\n    (\"DevOps\", \"Engineering\", 15),\n    (\"QA\", \"Engineering\", 20),\n    (\"Design\", \"Product\", 18),\n    (\"Research\", \"Product\", 12),\n    (\"PM\", \"Product\", 8),\n    (\"HR\", \"Operations\", 10),\n    (\"Finance\", \"Operations\", 12),\n    (\"Legal\", \"Operations\", 6),\n    (\"Admin\", \"Operations\", 8),\n    (\"North\", \"Sales\", 22),\n    (\"South\", \"Sales\", 18),\n    (\"Intl\", \"Sales\", 28),\n]\n\n# Build node structure\nnodes = {}\nfor name, parent, value in hierarchy_data:\n    nodes[name] = {\"name\": name, \"parent\": parent, \"value\": value, \"children\": []}\n\n# Link children to parents\nfor name, node in nodes.items():\n    if node[\"parent\"]:\n        nodes[node[\"parent\"]][\"children\"].append(name)\n\n# Compute values for branch nodes\nfor name in [\"Engineering\", \"Product\", \"Operations\", \"Sales\"]:\n    nodes[name][\"value\"] = sum(nodes[c][\"value\"] for c in nodes[name][\"children\"])\nnodes[\"Company\"][\"value\"] = sum(nodes[c][\"value\"] for c in nodes[\"Company\"][\"children\"])\n\n# Depth-based colors using Okabe-Ito palette\ndepth_colors = {\n    0: BRAND,  # Root: brand green\n    1: \"#C475FD\",  # Departments: vermillion\n    2: \"#4467A3\",  # Teams: blue\n}\n\n# Circle packing layout\ncircles = []\n\n# Root circle\nroot_radius = 280\ncircles.append({\"name\": \"Company\", \"x\": 0, \"y\": 0, \"radius\": root_radius, \"depth\": 0})\n\n# Department positioning\ndept_names = [\"Engineering\", \"Product\", \"Operations\", \"Sales\"]\ndept_values = [nodes[d][\"value\"] for d in dept_names]\ntotal_dept = sum(dept_values)\n\ndept_ring_radius = root_radius * 0.52\ndept_angles = [np.pi * 0.75, np.pi * 0.25, -np.pi * 0.25, -np.pi * 0.75]\n\ndept_circles = {}\nfor i, dept in enumerate(dept_names):\n    dept_radius = np.sqrt(dept_values[i] / total_dept) * root_radius * 0.42\n    dept_x = dept_ring_radius * np.cos(dept_angles[i])\n    dept_y = dept_ring_radius * np.sin(dept_angles[i])\n    circles.append({\"name\": dept, \"x\": dept_x, \"y\": dept_y, \"radius\": dept_radius, \"depth\": 1})\n    dept_circles[dept] = {\"x\": dept_x, \"y\": dept_y, \"radius\": dept_radius}\n\n# Team positioning within each department\nfor dept in dept_names:\n    children = nodes[dept][\"children\"]\n    if not children:\n        continue\n\n    parent = dept_circles[dept]\n    child_values = [nodes[c][\"value\"] for c in children]\n    total_child = sum(child_values)\n    n_children = len(children)\n\n    child_ring_radius = parent[\"radius\"] * 0.55\n    angle_step = 2 * np.pi / n_children\n    start_angle = np.pi / 2\n\n    for j, child in enumerate(children):\n        child_radius = np.sqrt(child_values[j] / total_child) * parent[\"radius\"] * 0.40\n        angle = start_angle + j * angle_step\n        child_x = parent[\"x\"] + child_ring_radius * np.cos(angle)\n        child_y = parent[\"y\"] + child_ring_radius * np.sin(angle)\n\n        # Ensure child stays within parent boundary\n        dist_to_parent_center = np.sqrt((child_x - parent[\"x\"]) ** 2 + (child_y - parent[\"y\"]) ** 2)\n        max_dist = parent[\"radius\"] - child_radius - 3\n        if dist_to_parent_center + child_radius > parent[\"radius\"] - 2:\n            scale = max_dist / dist_to_parent_center\n            child_x = parent[\"x\"] + (child_x - parent[\"x\"]) * scale\n            child_y = parent[\"y\"] + (child_y - parent[\"y\"]) * scale\n\n        circles.append({\"name\": child, \"x\": child_x, \"y\": child_y, \"radius\": child_radius, \"depth\": 2})\n\n# Create figure (square format for symmetric visualization)\nfig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Draw circles from largest to smallest\ncircles_sorted = sorted(circles, key=lambda c: -c[\"radius\"])\n\nfor circle in circles_sorted:\n    color = depth_colors.get(circle[\"depth\"], INK_MUTED)\n\n    circ = patches.Circle(\n        (circle[\"x\"], circle[\"y\"]), circle[\"radius\"], facecolor=color, edgecolor=INK_SOFT, linewidth=2.5, alpha=0.8\n    )\n    ax.add_patch(circ)\n\n    # Add labels for circles that are large enough\n    if circle[\"radius\"] > 20:\n        fontsize = min(18, max(11, circle[\"radius\"] * 0.25))\n        ax.text(\n            circle[\"x\"],\n            circle[\"y\"],\n            circle[\"name\"],\n            ha=\"center\",\n            va=\"center\",\n            fontsize=fontsize,\n            fontweight=\"bold\",\n            color=INK,\n        )\n\n# Set equal aspect ratio and limits\nax.set_aspect(\"equal\")\npadding = root_radius * 0.15\nax.set_xlim(-root_radius - padding, root_radius + padding)\nax.set_ylim(-root_radius - padding, root_radius + padding)\n\n# Remove axes for cleaner visualization\nax.axis(\"off\")\n\n# Title\nax.set_title(\"circlepacking-basic · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"bold\", pad=20, color=INK)\n\n# Legend\nlegend_elements = [\n    patches.Patch(facecolor=depth_colors[0], edgecolor=INK_SOFT, alpha=0.8, label=\"Company (Root)\"),\n    patches.Patch(facecolor=depth_colors[1], edgecolor=INK_SOFT, alpha=0.8, label=\"Departments\"),\n    patches.Patch(facecolor=depth_colors[2], edgecolor=INK_SOFT, alpha=0.8, label=\"Teams\"),\n]\nleg = ax.legend(handles=legend_elements, loc=\"upper right\", fontsize=16, framealpha=0.95)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nfor text in leg.get_texts():\n    text.set_color(INK_SOFT)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}