{"spec_id":"icicle-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nicicle-basic: Basic Icicle Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_rect,\n    element_text,\n    geom_rect,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    theme,\n    theme_void,\n)\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data: File system hierarchy with sizes (MB)\ndata = [\n    {\"name\": \"root\", \"parent\": \"\", \"value\": 0},\n    {\"name\": \"Documents\", \"parent\": \"root\", \"value\": 0},\n    {\"name\": \"Photos\", \"parent\": \"root\", \"value\": 0},\n    {\"name\": \"Projects\", \"parent\": \"root\", \"value\": 0},\n    {\"name\": \"Archive\", \"parent\": \"root\", \"value\": 0},\n    {\"name\": \"Reports\", \"parent\": \"Documents\", \"value\": 450},\n    {\"name\": \"Invoices\", \"parent\": \"Documents\", \"value\": 280},\n    {\"name\": \"Notes\", \"parent\": \"Documents\", \"value\": 180},\n    {\"name\": \"Vacation\", \"parent\": \"Photos\", \"value\": 680},\n    {\"name\": \"Family\", \"parent\": \"Photos\", \"value\": 520},\n    {\"name\": \"Events\", \"parent\": \"Photos\", \"value\": 340},\n    {\"name\": \"WebApp\", \"parent\": \"Projects\", \"value\": 0},\n    {\"name\": \"DataSci\", \"parent\": \"Projects\", \"value\": 0},\n    {\"name\": \"Mobile\", \"parent\": \"Projects\", \"value\": 380},\n    {\"name\": \"Frontend\", \"parent\": \"WebApp\", \"value\": 320},\n    {\"name\": \"Backend\", \"parent\": \"WebApp\", \"value\": 420},\n    {\"name\": \"Config\", \"parent\": \"WebApp\", \"value\": 200},\n    {\"name\": \"Models\", \"parent\": \"DataSci\", \"value\": 520},\n    {\"name\": \"Scripts\", \"parent\": \"DataSci\", \"value\": 300},\n    {\"name\": \"Old2023\", \"parent\": \"Archive\", \"value\": 1200},\n    {\"name\": \"Old2022\", \"parent\": \"Archive\", \"value\": 950},\n]\n\ndf = pd.DataFrame(data)\n\n# Build lookup tables\nname_to_idx = {row[\"name\"]: idx for idx, row in df.iterrows()}\nchildren_map = {name: df[df[\"parent\"] == name][\"name\"].tolist() for name in df[\"name\"]}\n\n# Calculate values for non-leaf nodes (bottom-up aggregation)\nprocessed = set()\nwhile len(processed) < len(df):\n    for _, row in df.iterrows():\n        name = row[\"name\"]\n        if name in processed:\n            continue\n        kids = children_map[name]\n        if len(kids) == 0:\n            processed.add(name)\n        elif all(k in processed for k in kids):\n            total = sum(df.loc[name_to_idx[k], \"value\"] for k in kids)\n            df.loc[name_to_idx[name], \"value\"] = total\n            processed.add(name)\n\n# Calculate depths (distance from root)\ndepths = {\"root\": 0}\nqueue = [\"root\"]\nwhile queue:\n    current = queue.pop(0)\n    for child in children_map[current]:\n        depths[child] = depths[current] + 1\n        queue.append(child)\n\nmax_depth = max(depths.values())\n\n# Build icicle rectangles using iterative BFS\nrects = []\nlayout_queue = [(\"root\", 0.0, 1.0)]\n\nwhile layout_queue:\n    name, x_start, x_end = layout_queue.pop(0)\n    depth = depths[name]\n    y_top = max_depth - depth + 1\n    y_bottom = max_depth - depth\n    value = df.loc[name_to_idx[name], \"value\"]\n\n    rects.append(\n        {\"name\": name, \"xmin\": x_start, \"xmax\": x_end, \"ymin\": y_bottom, \"ymax\": y_top, \"depth\": depth, \"value\": value}\n    )\n\n    # Queue children proportionally\n    kids = children_map[name]\n    if kids:\n        kid_values = [(k, df.loc[name_to_idx[k], \"value\"]) for k in kids]\n        kid_values.sort(key=lambda x: -x[1])\n        total_value = sum(v for _, v in kid_values)\n        if total_value > 0:\n            curr_x = x_start\n            for kid, val in kid_values:\n                width = (val / total_value) * (x_end - x_start)\n                layout_queue.append((kid, curr_x, curr_x + width))\n                curr_x += width\n\nrect_df = pd.DataFrame(rects)\n\n# Color palette by depth using Okabe-Ito\ncolors = {0: IMPRINT[0], 1: IMPRINT[1], 2: IMPRINT[2], 3: IMPRINT[3], 4: IMPRINT[4], 5: IMPRINT[5]}\nrect_df[\"fill_color\"] = rect_df[\"depth\"].map(colors)\n\n# Calculate label positions and widths\nrect_df[\"width\"] = rect_df[\"xmax\"] - rect_df[\"xmin\"]\nrect_df[\"x_center\"] = (rect_df[\"xmin\"] + rect_df[\"xmax\"]) / 2\nrect_df[\"y_center\"] = (rect_df[\"ymin\"] + rect_df[\"ymax\"]) / 2\n\n# Labels: show name + value for wide rectangles, name only for medium, hide for very narrow\nrect_df[\"label\"] = rect_df.apply(\n    lambda r: f\"{r['name']}\\n({int(r['value'])} MB)\" if r[\"width\"] > 0.05 else (r[\"name\"] if r[\"width\"] > 0.02 else \"\"),\n    axis=1,\n)\n\n# Convert depth to categorical with descriptive labels\nlevel_labels = {\n    0: \"Level 0 (Root)\",\n    1: \"Level 1 (Folders)\",\n    2: \"Level 2 (Subfolders)\",\n    3: \"Level 3 (Groups)\",\n    4: \"Level 4 (Items)\",\n    5: \"Level 5 (Leaf)\",\n}\nrect_df[\"depth_label\"] = pd.Categorical(\n    rect_df[\"depth\"].map(level_labels), categories=list(level_labels.values()), ordered=True\n)\n\n# Separate light and dark backgrounds for text color contrast\ndark_bg = rect_df[rect_df[\"depth\"].isin([0, 1, 3])]\nlight_bg = rect_df[rect_df[\"depth\"].isin([2, 4, 5])]\n\n# Create plot\nplot = (\n    ggplot(rect_df)\n    + geom_rect(aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"depth_label\"), color=\"white\", size=1.5)\n    + geom_text(aes(x=\"x_center\", y=\"y_center\", label=\"label\"), data=dark_bg, size=11, color=INK)\n    + geom_text(\n        aes(x=\"x_center\", y=\"y_center\", label=\"label\"),\n        data=light_bg,\n        size=11,\n        color=INK if THEME == \"light\" else INK_SOFT,\n    )\n    + scale_fill_manual(\n        values={\n            \"Level 0 (Root)\": IMPRINT[0],\n            \"Level 1 (Folders)\": IMPRINT[1],\n            \"Level 2 (Subfolders)\": IMPRINT[2],\n            \"Level 3 (Groups)\": IMPRINT[3],\n            \"Level 4 (Items)\": IMPRINT[4],\n            \"Level 5 (Leaf)\": IMPRINT[5],\n        },\n        name=\"Hierarchy Level\",\n    )\n    + labs(title=\"icicle-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),\n        figure_size=(16, 9),\n        plot_title=element_text(size=28, ha=\"center\", weight=\"bold\", color=INK),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}