{"spec_id":"circlepacking-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ncirclepacking-basic: Circle Packing Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_rect,\n    element_text,\n    geom_polygon,\n    geom_text,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_fill_manual,\n    scale_size_identity,\n    theme,\n    theme_void,\n)\n\n\nnp.random.seed(42)\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Investment portfolio hierarchy\n# Format: (id, label, parent_id, value)\nnodes = [\n    (\"root\", \"Portfolio\", None, None),\n    (\"stocks\", \"Stocks\", \"root\", 450000),\n    (\"bonds\", \"Bonds\", \"root\", 250000),\n    (\"realestate\", \"Real Estate\", \"root\", 180000),\n    (\"tech\", \"Tech\", \"stocks\", 180000),\n    (\"healthcare\", \"Healthcare\", \"stocks\", 150000),\n    (\"finance\", \"Finance\", \"stocks\", 120000),\n    (\"corp\", \"Corporate\", \"bonds\", 150000),\n    (\"govt\", \"Government\", \"bonds\", 100000),\n    (\"commercial\", \"Commercial\", \"realestate\", 120000),\n    (\"residential\", \"Residential\", \"realestate\", 60000),\n]\n\n# Circle positions and radii\nroot_x, root_y, root_r = 0.0, 0.0, 1.0\n\n# Asset class radii (area encoding: r ∝ sqrt(value))\nasset_values = {\"stocks\": 450000, \"bonds\": 250000, \"realestate\": 180000}\nasset_total = sum(asset_values.values())\nasset_scale = 0.46\n\nstocks_r = np.sqrt(asset_values[\"stocks\"] / asset_total) * asset_scale\nbonds_r = np.sqrt(asset_values[\"bonds\"] / asset_total) * asset_scale\nrealestate_r = np.sqrt(asset_values[\"realestate\"] / asset_total) * asset_scale\n\n# Position asset classes in triangular arrangement\nstocks_x, stocks_y = 0.0, 0.32\nbonds_x, bonds_y = -0.32, -0.10\nrealestate_x, realestate_y = 0.32, -0.10\n\n# Stock holdings\nstock_holdings = {\"tech\": 180000, \"healthcare\": 150000, \"finance\": 120000}\nstock_total = sum(stock_holdings.values())\nstock_scale = stocks_r * 0.72\n\ntech_r = np.sqrt(stock_holdings[\"tech\"] / stock_total) * stock_scale\nhealthcare_r = np.sqrt(stock_holdings[\"healthcare\"] / stock_total) * stock_scale\nfinance_r = np.sqrt(stock_holdings[\"finance\"] / stock_total) * stock_scale\n\ntech_x, tech_y = stocks_x - 0.10, stocks_y\nhealthcare_x, healthcare_y = stocks_x + 0.10, stocks_y\nfinance_x, finance_y = stocks_x + 0.0, stocks_y - 0.14\n\n# Bond holdings\nbond_holdings = {\"corp\": 150000, \"govt\": 100000}\nbond_total = sum(bond_holdings.values())\nbond_scale = bonds_r * 0.68\n\ncorp_r = np.sqrt(bond_holdings[\"corp\"] / bond_total) * bond_scale\ngovt_r = np.sqrt(bond_holdings[\"govt\"] / bond_total) * bond_scale\n\ncorp_x, corp_y = bonds_x + 0.0, bonds_y + 0.08\ngovt_x, govt_y = bonds_x + 0.0, bonds_y - 0.10\n\n# Real estate holdings\nrealestate_holdings = {\"commercial\": 120000, \"residential\": 60000}\nrealestate_total = sum(realestate_holdings.values())\nrealestate_scale = realestate_r * 0.68\n\ncommercial_r = np.sqrt(realestate_holdings[\"commercial\"] / realestate_total) * realestate_scale\nresidential_r = np.sqrt(realestate_holdings[\"residential\"] / realestate_total) * realestate_scale\n\ncommercial_x, commercial_y = realestate_x + 0.0, realestate_y + 0.08\nresidential_x, residential_y = realestate_x + 0.0, realestate_y - 0.10\n\n# All circles data: (id, label, cx, cy, r, depth)\ncircles_data = [\n    (\"root\", \"Portfolio\", root_x, root_y, root_r, 0),\n    (\"stocks\", \"Stocks\", stocks_x, stocks_y, stocks_r, 1),\n    (\"bonds\", \"Bonds\", bonds_x, bonds_y, bonds_r, 1),\n    (\"realestate\", \"Real Estate\", realestate_x, realestate_y, realestate_r, 1),\n    (\"tech\", \"Tech\", tech_x, tech_y, tech_r, 2),\n    (\"healthcare\", \"Healthcare\", healthcare_x, healthcare_y, healthcare_r, 2),\n    (\"finance\", \"Finance\", finance_x, finance_y, finance_r, 2),\n    (\"corp\", \"Corporate\", corp_x, corp_y, corp_r, 2),\n    (\"govt\", \"Government\", govt_x, govt_y, govt_r, 2),\n    (\"commercial\", \"Commercial\", commercial_x, commercial_y, commercial_r, 2),\n    (\"residential\", \"Residential\", residential_x, residential_y, residential_r, 2),\n]\n\n# Sort by depth for proper layering\ncircles_data = sorted(circles_data, key=lambda c: c[5])\n\n# Build polygon dataframe for drawing circles\npolygon_rows = []\nn_points = 64\n\nfor circle_id, _label, cx, cy, r, depth in circles_data:\n    angles = np.linspace(0, 2 * np.pi, n_points)\n    xs = cx + r * np.cos(angles)\n    ys = cy + r * np.sin(angles)\n    for j, (x, y) in enumerate(zip(xs, ys, strict=True)):\n        polygon_rows.append({\"circle_id\": circle_id, \"x\": x, \"y\": y, \"order\": j, \"depth\": depth})\n\ndf_circles = pd.DataFrame(polygon_rows)\n\n# Build labels dataframe\nlabel_rows = []\nfor _circle_id, label, cx, cy, r, depth in circles_data:\n    if depth == 0:\n        continue\n    if depth == 1:\n        label_y = cy + r * 0.60\n        text_size = 11\n    else:\n        label_y = cy\n        text_size = 9\n    label_rows.append({\"x\": cx, \"y\": label_y, \"label\": label, \"text_size\": text_size, \"depth\": depth})\n\ndf_labels = pd.DataFrame(label_rows)\n\n# Create the plot\nplot = (\n    ggplot()\n    + geom_polygon(\n        df_circles, aes(x=\"x\", y=\"y\", group=\"circle_id\", fill=\"factor(depth)\"), color=INK_SOFT, size=0.4, alpha=0.88\n    )\n    + geom_text(\n        df_labels, aes(x=\"x\", y=\"y\", label=\"label\", size=\"text_size\"), color=INK, fontweight=\"bold\", show_legend=False\n    )\n    + scale_fill_manual(values=IMPRINT, labels=[\"Root\", \"Asset Classes\", \"Holdings\"], name=\"Hierarchy Level\")\n    + scale_size_identity()\n    + coord_fixed(ratio=1)\n    + labs(title=\"circlepacking-basic · plotnine · anyplot.ai\")\n    + theme_void()\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        figure_size=(12, 12),\n        plot_title=element_text(size=26, ha=\"center\", weight=\"bold\", color=INK, margin={\"b\": 20}),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=13, color=INK_SOFT),\n        legend_title=element_text(size=14, weight=\"bold\", color=INK),\n        legend_position=\"bottom\",\n        legend_direction=\"horizontal\",\n    )\n    + guides(fill=guide_legend(override_aes={\"size\": 0.5}))\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=300, width=12, height=12, verbose=False)\n"}