{"spec_id":"star-chart-constellation","library":"makie","language":"julia","code":"# anyplot.ai\n# star-chart-constellation: Star Chart with Constellations\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-06-17\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 — constellation accent\n\n# --- Sky window: a stereographic projection centred on the winter sky -------\nconst RA0  = 6.0     # central Right Ascension (hours)\nconst DEC0 = 8.0     # central Declination (degrees)\nconst CMAX = 55.0    # angular radius of the circular sky window (degrees)\nconst RBND = 2 * tand(CMAX / 2)   # projected radius of the sky boundary circle\n\n# Stereographic projection: (RA hours, Dec degrees) -> planar (x, y).\n# x is negated so the chart reads like a view of the sky (RA increases left).\nproject(ra_h, dec_d) = begin\n    ra  = deg2rad(ra_h * 15);  dec  = deg2rad(dec_d)\n    ra0 = deg2rad(RA0 * 15);   dec0 = deg2rad(DEC0)\n    k = 2 / (1 + sin(dec0) * sin(dec) + cos(dec0) * cos(dec) * cos(ra - ra0))\n    (-k * cos(dec) * sin(ra - ra0),\n      k * (cos(dec0) * sin(dec) - sin(dec0) * cos(dec) * cos(ra - ra0)))\nend\n\n# Angular distance (degrees) of a sky point from the projection centre.\nangdist(ra_h, dec_d) = acosd(clamp(\n    sind(DEC0) * sind(dec_d) + cosd(DEC0) * cosd(dec_d) * cosd(15 * (ra_h - RA0)),\n    -1, 1))\n\n# Apparent magnitude -> marker size: brighter stars (lower mag) render larger.\nmagsize(m) = clamp((6.4 - m) * 3.0, 2.5, 34.0)\n\n# --- Constellation catalog (bright stars: RA hours, Dec deg, magnitude) -----\nconst CONSTELLATIONS = [\n    (abbr = \"Ori\",\n     stars = [(5.919, 7.407, 0.42), (5.242, -8.202, 0.18), (5.418, 6.350, 1.64),\n              (5.796, -9.670, 2.07), (5.679, -1.943, 1.74), (5.604, -1.202, 1.69),\n              (5.533, -0.299, 2.23), (5.585, 9.934, 3.39)],\n     edges = [(1, 5), (3, 7), (5, 6), (6, 7), (5, 4), (7, 2), (1, 8), (3, 8)]),\n    (abbr = \"Tau\",\n     stars = [(4.599, 16.509, 0.85), (5.438, 28.608, 1.65), (5.627, 21.142, 3.00),\n              (4.330, 15.628, 3.65), (4.011, 12.490, 3.40)],\n     edges = [(5, 4), (4, 1), (1, 2), (1, 3)]),\n    (abbr = \"Gem\",\n     stars = [(7.755, 28.026, 1.14), (7.577, 31.888, 1.58), (6.629, 16.399, 1.93),\n              (7.335, 21.982, 3.53), (6.732, 25.131, 3.06), (6.383, 22.514, 2.87)],\n     edges = [(2, 1), (1, 4), (4, 3), (2, 5), (5, 6)]),\n    (abbr = \"CMa\",\n     stars = [(6.752, -16.716, -1.46), (6.378, -17.956, 1.98), (7.140, -26.393, 1.83),\n              (6.977, -28.972, 1.50), (7.402, -29.303, 2.45)],\n     edges = [(1, 2), (1, 3), (3, 4), (4, 5), (3, 5)]),\n    (abbr = \"CMi\",\n     stars = [(7.655, 5.225, 0.34), (7.452, 8.289, 2.89)],\n     edges = [(1, 2)]),\n    (abbr = \"Aur\",\n     stars = [(5.278, 45.998, 0.08), (5.992, 44.947, 1.90),\n              (5.995, 37.213, 2.62), (4.950, 33.166, 2.69)],\n     edges = [(1, 2), (2, 3), (3, 4), (4, 1)]),\n    (abbr = \"Per\",\n     stars = [(3.405, 49.861, 1.79), (3.136, 40.956, 2.12), (3.902, 31.884, 2.85),\n              (3.964, 40.010, 2.89), (3.715, 47.788, 3.01)],\n     edges = [(5, 1), (1, 4), (4, 3), (1, 2)]),\n    (abbr = \"Eri\",\n     stars = [(2.939, -8.898, 3.89), (3.549, -9.458, 3.73),\n              (3.721, -9.764, 3.54), (3.967, -13.509, 2.95)],\n     edges = [(1, 2), (2, 3), (3, 4)]),\n    (abbr = \"Lep\",\n     stars = [(5.545, -17.822, 2.58), (5.470, -20.759, 2.81), (5.091, -22.371, 3.19),\n              (5.220, -16.205, 3.31), (5.744, -22.448, 3.59), (5.855, -20.879, 3.81)],\n     edges = [(3, 2), (2, 1), (1, 4), (2, 5), (5, 6)]),\n    (abbr = \"Col\",\n     stars = [(5.660, -34.074, 2.65), (5.849, -35.768, 3.12), (5.958, -35.283, 4.36),\n              (5.521, -35.470, 3.87), (6.357, -33.437, 3.85)],\n     edges = [(4, 1), (1, 2), (2, 5), (2, 3)]),\n    (abbr = \"Mon\",\n     stars = [(6.480, -7.033, 3.74), (7.685, -9.551, 3.93), (6.247, -6.275, 3.98),\n              (7.198, -0.493, 4.15), (8.143, -2.983, 4.34)],\n     edges = [(3, 1), (1, 4), (4, 2), (2, 5)]),\n    (abbr = \"Cnc\",\n     stars = [(8.275, 9.186, 3.53), (8.745, 18.154, 3.94), (8.778, 28.760, 4.02),\n              (8.975, 11.858, 4.26), (8.722, 21.469, 4.66)],\n     edges = [(3, 5), (5, 2), (2, 4), (2, 1)]),\n    (abbr = \"Lyn\",\n     stars = [(9.351, 34.392, 3.14), (9.318, 36.803, 3.82), (8.382, 43.188, 4.25),\n              (6.908, 58.421, 4.35), (6.323, 59.011, 4.44)],\n     edges = [(1, 2), (2, 3), (3, 4), (4, 5)]),\n    (abbr = \"Hya\",\n     stars = [(8.925, 5.946, 3.11), (8.779, 6.419, 3.38), (8.622, 5.704, 4.14),\n              (8.644, 3.341, 4.45), (8.720, 3.399, 4.30), (8.795, 5.835, 4.35)],\n     edges = [(1, 6), (6, 2), (2, 3), (3, 4), (4, 5), (5, 1)]),\n]\n\n# --- Build plotting arrays from the catalog ---------------------------------\nstar_x = Float64[]; star_y = Float64[]; star_ms = Float64[]\nedge_x = Float64[]; edge_y = Float64[]\nlabel_x = Float64[]; label_y = Float64[]; label_t = String[]\nfor c in CONSTELLATIONS\n    for s in c.stars\n        x, y = project(s[1], s[2])\n        push!(star_x, x); push!(star_y, y); push!(star_ms, magsize(s[3]))\n    end\n    for (i, j) in c.edges\n        x1, y1 = project(c.stars[i][1], c.stars[i][2])\n        x2, y2 = project(c.stars[j][1], c.stars[j][2])\n        append!(edge_x, (x1, x2, NaN)); append!(edge_y, (y1, y2, NaN))\n    end\n    cra = mean(s[1] for s in c.stars); cdec = mean(s[2] for s in c.stars)\n    lx, ly = project(cra, cdec)\n    # Offset the label away from the brightest (lowest-magnitude) star so it\n    # never crowds the most prominent marker of the group.\n    bs = c.stars[argmin(s[3] for s in c.stars)]\n    bx, by = project(bs[1], bs[2])\n    dx = lx - bx; dy = ly - by; nrm = hypot(dx, dy)\n    off = 0.11 * RBND\n    if nrm < 1e-6\n        push!(label_x, lx); push!(label_y, ly + off)\n    else\n        push!(label_x, lx + off * dx / nrm); push!(label_y, ly + off * dy / nrm)\n    end\n    push!(label_t, c.abbr)\nend\n\n# --- Background star field (faint stars filling the sky window) --------------\nn_cand = 1100\ncand_ra  = 2.0 .+ 8.0 .* rand(n_cand)\ncand_dec = -42.0 .+ 100.0 .* rand(n_cand)\ncand_mag = 3.3 .+ 3.2 .* rand(n_cand)\nkeep = [angdist(cand_ra[i], cand_dec[i]) <= CMAX * 0.992 for i in 1:n_cand]\nfield_ra = cand_ra[keep]; field_dec = cand_dec[keep]; field_mag = cand_mag[keep]\nn_field = min(250, length(field_ra))\nfield_x = Float64[]; field_y = Float64[]\nfor i in 1:n_field\n    x, y = project(field_ra[i], field_dec[i])\n    push!(field_x, x); push!(field_y, y)\nend\nfield_ms = magsize.(field_mag[1:n_field])\nfield_color = [RGBAf(INK.r, INK.g, INK.b, clamp(0.22 + (6.4 - m) * 0.12, 0.2, 0.85))\n               for m in field_mag[1:n_field]]\n\n# --- Coordinate grid + circular sky boundary --------------------------------\nθ = range(0, 2π, length = 400)\nboundary_x = RBND .* cos.(θ); boundary_y = RBND .* sin.(θ)\n\ngrid_x = Float64[]; grid_y = Float64[]\nfor d in (-30, -15, 0, 15, 30, 45)                     # parallels (constant Dec)\n    for ra in range(2.0, 10.0, length = 260)\n        if angdist(ra, d) <= CMAX\n            x, y = project(ra, d); push!(grid_x, x); push!(grid_y, y)\n        else\n            push!(grid_x, NaN); push!(grid_y, NaN)\n        end\n    end\n    push!(grid_x, NaN); push!(grid_y, NaN)\nend\nfor r in (3, 4, 5, 6, 7, 8, 9)                          # meridians (constant RA)\n    for dec in range(-42, 58, length = 260)\n        if angdist(r, dec) <= CMAX\n            x, y = project(r, dec); push!(grid_x, x); push!(grid_y, y)\n        else\n            push!(grid_x, NaN); push!(grid_y, NaN)\n        end\n    end\n    push!(grid_x, NaN); push!(grid_y, NaN)\nend\n\n# Grid tick labels: Dec along the central meridian, RA along a lower parallel.\ndec_tx = Float64[]; dec_ty = Float64[]; dec_tt = String[]\nfor d in (-30, 0, 30)\n    x, y = project(RA0, d)\n    push!(dec_tx, x - 0.035 * RBND); push!(dec_ty, y)\n    push!(dec_tt, (d > 0 ? \"+\" : \"\") * string(d) * \"°\")\nend\nra_tx = Float64[]; ra_ty = Float64[]; ra_tt = String[]\nfor r in (4, 5, 6, 7, 8)\n    x, y = project(r, -28)\n    push!(ra_tx, x); push!(ra_ty, y); push!(ra_tt, string(r) * \"h\")\nend\n\n# Magnitude legend, parked in the empty corner outside the circular window.\nleg_x    = -1.02 * RBND\nleg_ys   = collect((1.00, 0.86, 0.72) .* RBND)\nleg_mags = (0.0, 2.0, 4.0)\n\n# --- Plot -------------------------------------------------------------------\nfig = Figure(size = (1200, 1200), fontsize = 16, backgroundcolor = PAGE_BG)\nax = Axis(fig[1, 1];\n    title = \"star-chart-constellation · julia · makie · anyplot.ai\",\n    titlesize = 25, titlecolor = INK, titlegap = 16,\n    backgroundcolor = PAGE_BG, aspect = DataAspect())\nhidedecorations!(ax)\nhidespines!(ax)\nlimits!(ax, -1.18 * RBND, 1.18 * RBND, -1.18 * RBND, 1.18 * RBND)\n\n# coordinate grid + sky boundary\nlines!(ax, grid_x, grid_y; color = RGBAf(INK.r, INK.g, INK.b, 0.13), linewidth = 1.0)\nlines!(ax, boundary_x, boundary_y; color = RGBAf(INK.r, INK.g, INK.b, 0.45), linewidth = 2.5)\n\n# background star field\nscatter!(ax, field_x, field_y; markersize = field_ms, color = field_color, strokewidth = 0)\n\n# constellation stick figures\nlines!(ax, edge_x, edge_y; color = RGBAf(BRAND.r, BRAND.g, BRAND.b, 0.55), linewidth = 2.0)\n\n# constellation bright stars\nscatter!(ax, star_x, star_y; markersize = star_ms, color = BRAND,\n    strokecolor = PAGE_BG, strokewidth = 1.0)\n\n# constellation name labels\ntext!(ax, label_x, label_y; text = label_t, align = (:center, :bottom),\n    color = INK, fontsize = 22, font = :bold)\n\n# coordinate tick labels\ntext!(ax, dec_tx, dec_ty; text = dec_tt, align = (:right, :center),\n    color = INK_SOFT, fontsize = 15)\ntext!(ax, ra_tx, ra_ty; text = ra_tt, align = (:center, :center),\n    color = INK_SOFT, fontsize = 15)\n\n# magnitude legend\nscatter!(ax, fill(leg_x, 3), leg_ys; markersize = collect(magsize.(leg_mags)),\n    color = RGBAf(INK.r, INK.g, INK.b, 0.75), strokewidth = 0)\ntext!(ax, fill(leg_x + 0.07 * RBND, 3), leg_ys;\n    text = [\"mag $(Int(m))\" for m in leg_mags], align = (:left, :center),\n    color = INK_SOFT, fontsize = 15)\ntext!(ax, leg_x, 1.12 * RBND; text = \"Star magnitude\", align = (:left, :center),\n    color = INK, fontsize = 16)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}