|
| 1 | +# anyplot.ai |
| 2 | +# scatter-categorical: Categorical Scatter Plot |
| 3 | +# Library: Makie.jl 0.21 | Julia 1.11 |
| 4 | +# Quality: pending | Created: 2026-09-05 |
| 5 | + |
| 6 | +using CairoMakie |
| 7 | +using Colors |
| 8 | +using Random |
| 9 | + |
| 10 | +Random.seed!(42) |
| 11 | + |
| 12 | +# --- Theme tokens ------------------------------------------------------------ |
| 13 | +const THEME = get(ENV, "ANYPLOT_THEME", "light") |
| 14 | +const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" |
| 15 | +const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" |
| 16 | +const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" |
| 17 | +const IMPRINT_PALETTE = [ |
| 18 | + colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233", |
| 19 | + colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314", |
| 20 | +] |
| 21 | + |
| 22 | +# --- Data --------------------------------------------------------------------- |
| 23 | +# Soil nutrient survey across three land-use types: fertilized cropland runs |
| 24 | +# nitrogen-rich, grassland sits in between, and undisturbed forest soil is |
| 25 | +# nutrient-poor but more variable plot to plot. |
| 26 | +land_uses = ["Cropland", "Grassland", "Forest"] |
| 27 | +n_means = [45.0, 26.0, 12.0] |
| 28 | +n_sds = [6.0, 5.0, 4.5] |
| 29 | +p_means = [31.0, 19.0, 8.0] |
| 30 | +p_sds = [4.5, 4.0, 3.5] |
| 31 | +n_samples = 60 |
| 32 | +markers = [:circle, :utriangle, :diamond] |
| 33 | + |
| 34 | +nitrogen = Float64[] |
| 35 | +phosphorus = Float64[] |
| 36 | +group_idx = Int[] |
| 37 | + |
| 38 | +for i in eachindex(land_uses) |
| 39 | + append!(nitrogen, n_means[i] .+ n_sds[i] .* randn(n_samples)) |
| 40 | + append!(phosphorus, p_means[i] .+ p_sds[i] .* randn(n_samples) .+ 0.3 .* (nitrogen[end-n_samples+1:end] .- n_means[i])) |
| 41 | + append!(group_idx, fill(i, n_samples)) |
| 42 | +end |
| 43 | + |
| 44 | +title_str = "scatter-categorical · julia · makie · anyplot.ai" |
| 45 | + |
| 46 | +# --- Plot ---------------------------------------------------------------------- |
| 47 | +fig = Figure( |
| 48 | + resolution = (1600, 900), |
| 49 | + fontsize = 14, |
| 50 | + backgroundcolor = PAGE_BG, |
| 51 | +) |
| 52 | + |
| 53 | +ax = Axis( |
| 54 | + fig[1, 1]; |
| 55 | + title = title_str, |
| 56 | + titlesize = 20, |
| 57 | + titlecolor = INK, |
| 58 | + xlabel = "Soil Nitrogen (mg/kg)", |
| 59 | + ylabel = "Soil Phosphorus (mg/kg)", |
| 60 | + xlabelsize = 14, |
| 61 | + ylabelsize = 14, |
| 62 | + xlabelcolor = INK, |
| 63 | + ylabelcolor = INK, |
| 64 | + xticklabelsize = 12, |
| 65 | + yticklabelsize = 12, |
| 66 | + xticklabelcolor = INK_SOFT, |
| 67 | + yticklabelcolor = INK_SOFT, |
| 68 | + xtickcolor = INK_SOFT, |
| 69 | + ytickcolor = INK_SOFT, |
| 70 | + backgroundcolor = PAGE_BG, |
| 71 | + topspinevisible = false, |
| 72 | + rightspinevisible = false, |
| 73 | + leftspinecolor = INK_SOFT, |
| 74 | + bottomspinecolor = INK_SOFT, |
| 75 | + xgridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15), |
| 76 | + ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15), |
| 77 | + xminorgridvisible = false, |
| 78 | + yminorgridvisible = false, |
| 79 | +) |
| 80 | + |
| 81 | +for i in eachindex(land_uses) |
| 82 | + mask = group_idx .== i |
| 83 | + scatter!(ax, nitrogen[mask], phosphorus[mask]; |
| 84 | + color = IMPRINT_PALETTE[i], marker = markers[i], |
| 85 | + markersize = 16, strokewidth = 1, strokecolor = PAGE_BG, |
| 86 | + label = land_uses[i]) |
| 87 | +end |
| 88 | + |
| 89 | +axislegend(ax, position = :rb, labelcolor = INK, framevisible = false, labelsize = 13) |
| 90 | + |
| 91 | +# --- Save ----------------------------------------------------------------------- |
| 92 | +save("plot-$(THEME).png", fig; px_per_unit = 2) |
0 commit comments