{"spec_id":"histogram-2d","library":"makie","language":"julia","code":"# anyplot.ai\n# histogram-2d: 2D Histogram Heatmap\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing ColorSchemes\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]\nconst IMPRINT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\n\n# Data — correlated daily returns for two asset classes (%)\nn_points = 20_000\ncorrelation = 0.65\nz1 = randn(n_points)\nz2 = randn(n_points)\nequity_returns = z1 .* 1.4\nbond_returns = (correlation .* z1 .+ sqrt(1 - correlation^2) .* z2) .* 0.6 .+ 0.05\n\n# 2D histogram binning (rectangular bins)\nn_bins = 32\nx_edges = range(minimum(equity_returns), maximum(equity_returns), length = n_bins + 1)\ny_edges = range(minimum(bond_returns), maximum(bond_returns), length = n_bins + 1)\ncounts = zeros(Int, n_bins, n_bins)\nfor i in eachindex(equity_returns)\n    xi = clamp(searchsortedlast(x_edges, equity_returns[i]), 1, n_bins)\n    yi = clamp(searchsortedlast(y_edges, bond_returns[i]), 1, n_bins)\n    counts[xi, yi] += 1\nend\nx_centers = (x_edges[1:end-1] .+ x_edges[2:end]) ./ 2\ny_centers = (y_edges[1:end-1] .+ y_edges[2:end]) ./ 2\n\n# Empty bins render as page background instead of the colormap's low end,\n# so the density shape stands out instead of a solid-green rectangle\ncounts_display = Float64.(counts)\ncounts_display[counts_display .== 0] .= NaN\n\n# Plot — joint density heatmap with marginal histograms for univariate context\ntitle_str = \"histogram-2d · julia · makie · anyplot.ai\"\n\nfig = Figure(resolution = (1200, 1200), fontsize = 14, backgroundcolor = PAGE_BG)\n\nLabel(fig[1, 1:3], title_str; fontsize = 20, color = INK, font = :bold)\n\nax_top = Axis(fig[2, 1]; backgroundcolor = PAGE_BG)\n\nax_main = Axis(\n    fig[3, 1];\n    xlabel = \"Equity Daily Return (%)\",\n    ylabel = \"Bond Daily Return (%)\",\n    xlabelcolor = INK,\n    ylabelcolor = INK,\n    xticklabelcolor = INK_SOFT,\n    yticklabelcolor = INK_SOFT,\n    xtickcolor = 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    ygridvisible = false,\n    xlabelsize = 14,\n    ylabelsize = 14,\n    xticklabelsize = 14,\n    yticklabelsize = 14,\n)\n\nax_right = Axis(fig[3, 2]; backgroundcolor = PAGE_BG)\n\nlinkxaxes!(ax_top, ax_main)\nlinkyaxes!(ax_right, ax_main)\n\nhist!(ax_top, equity_returns; bins = x_edges, color = IMPRINT_PALETTE[1])\n# Log color scale reveals structure in the sparse outer lobes (counts span ~1 to 170+)\nhm = heatmap!(\n    ax_main,\n    x_centers,\n    y_centers,\n    counts_display;\n    colormap = IMPRINT_SEQ,\n    nan_color = PAGE_BG,\n    colorscale = log10,\n)\nhist!(ax_right, bond_returns; bins = y_edges, direction = :x, color = IMPRINT_PALETTE[1])\n\nhidedecorations!(ax_top)\nhidespines!(ax_top)\nhidedecorations!(ax_right)\nhidespines!(ax_right)\n\nColorbar(\n    fig[3, 3],\n    hm;\n    label = \"Count\",\n    labelcolor = INK,\n    ticklabelcolor = INK_SOFT,\n    tickcolor = INK_SOFT,\n    labelsize = 14,\n    ticklabelsize = 14,\n)\n\nrowsize!(fig.layout, 2, Relative(0.16))\ncolsize!(fig.layout, 2, Relative(0.16))\ncolsize!(fig.layout, 3, Relative(0.05))\nrowgap!(fig.layout, 8)\ncolgap!(fig.layout, 8)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}