{"spec_id":"mosaic-categorical","library":"makie","language":"julia","code":"# anyplot.ai\n# mosaic-categorical: Mosaic Plot for Categorical Association Analysis\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 87/100 | Created: 2026-09-02\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 INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n# Per-segment label ink chosen for contrast against each fixed data color\n# (green/lavender read best with dark text, blue reads best with light text).\nconst CELL_LABEL_INK = [colorant\"#1A1A17\", colorant\"#1A1A17\", colorant\"#FFFFFF\"]\n\n# --- Data ---------------------------------------------------------------------\n# Employee satisfaction survey cross-tabulated by department and satisfaction level.\ndepartments = [\"Engineering\", \"Sales\", \"Support\", \"Marketing\"]\nsatisfaction_levels = [\"Low\", \"Medium\", \"High\"]\ndepartment_sizes = [220, 180, 150, 130]\nsatisfaction_shares = [\n    0.10 0.35 0.55\n    0.20 0.45 0.35\n    0.35 0.40 0.25\n    0.15 0.40 0.45\n]\n\nn_rows = length(departments)\nn_cols = length(satisfaction_levels)\ncounts = zeros(Int, n_rows, n_cols)\nfor i in 1:n_rows, j in 1:n_cols\n    counts[i, j] = round(Int, department_sizes[i] * satisfaction_shares[i, j] * (0.9 + 0.2 * rand()))\nend\n\nrow_totals = vec(sum(counts; dims = 2))\ntotal = sum(counts)\n\n# Most notable cell: the department with the highest conditional share of\n# \"Low\" satisfaction, called out below with a bolder tile outline.\nlow_col = findfirst(==(\"Low\"), satisfaction_levels)\nnotable_row = argmax(satisfaction_shares[:, low_col])\n\n# --- Mosaic geometry ------------------------------------------------------------\n# Column widths encode marginal proportions of department; segment heights within\n# each column encode the conditional proportion of satisfaction level.\ngap_x = 0.02\ngap_y = 0.02\nusable_width = 1.0 - gap_x * (n_rows - 1)\nusable_height = 1.0 - gap_y * (n_cols - 1)\ncolumn_widths = row_totals ./ total .* usable_width\n\nx_starts = zeros(n_rows)\nfor i in 2:n_rows\n    x_starts[i] = x_starts[i - 1] + column_widths[i - 1] + gap_x\nend\n\n# --- Plot -----------------------------------------------------------------------\nfig = Figure(\n    size            = (1200, 1200),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"mosaic-categorical · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Department\",\n    ylabel             = \"Satisfaction share\",\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridvisible       = false,\n)\n\nfor i in 1:n_rows\n    y_start = 0.0\n    for j in 1:n_cols\n        height = counts[i, j] / row_totals[i] * usable_height\n        rect = Rect2f(x_starts[i], y_start, column_widths[i], height)\n        is_notable = i == notable_row && j == low_col\n        stroke_color = is_notable ? INK : PAGE_BG\n        stroke_width = is_notable ? 3.5 : 2\n        poly!(ax, rect; color = IMPRINT_PALETTE[j], strokecolor = stroke_color, strokewidth = stroke_width)\n\n        if height > 0.05\n            pct = round(Int, counts[i, j] / total * 100)\n            text!(\n                ax, x_starts[i] + column_widths[i] / 2, y_start + height / 2;\n                text = \"$(pct)%\", color = CELL_LABEL_INK[j], fontsize = 11,\n                align = (:center, :center),\n            )\n        end\n\n        y_start += height + gap_y\n    end\nend\n\nax.xticks = (x_starts .+ column_widths ./ 2, departments)\nax.yticks = (0:0.25:1, [\"0%\", \"25%\", \"50%\", \"75%\", \"100%\"])\nxlims!(ax, -0.02, 1.02)\nylims!(ax, -0.02, 1.02)\n\nlegend_elements = [PolyElement(color = IMPRINT_PALETTE[j]) for j in 1:n_cols]\nLegend(\n    fig[1, 2], legend_elements, satisfaction_levels, \"Satisfaction level\";\n    framevisible = false, labelcolor = INK, titlecolor = INK, tellheight = false,\n)\ncolsize!(fig.layout, 1, Relative(0.82))\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}