{"spec_id":"line-pca-variance-cumulative","library":"makie","language":"julia","code":"# anyplot.ai\n# line-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-05-29\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\nusing LinearAlgebra\n\nRandom.seed!(42)\n\n# Theme tokens — Imprint palette data colors are theme-independent; chrome flips\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\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\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green (always first series)\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#AE3030\",  # 5 — matte red\n    colorant\"#2ABCCD\",  # 6 — cyan\n    colorant\"#954477\",  # 7 — rose\n    colorant\"#99B314\",  # 8 — lime\n]\nconst ANYPLOT_AMBER = colorant\"#DDCC77\"\n\n# Data — synthetic metabolomics dataset: 18 spectral features, 5 true latent factors\nn_samples  = 200\nn_features = 18\n\nlatent   = randn(n_samples, 5)\nloadings = randn(5, n_features)\nX = latent * loadings .+ 0.4 .* randn(n_samples, n_features)\n\n# PCA via eigendecomposition of the sample covariance matrix\nX_c = X .- mean(X, dims=1)\nC   = (X_c' * X_c) ./ (n_samples - 1)\neig_result = eigen(Symmetric(C))\neigenvalues = reverse(max.(eig_result.values, 0.0))\n\nevr            = eigenvalues ./ sum(eigenvalues)\ncumulative_pct = cumsum(evr) .* 100\nindividual_pct = evr .* 100\nn_comp         = length(eigenvalues)\ncomponents     = collect(1:n_comp)\n\n# Elbow: component furthest from the diagonal connecting first and last point\nx_n = (components .- 1.0) ./ (n_comp - 1.0)\ny_n = (cumulative_pct .- cumulative_pct[1]) ./ (cumulative_pct[end] - cumulative_pct[1])\nelbow_idx = argmax(abs.(y_n .- x_n))\n\nidx_90 = something(findfirst(>=(90.0), cumulative_pct), n_comp)\nidx_95 = something(findfirst(>=(95.0), cumulative_pct), n_comp)\n\ntitle_str = \"line-pca-variance-cumulative · julia · makie · anyplot.ai\"\n\n# Figure — landscape 3200×1800 (resolution=(1600,900) × px_per_unit=2)\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 13,\n    backgroundcolor = PAGE_BG,\n)\n\nink_grid = RGBAf(INK.r, INK.g, INK.b, 0.12)\n\n# Top panel — cumulative explained variance\nax1 = Axis(\n    fig[1, 1];\n    title              = title_str,\n    titlesize          = 20,\n    titlecolor         = INK,\n    ylabel             = \"Cumulative Variance (%)\",\n    ylabelsize         = 13,\n    ylabelcolor        = INK,\n    xticklabelsvisible = false,\n    xticksvisible      = false,\n    yticklabelsize     = 11,\n    yticklabelcolor    = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = ink_grid,\n    yminorgridvisible  = false,\n    xminorgridvisible  = false,\n    limits             = (0.5, n_comp + 0.5, 0.0, 108.0),\n)\n\nlines!(ax1, components, cumulative_pct;\n    color = IMPRINT_PALETTE[1], linewidth = 2.5)\nscatter!(ax1, components, cumulative_pct;\n    color = IMPRINT_PALETTE[1], markersize = 9, strokewidth = 0)\n\nhlines!(ax1, [90.0, 95.0];\n    color = ANYPLOT_AMBER, linewidth = 1.5, linestyle = :dash)\nvlines!(ax1, Float64.([idx_90, idx_95]);\n    color = ANYPLOT_AMBER, linewidth = 1.0, linestyle = :dot)\n\nhspan!(ax1, 90.0, 95.0; color = RGBAf(ANYPLOT_AMBER.r, ANYPLOT_AMBER.g, ANYPLOT_AMBER.b, 0.10))\n\ntext!(ax1, Float64(n_comp) - 0.3, 91.5;\n    text = \"90%\", color = INK_SOFT, fontsize = 13, align = (:right, :bottom))\ntext!(ax1, Float64(n_comp) - 0.3, 96.5;\n    text = \"95%\", color = INK_SOFT, fontsize = 13, align = (:right, :bottom))\n\nscatter!(ax1, [Float64(elbow_idx)], [cumulative_pct[elbow_idx]];\n    color = IMPRINT_PALETTE[3], markersize = 14, strokewidth = 0)\ntext!(ax1, Float64(elbow_idx) + 0.4, cumulative_pct[elbow_idx];\n    text = \"PC$(elbow_idx) (elbow)\", color = INK, fontsize = 13,\n    align = (:left, :center))\n\n# Bottom panel — individual explained variance bars\nax2 = Axis(\n    fig[2, 1];\n    xlabel             = \"Number of Components\",\n    ylabel             = \"Individual (%)\",\n    xlabelsize         = 13,\n    ylabelsize         = 13,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    xticklabelsize     = 11,\n    yticklabelsize     = 10,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = ink_grid,\n    yminorgridvisible  = false,\n    xminorgridvisible  = false,\n    xticks             = 1:3:n_comp,\n    limits             = (0.5, n_comp + 0.5, 0.0, nothing),\n)\n\nbarplot!(ax2, components, individual_pct;\n    color = IMPRINT_PALETTE[1], strokewidth = 0)\n\nvlines!(ax2, [Float64(elbow_idx)];\n    color = IMPRINT_PALETTE[3], linewidth = 1.5, linestyle = :dash)\n\nlinkxaxes!(ax1, ax2)\nrowsize!(fig.layout, 1, Relative(0.70))\nrowsize!(fig.layout, 2, Relative(0.30))\nrowgap!(fig.layout, 1, 6)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}