{"spec_id":"upset-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# upset-basic: UpSet Plot for Multi-Set Intersection Analysis\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 96/100 | Created: 2026-09-09\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\") ---\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\"\nconst INK_MUTED  = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\nconst GRID_RGBA  = RGBAf(INK.r, INK.g, INK.b, 0.15)\n\n# Imprint palette (see prompts/default-style-guide.md \"Categorical Palette\")\nconst BRAND      = colorant\"#009E73\"  # position 1 — ALWAYS first series\nconst IMPRINT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])  # sequential — degree encoding\n\n# --- Data: differential-expression gene sets across sequencing experiments ---\n# Most genes are assay-specific hits (drawn independently per set), but a\n# \"pan-omics\" subset of broadly-active genes shows up across most assays —\n# a realistic biological pattern that also produces genuine degree-4/5\n# intersections rather than the top-15 being dominated by degree <= 3.\nset_names = [\"RNA-seq\", \"ChIP-seq\", \"ATAC-seq\", \"Proteomics\", \"Methylation\"]\nn_sets = length(set_names)\nn_genes = 3000\nmembership_probs = [0.34, 0.27, 0.21, 0.16, 0.11]\nhub_frac = 0.10   # fraction of genes that are broadly-active \"pan-omics\" hits\nhub_prob = 0.75   # per-set membership probability for those hub genes\n\nmembership = falses(n_genes, n_sets)\nfor i in 1:n_genes\n    is_hub = rand() < hub_frac\n    for j in 1:n_sets\n        p = is_hub ? hub_prob : membership_probs[j]\n        membership[i, j] = rand() < p\n    end\nend\nkeep = [any(view(membership, i, :)) for i in 1:n_genes]\nmembership = membership[keep, :]\nn_elements = size(membership, 1)\n\n# Sort sets by total size (largest drawn at the top of the matrix)\nset_sizes = vec(sum(membership, dims = 1))\nset_order = sortperm(set_sizes, rev = true)\nset_names_sorted = set_names[set_order]\nset_sizes_sorted = set_sizes[set_order]\nmembership = membership[:, set_order]\nset_y = [n_sets - k + 1 for k in 1:n_sets]  # top row = largest set\n\n# Count occurrences of each membership pattern, keep the top 15 by size\ncombo_counts = Dict{Vector{Bool}, Int}()\nfor i in 1:n_elements\n    key = membership[i, :]\n    combo_counts[key] = get(combo_counts, key, 0) + 1\nend\ncombos = collect(keys(combo_counts))\ncounts = [combo_counts[c] for c in combos]\ncombo_order = sortperm(counts, rev = true)\nn_show = min(15, length(combos))\ntop_combos = combos[combo_order[1:n_show]]\ntop_counts = counts[combo_order[1:n_show]]\ndegrees = [sum(c) for c in top_combos]\ndeg_min, deg_max = extrema(degrees)\ndeg_span = max(deg_max - deg_min, 1)\nbar_colors = [get(IMPRINT_SEQ, (d - deg_min) / deg_span) for d in degrees]\n\n# --- Figure ---------------------------------------------------------------\nfig = Figure(\n    resolution = (1600, 900),\n    fontsize = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nLabel(\n    fig[0, 1:3],\n    \"upset-basic · julia · makie · anyplot.ai\",\n    fontsize = 22,\n    font = :bold,\n    color = INK,\n)\n\nax_bars = Axis(\n    fig[1, 2];\n    ylabel = \"Intersection size\",\n    ylabelcolor = INK,\n    ylabelsize = 14,\n    yticklabelcolor = INK_SOFT,\n    yticklabelsize = 12,\n    xticksvisible = false,\n    xticklabelsvisible = false,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    bottomspinevisible = false,\n    leftspinecolor = INK_SOFT,\n    ygridcolor = GRID_RGBA,\n    ygridvisible = true,\n    xgridvisible = false,\n)\n\nax_setbars = Axis(\n    fig[2, 1];\n    xlabel = \"Set size\",\n    xlabelcolor = INK,\n    xlabelsize = 14,\n    xticklabelcolor = INK_SOFT,\n    xticklabelsize = 12,\n    yticksvisible = false,\n    yticklabelsvisible = false,\n    xreversed = true,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    leftspinevisible = false,\n    bottomspinecolor = INK_SOFT,\n    xgridcolor = GRID_RGBA,\n    xgridvisible = true,\n    ygridvisible = false,\n)\n\nax_matrix = Axis(\n    fig[2, 2];\n    yticks = (1:n_sets, reverse(set_names_sorted)),\n    yticklabelcolor = INK_SOFT,\n    yticklabelsize = 12,\n    xticksvisible = false,\n    xticklabelsvisible = false,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    leftspinevisible = false,\n    bottomspinevisible = false,\n    ygridvisible = false,\n    xgridvisible = false,\n)\n\nlinkxaxes!(ax_bars, ax_matrix)\nlinkyaxes!(ax_setbars, ax_matrix)\nxlims!(ax_matrix, 0.3, n_show + 0.7)\nylims!(ax_matrix, 0.3, n_sets + 0.7)\n\n# Intersection size bars — colored by degree (how many sets overlap)\nbarplot!(ax_bars, 1:n_show, top_counts; color = bar_colors, width = 0.65)\nylims!(ax_bars, 0, maximum(top_counts) * 1.15)\n\n# Callout on the single largest intersection to sharpen the \"aha\"\ntext!(\n    ax_bars, 1, top_counts[1];\n    text = string(top_counts[1]),\n    align = (:center, :bottom),\n    offset = (0, 4),\n    fontsize = 13,\n    color = INK,\n    font = :bold,\n)\n\n# Set size bars — single series, brand color\nbarplot!(ax_setbars, set_y, set_sizes_sorted; direction = :x, color = BRAND, width = 0.65)\n\n# Alternating row bands for readability\nfor k in 1:n_sets\n    if isodd(k)\n        hspan!(ax_matrix, set_y[k] - 0.5, set_y[k] + 0.5; color = (INK, 0.04))\n    end\nend\n\n# Dot matrix — connecting lines first, then non-member and member dots\nfor j in 1:n_show\n    combo = top_combos[j]\n    member_rows = [set_y[k] for k in 1:n_sets if combo[k]]\n    if length(member_rows) >= 2\n        lines!(ax_matrix, fill(j, 2), [minimum(member_rows), maximum(member_rows)];\n               color = INK_SOFT, linewidth = 3)\n    end\nend\n\nmember_x = Float64[]; member_yv = Float64[]\nabsent_x = Float64[]; absent_yv = Float64[]\nfor j in 1:n_show, k in 1:n_sets\n    combo = top_combos[j]\n    if combo[k]\n        push!(member_x, j); push!(member_yv, set_y[k])\n    else\n        push!(absent_x, j); push!(absent_yv, set_y[k])\n    end\nend\n\nscatter!(ax_matrix, absent_x, absent_yv; color = (INK_MUTED, 0.3), markersize = 16)\nscatter!(ax_matrix, member_x, member_yv; color = INK, markersize = 24)\n\nColorbar(\n    fig[1, 3];\n    limits = (deg_min, deg_max),\n    colormap = IMPRINT_SEQ,\n    label = \"Sets in intersection\",\n    labelcolor = INK,\n    labelsize = 14,\n    ticklabelcolor = INK_SOFT,\n    ticklabelsize = 12,\n    ticks = deg_min:deg_max,\n    width = 18,\n)\n\ncolsize!(fig.layout, 1, Relative(0.14))\ncolsize!(fig.layout, 2, Relative(0.80))\ncolsize!(fig.layout, 3, Relative(0.06))\nrowsize!(fig.layout, 1, Relative(0.32))\nrowsize!(fig.layout, 2, Relative(0.68))\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}