{"spec_id":"scatter-annotated","library":"makie","language":"julia","code":"# anyplot.ai\n# scatter-annotated: Annotated Scatter Plot with Text Labels\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 88/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\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 BRAND    = colorant\"#009E73\"  # Imprint palette position 1 — always first series\n\n# --- Data -----------------------------------------------------------------\ncompany_names = [\n    \"NovaSys\", \"ByteForge\", \"QuantumLeap\", \"DataWeave\", \"CloudPeak\", \"SwiftAI\",\n    \"NeuralArc\", \"EdgeStack\", \"VectorFlow\", \"PixelCraft\", \"StreamLine\", \"CoreLogic\",\n    \"BrightPath\", \"ZenithTech\", \"ApexData\", \"TrueNorth\", \"SilverBit\", \"GreenSpark\",\n    \"BluePeak\", \"RedShift\", \"OrbitLabs\", \"FusionWorks\", \"PrimeCode\", \"NextWave\",\n]\nn = length(company_names)\n\nrd_spend = round.(exp.(randn(n) .* 0.4 .+ 2.5); digits=1)                 # R&D spend ($M)\n\n# Log-normal spend naturally throws a far-right tail; cap the single biggest\n# spender's gap above the rest to 20% of the remaining cluster's range so the\n# outlier still reads as notable without stranding the right side of the canvas.\nsorted_spend = sort(rd_spend)\ngap_cap = 0.2 * (sorted_spend[end-1] - sorted_spend[1])\nif maximum(rd_spend) - sorted_spend[end-1] > gap_cap\n    rd_spend[argmax(rd_spend)] = round(sorted_spend[end-1] + gap_cap; digits=1)\nend\n\nrevenue_growth = round.(0.9 .* rd_spend .+ randn(n) .* 6 .+ 5; digits=1)  # Revenue growth (%)\n\n# Highlight a handful of notable points instead of labeling all 24: the\n# biggest / leanest spenders, the top / bottom growers, and the two\n# companies whose growth deviates most from the spend-growth trend. Some\n# extremes coincide (e.g. the same company is both min-growth and\n# min-spend), so backfill with the next-most-extreme distinct index until\n# six distinct companies are highlighted.\ntrend_residual = revenue_growth .- 0.9 .* rd_spend\nslot_rankings = [\n    sortperm(revenue_growth; rev=true), sortperm(revenue_growth),\n    sortperm(rd_spend; rev=true), sortperm(rd_spend),\n    sortperm(trend_residual; rev=true), sortperm(trend_residual),\n]\nlabeled_idx = Int[]\nfor ranking in slot_rankings\n    for idx in ranking\n        if idx ∉ labeled_idx\n            push!(labeled_idx, idx)\n            break\n        end\n    end\nend\n\ncx, cy = mean(rd_spend), mean(revenue_growth)\nx_range = maximum(rd_spend) - minimum(rd_spend)\ny_range = maximum(revenue_growth) - minimum(revenue_growth)\n\n# Precompute each label's offset position (and alignment) once so the same\n# values drive both the axis-limit padding below and the draw loop later —\n# a label landing outside the axis's auto-computed data range would\n# otherwise get silently clipped by the axis viewport.\nlabel_dx    = [(rd_spend[i] >= cx ? 1 : -1) * 0.08 * x_range for i in labeled_idx]\nlabel_dy    = [(revenue_growth[i] >= cy ? 1 : -1) * 0.11 * y_range for i in labeled_idx]\n\n# Two highlighted points can sit close together in data space and fall on\n# the same side of the centroid on both axes, sending their label anchors\n# in the same direction by a near-identical amount -- close enough for one\n# anchor to land on top of the other (or on top of the marker cluster\n# itself) and read as a missing label. Detect any such close, same-\n# quadrant pair and push the later point's offset out farther so its\n# anchor clears the first instead of coinciding with it.\nfor k in eachindex(labeled_idx), j in 1:(k - 1)\n    same_quadrant = sign(label_dx[j]) == sign(label_dx[k]) && sign(label_dy[j]) == sign(label_dy[k])\n    i_j, i_k = labeled_idx[j], labeled_idx[k]\n    nearby = hypot((rd_spend[i_j] - rd_spend[i_k]) / x_range, (revenue_growth[i_j] - revenue_growth[i_k]) / y_range) < 0.15\n    if same_quadrant && nearby\n        label_dx[k] *= 1.8\n        label_dy[k] *= 1.8\n    end\nend\n\nlabel_x     = [rd_spend[labeled_idx[k]] + label_dx[k] for k in eachindex(labeled_idx)]\nlabel_y     = [revenue_growth[labeled_idx[k]] + label_dy[k] for k in eachindex(labeled_idx)]\n\n# Pad well beyond the label anchors themselves (not just the data range) to\n# leave room for the rendered text glyphs and the connector lines.\nx_pad = 0.12 * x_range\ny_pad = 0.12 * y_range\nx_lo, x_hi = min(minimum(rd_spend), minimum(label_x)) - x_pad, max(maximum(rd_spend), maximum(label_x)) + x_pad\ny_lo, y_hi = min(minimum(revenue_growth), minimum(label_y)) - y_pad, max(maximum(revenue_growth), maximum(label_y)) + y_pad\n\n# --- Plot -------------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"scatter-annotated · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"R&D Spend (\\$M)\",\n    ylabel            = \"Revenue Growth (%)\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\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    xgridcolor        = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    ygridcolor        = RGBAf(INK.r, INK.g, INK.b, 0.15),\n)\n\nunlabeled_idx = setdiff(1:n, labeled_idx)\nscatter!(ax, rd_spend[unlabeled_idx], revenue_growth[unlabeled_idx];\n    color = BRAND, alpha = 0.7, markersize = 15, strokewidth = 1, strokecolor = PAGE_BG)\n\n# Highlighted points get a larger, fully-opaque marker with a dark ring so\n# the labeled subset reads as an unambiguous focal point — the ring makes\n# the distinction independent of alpha-blending, which is easy to lose in\n# a saved raster next to the merely-translucent unlabeled markers.\nscatter!(ax, rd_spend[labeled_idx], revenue_growth[labeled_idx];\n    color = BRAND, alpha = 1.0, markersize = 24, strokewidth = 2, strokecolor = INK)\n\n# Explicit limits, padded to fit every label anchor computed above, so an\n# offset label can never fall outside the axis viewport and get clipped.\nxlims!(ax, x_lo, x_hi)\nylims!(ax, y_lo, y_hi)\n\n# Push each label away from the data centroid so it lands in open space,\n# with a thin connector line back to its point.\nfor k in eachindex(labeled_idx)\n    i = labeled_idx[k]\n    lx, ly = label_x[k], label_y[k]\n    halign = label_dx[k] >= 0 ? :left : :right\n    valign = label_dy[k] >= 0 ? :bottom : :top\n\n    lines!(ax, [rd_spend[i], lx], [revenue_growth[i], ly];\n        color = INK_SOFT, linewidth = 1, alpha = 0.6)\n    text!(ax, lx, ly; text = company_names[i], align = (halign, valign),\n        fontsize = 13, color = INK_SOFT)\nend\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}