{"spec_id":"treemap-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# treemap-basic: Basic Treemap\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 88/100 | Created: 2026-08-04\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens -----------------------------------------------------------------\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEV_BG  = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\",\n    colorant\"#BD8233\", colorant\"#AE3030\", colorant\"#2ABCCD\",\n    colorant\"#954477\", colorant\"#99B314\",\n]\n\n# Squarified treemap layout (Bruls, Huizing & van Wijk, 2000). These are\n# data-layout helpers only -- the plot itself stays top-level, top-down below.\nfunction row_worst_ratio(row, side)\n    s  = sum(row)\n    mx = maximum(row)\n    mn = minimum(row)\n    max((side^2 * mx) / s^2, s^2 / (side^2 * mn))\nend\n\nfunction row_rects(row, x, y, w, h)\n    s = sum(row)\n    out = NTuple{4,Float64}[]\n    if w >= h\n        # Wide container: carve a vertical column (width = s/h, full height)\n        # and stack items top-to-bottom inside it.\n        col_w = s / h\n        cy = y\n        for v in row\n            rh = v / col_w\n            push!(out, (x, cy, col_w, rh))\n            cy += rh\n        end\n    else\n        # Tall container: carve a horizontal band (height = s/w, full width)\n        # and lay items left-to-right inside it.\n        row_h = s / w\n        cx = x\n        for v in row\n            rw = v / row_h\n            push!(out, (cx, y, rw, row_h))\n            cx += rw\n        end\n    end\n    out\nend\n\nfunction squarify(values, x, y, w, h)\n    remaining = Float64.(values)\n    rects = NTuple{4,Float64}[]\n    row = Float64[]\n    cx, cy, cw, ch = x, y, w, h\n    while !isempty(remaining)\n        side  = min(cw, ch)\n        trial = vcat(row, remaining[1])\n        if isempty(row) || row_worst_ratio(trial, side) <= row_worst_ratio(row, side)\n            push!(row, popfirst!(remaining))\n        else\n            append!(rects, row_rects(row, cx, cy, cw, ch))\n            s = sum(row)\n            if cw >= ch\n                col_w = s / ch\n                cx += col_w\n                cw -= col_w\n            else\n                row_h = s / cw\n                cy += row_h\n                ch -= row_h\n            end\n            row = Float64[]\n        end\n    end\n    if !isempty(row)\n        append!(rects, row_rects(row, cx, cy, cw, ch))\n    end\n    rects\nend\n\n# Label ink chosen per-tile by relative luminance of the fill -- keeps text\n# legible whether the tile sits at full category saturation or a light tint.\nfunction contrast_ink(c)\n    0.2126 * red(c) + 0.7152 * green(c) + 0.0722 * blue(c) > 0.5 ?\n        colorant\"#1A1A17\" : colorant\"#FAF8F1\"\nend\n\n# Intra-category rank tint via perceptually uniform LCHab lightness lift\n# toward a fixed ceiling (never the theme background), so a given rank\n# renders the identical color in both light and dark PNGs -- only the\n# surrounding chrome is allowed to flip.\nfunction tint_lightness(base::RGB, frac::Float64)\n    lch      = convert(LCHab, base)\n    target_l = min(lch.l + 32.0, 92.0)\n    l        = lch.l + frac * (target_l - lch.l)\n    convert(RGB, LCHab(l, lch.c, lch.h))\nend\n\n# Data ---------------------------------------------------------------------\n# Cloud object-storage usage (TB) by owning team and application.\ncategories = [\n    (\"Engineering\", [\n        (\"CI/CD Artifacts\", 82.0), (\"Build Caches\", 54.0), (\"Container Registry\", 39.0),\n    ]),\n    (\"Data & Analytics\", [\n        (\"Data Warehouse\", 96.0), (\"ML Model Store\", 61.0), (\"Log Archive\", 45.0),\n    ]),\n    (\"Product Design\", [\n        (\"Asset Library\", 28.0), (\"Prototype Files\", 17.0),\n    ]),\n    (\"Marketing\", [\n        (\"Campaign Media\", 33.0), (\"Video Archive\", 22.0),\n    ]),\n    (\"Customer Support\", [\n        (\"Ticket Attachments\", 43.0),\n    ]),\n]\n\ncat_totals = [sum(v for (_, v) in subs) for (_, subs) in categories]\norder      = sortperm(cat_totals; rev = true)\ncategories = categories[order]\ncat_totals = cat_totals[order]\n\n# Layout ---------------------------------------------------------------------\nconst TM_W = 16.0\nconst TM_H = 9.0\narea  = TM_W * TM_H\nscale = area / sum(cat_totals)\n\ntop_rects = squarify(cat_totals .* scale, 0.0, 0.0, TM_W, TM_H)\n\n# Sub-rectangles reuse the same global `scale`, so each category's children\n# tile its parent rectangle exactly (their values sum to the parent's total).\nleaf_rects  = NTuple{4,Float64}[]\nleaf_colors = RGB[]\nleaf_names  = String[]\nleaf_values = Float64[]\ncat_boxes   = NTuple{4,Float64}[]\ncat_names   = String[]\n\nfor (i, (name, subs)) in enumerate(categories)\n    cx, cy, cw, ch = top_rects[i]\n    push!(cat_boxes, (cx, cy, cw, ch))\n    push!(cat_names, name)\n\n    base       = IMPRINT_PALETTE[i]\n    sub_order  = sortperm(last.(subs); rev = true)\n    sub_names  = first.(subs)[sub_order]\n    sub_values = last.(subs)[sub_order]\n    subrects   = squarify(sub_values .* scale, cx, cy, cw, ch)\n\n    for (j, (sx, sy, sw, sh)) in enumerate(subrects)\n        tint = length(subs) > 1 ? (j - 1) / (length(subs) - 1) : 0.0\n        push!(leaf_rects, (sx, sy, sw, sh))\n        push!(leaf_colors, tint_lightness(base, tint))\n        push!(leaf_names, sub_names[j])\n        push!(leaf_values, sub_values[j])\n    end\nend\n\n# Title scaled to fit when prefixed with a descriptive subtitle.\ntitle_text    = \"Cloud Storage Usage by Team · treemap-basic · julia · makie · anyplot.ai\"\ntitle_default = 20\ntitle_size = length(title_text) > 67 ?\n    max(round(Int, title_default * 67 / length(title_text)), 13) :\n    title_default\n\n# Plot -----------------------------------------------------------------------\nfig = Figure(resolution = (1600, 900), backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title           = title_text,\n    titlesize       = title_size,\n    titlecolor      = INK,\n    backgroundcolor = PAGE_BG,\n    aspect          = DataAspect(),\n)\nhidedecorations!(ax)\nhidespines!(ax)\n\n# Leaf tiles: fill color encodes the category (hue) and rank within it\n# (lightness), with a page-bg hairline separating adjacent tiles.\nleaf_polys = [Rect2f(x, y, w, h) for (x, y, w, h) in leaf_rects]\npoly!(ax, leaf_polys; color = leaf_colors, strokecolor = PAGE_BG, strokewidth = 2.5)\n\n# Category-group outline reads as the parent boundary above the tile hairlines.\nfor (x, y, w, h) in cat_boxes\n    poly!(ax, Rect2f(x, y, w, h);\n        color       = (:white, 0.0),\n        strokecolor = INK_SOFT,\n        strokewidth = 3.0,\n    )\nend\n\n# Category label chips: a fixed elevated-bg backing keeps the header legible\n# regardless of which tile tint sits underneath, only drawn where the parent\n# rectangle is large enough to hold it.\nfor (i, (x, y, w, h)) in enumerate(cat_boxes)\n    label = \"$(cat_names[i]) · $(round(Int, cat_totals[i])) TB\"\n    chip_w = min(w - 0.16, length(label) * 0.115 + 0.3)\n    if w >= 1.6 && h >= 0.9 && chip_w > 0.6\n        chip_h = 0.46\n        chip_x = x + 0.08\n        chip_y = y + h - 0.08 - chip_h\n        poly!(ax, Rect2f(chip_x, chip_y, chip_w, chip_h);\n            color       = ELEV_BG,\n            strokecolor = INK_SOFT,\n            strokewidth = 1.0,\n        )\n        text!(ax, chip_x + chip_w / 2, chip_y + chip_h / 2;\n            text     = label,\n            align    = (:center, :center),\n            color    = INK,\n            fontsize = 14,\n            font     = :bold,\n        )\n    end\nend\n\n# Subcategory labels: name + value, only when the tile is large enough to\n# hold two lines of text without overflow (spec: smaller tiles omit labels).\nfor (i, (x, y, w, h)) in enumerate(leaf_rects)\n    name  = leaf_names[i]\n    label = \"$(name)\\n$(round(Int, leaf_values[i])) TB\"\n    if w >= max(0.9, length(name) * 0.075) && h >= 0.55\n        text!(ax, x + w / 2, y + h / 2;\n            text     = label,\n            align    = (:center, :center),\n            color    = contrast_ink(leaf_colors[i]),\n            fontsize = 12,\n            justification = :center,\n        )\n    end\nend\n\nxlims!(ax, 0, TM_W)\nylims!(ax, 0, TM_H)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}