{"spec_id":"treemap-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ntreemap-basic: Basic Treemap\nLibrary: plotly 6.9.0 | Python 3.13.14\nQuality: 95/100 | Updated: 2026-08-04\n\"\"\"\n\nimport colorsys\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if p != os.path.dirname(os.path.abspath(__file__))]\nimport plotly.graph_objects as go\n\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\n# Imprint palette (canonical order) for main categories\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data - Budget allocation by department and project (in thousands)\ncategories = [\n    \"Engineering\",\n    \"Engineering\",\n    \"Engineering\",\n    \"Engineering\",\n    \"Marketing\",\n    \"Marketing\",\n    \"Marketing\",\n    \"Sales\",\n    \"Sales\",\n    \"Sales\",\n    \"Operations\",\n    \"Operations\",\n    \"HR\",\n    \"HR\",\n]\nsubcategories = [\n    \"Infrastructure\",\n    \"Product Dev\",\n    \"QA\",\n    \"Research\",\n    \"Digital Ads\",\n    \"Content\",\n    \"Events\",\n    \"Direct Sales\",\n    \"Partnerships\",\n    \"Support\",\n    \"Logistics\",\n    \"Facilities\",\n    \"Recruiting\",\n    \"Training\",\n]\nvalues = [450, 380, 120, 200, 280, 150, 90, 320, 180, 140, 160, 120, 110, 80]\n\nunique_cats_ordered = [\"Engineering\", \"Marketing\", \"Sales\", \"Operations\", \"HR\"]\n\n# Category totals (each department is a treemap root)\ncategory_totals = {}\nfor cat, val in zip(categories, values, strict=True):\n    category_totals[cat] = category_totals.get(cat, 0) + val\n\n\ndef lighten(hex_color, amount):\n    \"\"\"Blend a hex color toward white in HLS space; amount 0=unchanged, 1=white.\"\"\"\n    r, g, b = (int(hex_color[i : i + 2], 16) / 255 for i in (1, 3, 5))\n    h, lightness, s = colorsys.rgb_to_hls(r, g, b)\n    lightness = lightness + (1 - lightness) * amount\n    r2, g2, b2 = colorsys.hls_to_rgb(h, lightness, s)\n    return f\"#{round(r2 * 255):02X}{round(g2 * 255):02X}{round(b2 * 255):02X}\"\n\n\ndef relative_luminance(hex_color):\n    \"\"\"WCAG relative luminance of a hex color.\"\"\"\n\n    def channel(c):\n        c /= 255\n        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4\n\n    r, g, b = (channel(int(hex_color[i : i + 2], 16)) for i in (1, 3, 5))\n    return 0.2126 * r + 0.7152 * g + 0.0722 * b\n\n\ndef best_text_color(bg_hex):\n    \"\"\"Pick whichever ink extreme has higher WCAG contrast against bg_hex.\n\n    Subcategory tiles get lightened toward white by a variable amount to encode\n    magnitude, so a single fixed text color washes out on the palest tiles in\n    dark theme. Choosing per-tile from the tile's own resulting lightness keeps\n    every leaf legible regardless of how far it was tinted.\n    \"\"\"\n    dark_ink, light_ink = \"#1A1A17\", \"#F0EFE8\"\n    bg_lum = relative_luminance(bg_hex)\n    contrast_dark = (bg_lum + 0.05) / (relative_luminance(dark_ink) + 0.05)\n    contrast_light = (relative_luminance(light_ink) + 0.05) / (bg_lum + 0.05)\n    return dark_ink if contrast_dark >= contrast_light else light_ink\n\n\n# Distinct, hierarchy-safe node ids (avoids label collisions between siblings,\n# e.g. two departments both running a \"Support\" line item); \"labels\" stays\n# display-only text, decoupled from the id used for parent lookups. Departments\n# are independent roots (parent \"\") rather than children of one synthetic\n# \"Budget\" node - Plotly's static export always renders a breadcrumb strip for\n# a single unifying root (a kaleido quirk that ignores pathbar.visible=False),\n# so per-department roots keep the canvas free of that stray element.\nids = unique_cats_ordered + [f\"{cat}/{sub}\" for cat, sub in zip(categories, subcategories, strict=True)]\nlabels = unique_cats_ordered + subcategories\nparents = [\"\"] * len(unique_cats_ordered) + categories\ntreemap_values = [category_totals[cat] for cat in unique_cats_ordered] + values\n\n# Category color assignment (Imprint, canonical order)\ncategory_color = dict(zip(unique_cats_ordered, IMPRINT, strict=True))\n\n# Subcategory shading intensity: within each category, the largest cost center\n# keeps the full hue, smaller ones lighten toward the page background - a\n# second, color-based encoding of magnitude layered on top of rectangle area.\ncategory_value_range = {\n    cat: (min(vals := [v for c, v in zip(categories, values, strict=True) if c == cat]), max(vals))\n    for cat in unique_cats_ordered\n}\n\ncolors = [category_color[cat] for cat in unique_cats_ordered]\nfor cat, val in zip(categories, values, strict=True):\n    lo, hi = category_value_range[cat]\n    share = 1.0 if hi == lo else (val - lo) / (hi - lo)\n    colors.append(lighten(category_color[cat], 0.55 - 0.4 * share))\n\n# Precomputed display text\ntext = [f\"<b>{cat}</b><br>${category_totals[cat]:,}K\" for cat in unique_cats_ordered]\ntext += [f\"{sub}<br>${val:,}K\" for sub, val in zip(subcategories, values, strict=True)]\n\n# Depth-aware label sizes reinforce the hierarchy: category headers read first,\n# cost-center labels follow at a smaller, secondary size.\ntextsizes = [24] * len(unique_cats_ordered) + [16] * len(subcategories)\n\n# Depth-aware text color: department headers sit on full-saturation base hues,\n# where the theme's INK always contrasts well, so they keep INK. Subcategory\n# tiles get lightened by a variable amount to encode magnitude, so a fixed INK\n# would wash out on the palest tiles in dark theme - pick each leaf's text\n# color from its own resulting tile lightness instead.\ntextcolors = [INK] * len(unique_cats_ordered) + [best_text_color(c) for c in colors[len(unique_cats_ordered) :]]\n\n# Create treemap\nfig = go.Figure(\n    go.Treemap(\n        ids=ids,\n        labels=labels,\n        parents=parents,\n        values=treemap_values,\n        branchvalues=\"total\",\n        text=text,\n        textinfo=\"text\",\n        textposition=\"middle center\",\n        textfont={\"size\": textsizes, \"color\": textcolors},\n        marker={\n            \"colors\": colors,\n            \"line\": {\"width\": 2, \"color\": PAGE_BG},\n            \"cornerradius\": 6,\n            \"pad\": {\"t\": 22, \"l\": 4, \"r\": 4, \"b\": 4},\n        },\n        hovertemplate=(\n            \"<b>%{label}</b><br>Budget: $%{value:,.0f}K<br>Share of department: %{percentParent:.1%}<extra></extra>\"\n        ),\n    )\n)\n\n# Layout with theme-adaptive styling\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"treemap-basic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 20, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    margin={\"t\": 45, \"l\": 20, \"r\": 20, \"b\": 20},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK_SOFT, \"size\": 14},\n)\n\n# Save\n# Hard target: 3200 x 1800 (landscape). See prompts/library/plotly.md \"Canvas\".\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}