{"spec_id":"icicle-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nicicle-basic: Basic Icicle Chart\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data - File system hierarchy with nested folders and file sizes\nnodes = [\n    {\"name\": \"Root\", \"parent\": None, \"value\": 0},\n    {\"name\": \"Documents\", \"parent\": \"Root\", \"value\": 0},\n    {\"name\": \"Media\", \"parent\": \"Root\", \"value\": 0},\n    {\"name\": \"Code\", \"parent\": \"Root\", \"value\": 0},\n    {\"name\": \"Reports\", \"parent\": \"Documents\", \"value\": 350},\n    {\"name\": \"Contracts\", \"parent\": \"Documents\", \"value\": 250},\n    {\"name\": \"Notes\", \"parent\": \"Documents\", \"value\": 150},\n    {\"name\": \"Images\", \"parent\": \"Media\", \"value\": 500},\n    {\"name\": \"Videos\", \"parent\": \"Media\", \"value\": 800},\n    {\"name\": \"Audio\", \"parent\": \"Media\", \"value\": 300},\n    {\"name\": \"Python\", \"parent\": \"Code\", \"value\": 400},\n    {\"name\": \"JavaScript\", \"parent\": \"Code\", \"value\": 350},\n    {\"name\": \"Data\", \"parent\": \"Code\", \"value\": 200},\n]\n\n# Build lookup and children map\nnode_dict = {n[\"name\"]: n for n in nodes}\nchildren = {n[\"name\"]: [] for n in nodes}\nfor n in nodes:\n    if n[\"parent\"]:\n        children[n[\"parent\"]].append(n[\"name\"])\n\n\n# Calculate leaf values for parent nodes (sum of children) - inline recursion\ndef calc_value(name):\n    if children[name]:\n        return sum(calc_value(c) for c in children[name])\n    return node_dict[name][\"value\"]\n\n\nfor n in nodes:\n    n[\"computed_value\"] = calc_value(n[\"name\"])\n\n\n# Assign levels (depth in tree) - inline recursion\ndef assign_level(name, level):\n    node_dict[name][\"level\"] = level\n    for c in children[name]:\n        assign_level(c, level + 1)\n\n\nassign_level(\"Root\", 0)\nmax_level = max(n[\"level\"] for n in nodes)\n\n# Assign colors by level using Okabe-Ito palette\nlevel_colors = []\nfor i in range(max_level + 1):\n    level_colors.append(IMPRINT[i % len(IMPRINT)])\n\n\n# Calculate icicle layout (horizontal, top-down)\ndef layout_icicle(name, x_start, x_end, rects):\n    node = node_dict[name]\n    level = node[\"level\"]\n\n    rect = {\n        \"name\": name,\n        \"level\": level,\n        \"x_start\": x_start,\n        \"x_end\": x_end,\n        \"y_start\": max_level - level,\n        \"y_end\": max_level - level + 1,\n        \"value\": node[\"computed_value\"],\n        \"color\": level_colors[min(level, len(level_colors) - 1)],\n    }\n    rects.append(rect)\n\n    if children[name]:\n        total_child_value = sum(node_dict[c][\"computed_value\"] for c in children[name])\n        current_x = x_start\n        for c in children[name]:\n            child_value = node_dict[c][\"computed_value\"]\n            child_width = (x_end - x_start) * (child_value / total_child_value)\n            layout_icicle(c, current_x, current_x + child_width, rects)\n            current_x += child_width\n\n\nrects = []\nlayout_icicle(\"Root\", 0, 100, rects)\n\n# Prepare data for Bokeh\nx_centers = [(r[\"x_start\"] + r[\"x_end\"]) / 2 for r in rects]\ny_centers = [(r[\"y_start\"] + r[\"y_end\"]) / 2 for r in rects]\nwidths = [r[\"x_end\"] - r[\"x_start\"] for r in rects]\nheights = [0.95 for r in rects]\ncolors = [r[\"color\"] for r in rects]\nnames = [r[\"name\"] for r in rects]\nvalues = [r[\"value\"] for r in rects]\n\nsource = ColumnDataSource(\n    data={\n        \"x\": x_centers,\n        \"y\": y_centers,\n        \"width\": widths,\n        \"height\": heights,\n        \"color\": colors,\n        \"name\": names,\n        \"value\": values,\n    }\n)\n\n# Create figure\np = figure(\n    width=4800,\n    height=2700,\n    title=\"icicle-basic · bokeh · anyplot.ai\",\n    x_range=(-12, 102),\n    y_range=(-0.3, max_level + 0.8),\n    tools=\"\",\n    toolbar_location=None,\n)\n\n# Draw rectangles\np.rect(\n    x=\"x\",\n    y=\"y\",\n    width=\"width\",\n    height=\"height\",\n    source=source,\n    fill_color=\"color\",\n    line_color=INK_SOFT,\n    line_width=3,\n    fill_alpha=0.9,\n)\n\n# Add labels for rectangles with sufficient width\nfor r in rects:\n    rect_width = r[\"x_end\"] - r[\"x_start\"]\n    x_center = (r[\"x_start\"] + r[\"x_end\"]) / 2\n    y_center = (r[\"y_start\"] + r[\"y_end\"]) / 2\n\n    if rect_width > 4:\n        font_size = \"24pt\" if rect_width > 20 else (\"20pt\" if rect_width > 10 else \"16pt\")\n        label_text = r[\"name\"]\n        if r[\"level\"] > 0 and rect_width > 6:\n            label_text = f\"{r['name']} ({r['value']} MB)\"\n\n        label = Label(\n            x=x_center,\n            y=y_center,\n            text=label_text,\n            text_align=\"center\",\n            text_baseline=\"middle\",\n            text_font_size=font_size,\n            text_color=INK,\n        )\n        p.add_layout(label)\n\n# Styling\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\np.title.align = \"center\"\n\n# Hide axes\np.xaxis.visible = False\np.yaxis.visible = False\np.xgrid.visible = False\np.ygrid.visible = False\n\n# Theme-adaptive background and outline\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\n# Add level labels on the left\nlevel_labels = [\"Root\", \"Categories\", \"Subcategories\"]\nfor i, label_text in enumerate(level_labels[: max_level + 1]):\n    label = Label(\n        x=-1,\n        y=max_level - i + 0.5,\n        text=label_text,\n        text_align=\"right\",\n        text_baseline=\"middle\",\n        text_font_size=\"20pt\",\n        text_color=INK_SOFT,\n    )\n    p.add_layout(label)\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}