{"spec_id":"sunburst-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# sunburst-basic: Basic Sunburst Chart\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 93/100 | Created: 2026-07-26\n\nusing CairoMakie\nusing Colors\n\n# --- Theme tokens -------------------------------------------------------------\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\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\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data: repository storage breakdown by directory (MB) ---------------------\n# Source Code is broken down one level further into a grandchild ring; the\n# other branches stop at 2 levels, which mirrors how real directory trees\n# vary in depth (the spec allows 2-4 levels).\nbranches = [\n    (\"Source Code\", [\n        (\"Core Engine\",   420.0, [(\"Parser\", 180.0), (\"Renderer\", 150.0), (\"Optimizer\", 90.0)]),\n        (\"API Layer\",     180.0, [(\"REST\", 100.0), (\"GraphQL\", 80.0)]),\n        (\"UI Components\", 260.0, [(\"Components\", 140.0), (\"Hooks\", 70.0), (\"Styles\", 50.0)]),\n        (\"Utilities\",      90.0, [(\"Helpers\", 50.0), (\"Validators\", 40.0)]),\n    ]),\n    (\"Test Suite\", [\n        (\"Unit Tests\", 150.0, nothing),\n        (\"Integration Tests\", 95.0, nothing),\n        (\"Fixtures\", 40.0, nothing),\n    ]),\n    (\"Assets\", [\n        (\"Images\", 320.0, nothing),\n        (\"Fonts\", 15.0, nothing),\n        (\"Icons\", 60.0, nothing),\n    ]),\n    (\"Documentation\", [\n        (\"User Guides\", 45.0, nothing),\n        (\"API Reference\", 70.0, nothing),\n        (\"Tutorials\", 25.0, nothing),\n    ]),\n]\n\nbranch_totals = [sum(cval for (_, cval, _) in children) for (_, children) in branches]\ngrand_total = sum(branch_totals)\n\nlargest_leaf_name, largest_leaf_value = \"\", 0.0\nfor (_, children) in branches, (cname, cval, _) in children\n    if cval > largest_leaf_value\n        global largest_leaf_name, largest_leaf_value = cname, cval\n    end\nend\n\n# --- Figure --------------------------------------------------------------------\nfig = Figure(\n    size            = (1200, 1200),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\ntitle_str = \"sunburst-basic · julia · makie · anyplot.ai\"\n\nax = Axis(\n    fig[1, 1];\n    title           = title_str,\n    titlesize       = 20,\n    titlecolor      = INK,\n    aspect          = DataAspect(),\n    backgroundcolor = PAGE_BG,\n)\nhidedecorations!(ax)\nhidespines!(ax)\n\nr_hole  = 0.24\nr1      = 0.52   # branch ring outer / child ring inner\nr2      = 0.78   # child ring outer / grandchild ring inner\nr_outer = 1.00   # grandchild ring outer (only where a child has grandchildren)\nn_arc   = 48\nstroke_w = 1.5\n\nlegend_elems  = PolyElement[]\nlegend_labels = String[]\n\ntheta = 0.0\nfor (i, (name, children)) in enumerate(branches)\n    global theta\n    branch_color = IMPRINT_PALETTE[i]\n    span = 2π * branch_totals[i] / grand_total\n\n    outer_pts = [Point2f(r1 * cos(t), r1 * sin(t)) for t in range(theta, theta + span; length = n_arc)]\n    inner_pts = [Point2f(r_hole * cos(t), r_hole * sin(t)) for t in range(theta + span, theta; length = n_arc)]\n    poly!(ax, vcat(outer_pts, inner_pts); color = branch_color, strokecolor = PAGE_BG, strokewidth = stroke_w)\n\n    push!(legend_elems, PolyElement(color = branch_color))\n    push!(legend_labels, \"$(name) · $(round(Int, branch_totals[i])) MB\")\n\n    branch_lum = 0.299 * Float64(red(branch_color)) + 0.587 * Float64(green(branch_color)) + 0.114 * Float64(blue(branch_color))\n    branch_label_color = branch_lum > 0.55 ? colorant\"#1A1A17\" : colorant\"#FAF8F1\"\n\n    mid_angle = theta + span / 2\n    if span > deg2rad(14)\n        r_label = (r_hole + r1) / 2\n        rot = (mid_angle > π / 2 && mid_angle < 3π / 2) ? mid_angle + π : mid_angle\n        text!(ax, r_label * cos(mid_angle), r_label * sin(mid_angle);\n              text = name, rotation = rot, align = (:center, :center),\n              color = branch_label_color, fontsize = 15)\n    end\n\n    child_theta = theta\n    for (j, (cname, cval, grandchildren)) in enumerate(children)\n        cspan = span * cval / branch_totals[i]\n\n        child_hsl = HSL(branch_color)\n        child_l = clamp(child_hsl.l + 0.14 * j, 0.0, 0.92)\n        child_color = RGB(HSL(child_hsl.h, child_hsl.s, child_l))\n\n        c_outer = [Point2f(r2 * cos(t), r2 * sin(t)) for t in range(child_theta, child_theta + cspan; length = n_arc)]\n        c_inner = [Point2f(r1 * cos(t), r1 * sin(t)) for t in range(child_theta + cspan, child_theta; length = n_arc)]\n        poly!(ax, vcat(c_outer, c_inner); color = child_color, strokecolor = PAGE_BG, strokewidth = stroke_w)\n\n        child_lum = 0.299 * Float64(red(child_color)) + 0.587 * Float64(green(child_color)) + 0.114 * Float64(blue(child_color))\n        child_label_color = child_lum > 0.55 ? colorant\"#1A1A17\" : colorant\"#FAF8F1\"\n\n        cmid = child_theta + cspan / 2\n        is_largest = cname == largest_leaf_name\n        if cspan > deg2rad(10)\n            r_label = (r1 + r2) / 2\n            rot = (cmid > π / 2 && cmid < 3π / 2) ? cmid + π : cmid\n            text!(ax, r_label * cos(cmid), r_label * sin(cmid);\n                  text = is_largest ? \"$(cname) ★\" : cname, rotation = rot, align = (:center, :center),\n                  color = child_label_color, fontsize = is_largest ? 13 : 12)\n        end\n\n        if grandchildren !== nothing\n            gc_theta = child_theta\n            for (k, (gname, gval)) in enumerate(grandchildren)\n                gspan = cspan * gval / cval\n\n                gc_l = clamp(child_hsl.l + 0.12 * k + 0.06, 0.0, 0.95)\n                gc_color = RGB(HSL(child_hsl.h, child_hsl.s * 0.9, gc_l))\n\n                g_outer = [Point2f(r_outer * cos(t), r_outer * sin(t)) for t in range(gc_theta, gc_theta + gspan; length = n_arc)]\n                g_inner = [Point2f(r2 * cos(t), r2 * sin(t)) for t in range(gc_theta + gspan, gc_theta; length = n_arc)]\n                poly!(ax, vcat(g_outer, g_inner); color = gc_color, strokecolor = PAGE_BG, strokewidth = stroke_w)\n\n                gc_lum = 0.299 * Float64(red(gc_color)) + 0.587 * Float64(green(gc_color)) + 0.114 * Float64(blue(gc_color))\n                gc_label_color = gc_lum > 0.55 ? colorant\"#1A1A17\" : colorant\"#FAF8F1\"\n\n                gmid = gc_theta + gspan / 2\n                if gspan > deg2rad(8)\n                    r_label = (r2 + r_outer) / 2\n                    rot = (gmid > π / 2 && gmid < 3π / 2) ? gmid + π : gmid\n                    text!(ax, r_label * cos(gmid), r_label * sin(gmid);\n                          text = gname, rotation = rot, align = (:center, :center),\n                          color = gc_label_color, fontsize = 10)\n                end\n\n                gc_theta += gspan\n            end\n        end\n\n        child_theta += cspan\n    end\n\n    theta += span\nend\n\n# --- Center focal point: grand total, as a storytelling anchor ----------------\ntext!(ax, 0.0, 0.055; text = \"TOTAL\", align = (:center, :center), color = INK_SOFT, fontsize = 11)\ntext!(ax, 0.0, -0.06; text = \"$(round(Int, grand_total)) MB\", align = (:center, :center), color = INK, fontsize = 17)\n\n# --- Inset legend: branch totals, doubling as a Makie GridLayout value table --\nLegend(\n    fig[1, 1], legend_elems, legend_labels;\n    tellwidth = false, tellheight = false,\n    halign = :right, valign = :top,\n    margin = (10, 10, 10, 10),\n    framevisible = false, backgroundcolor = :transparent,\n    labelcolor = INK_SOFT, labelsize = 11, patchsize = (12, 12), rowgap = 2,\n)\n\nxlims!(ax, -1.15, 1.15)\nylims!(ax, -1.15, 1.15)\n\n# --- Save ------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}