{"spec_id":"star-chart-constellation","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nstar-chart-constellation: Star Chart with Constellations\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-17\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.cm import ScalarMappable\nfrom matplotlib.colors import LinearSegmentedColormap, Normalize\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nANYPLOT_AMBER = \"#DDCC77\"  # warning/caution anchor — reused here for the Sun's ecliptic path\n\n# Imprint sequential cmap (single-polarity continuous: brand green -> blue) for magnitude\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Data - Major stars with RA (hours), Dec (degrees), apparent magnitude, IAU constellation\nnp.random.seed(42)\n\nstars = {\n    # Orion\n    \"Betelgeuse\": (5.92, 7.41, 0.42, \"Ori\"),\n    \"Rigel\": (5.24, -8.20, 0.13, \"Ori\"),\n    \"Bellatrix\": (5.42, 6.35, 1.64, \"Ori\"),\n    \"Mintaka\": (5.53, -0.30, 2.23, \"Ori\"),\n    \"Alnilam\": (5.60, -1.20, 1.69, \"Ori\"),\n    \"Alnitak\": (5.68, -1.94, 1.77, \"Ori\"),\n    \"Saiph\": (5.80, -9.67, 2.09, \"Ori\"),\n    # Ursa Major (Big Dipper)\n    \"Dubhe\": (11.06, 61.75, 1.79, \"UMa\"),\n    \"Merak\": (11.03, 56.38, 2.37, \"UMa\"),\n    \"Phecda\": (11.90, 53.69, 2.44, \"UMa\"),\n    \"Megrez\": (12.26, 57.03, 3.31, \"UMa\"),\n    \"Alioth\": (12.90, 55.96, 1.77, \"UMa\"),\n    \"Mizar\": (13.40, 54.93, 2.27, \"UMa\"),\n    \"Alkaid\": (13.79, 49.31, 1.86, \"UMa\"),\n    # Cassiopeia\n    \"Schedar\": (0.68, 56.54, 2.23, \"Cas\"),\n    \"Caph\": (0.15, 59.15, 2.27, \"Cas\"),\n    \"Gamma Cas\": (0.95, 60.72, 2.47, \"Cas\"),\n    \"Ruchbah\": (1.36, 60.24, 2.68, \"Cas\"),\n    \"Segin\": (1.91, 63.67, 3.37, \"Cas\"),\n    # Leo\n    \"Regulus\": (10.14, 11.97, 1.35, \"Leo\"),\n    \"Denebola\": (11.82, 14.57, 2.14, \"Leo\"),\n    \"Algieba\": (10.33, 19.84, 2.28, \"Leo\"),\n    \"Zosma\": (11.24, 20.52, 2.56, \"Leo\"),\n    \"Chertan\": (11.24, 15.43, 3.34, \"Leo\"),\n    \"Eta Leo\": (10.12, 16.76, 3.52, \"Leo\"),\n    # Cygnus\n    \"Deneb\": (20.69, 45.28, 1.25, \"Cyg\"),\n    \"Sadr\": (20.37, 40.26, 2.23, \"Cyg\"),\n    \"Gienah Cyg\": (20.77, 33.97, 2.46, \"Cyg\"),\n    \"Delta Cyg\": (19.75, 45.13, 2.87, \"Cyg\"),\n    \"Albireo\": (19.51, 27.96, 3.08, \"Cyg\"),\n    # Gemini\n    \"Pollux\": (7.76, 28.03, 1.14, \"Gem\"),\n    \"Castor\": (7.58, 31.89, 1.58, \"Gem\"),\n    \"Alhena\": (6.63, 16.40, 1.93, \"Gem\"),\n    \"Wasat\": (7.07, 21.98, 3.53, \"Gem\"),\n    \"Mebsuta\": (6.73, 25.13, 2.98, \"Gem\"),\n    \"Tejat\": (6.38, 22.51, 2.88, \"Gem\"),\n    # Taurus\n    \"Aldebaran\": (4.60, 16.51, 0.85, \"Tau\"),\n    \"Elnath\": (5.44, 28.61, 1.65, \"Tau\"),\n    \"Alcyone\": (3.79, 24.11, 2.87, \"Tau\"),\n    \"Tianguan\": (5.63, 21.14, 3.00, \"Tau\"),\n    \"Lambda Tau\": (4.01, 12.49, 3.47, \"Tau\"),\n    # Lyra\n    \"Vega\": (18.62, 38.78, 0.03, \"Lyr\"),\n    \"Sheliak\": (18.83, 33.36, 3.45, \"Lyr\"),\n    \"Sulafat\": (18.98, 32.69, 3.24, \"Lyr\"),\n    \"Delta2 Lyr\": (18.91, 36.90, 4.30, \"Lyr\"),\n    # Aquila\n    \"Altair\": (19.85, 8.87, 0.77, \"Aql\"),\n    \"Tarazed\": (19.77, 10.61, 2.72, \"Aql\"),\n    \"Alshain\": (19.92, 6.41, 3.71, \"Aql\"),\n    # Scorpius\n    \"Antares\": (16.49, -26.43, 0.96, \"Sco\"),\n    \"Shaula\": (17.56, -37.10, 1.63, \"Sco\"),\n    \"Sargas\": (17.62, -43.00, 1.87, \"Sco\"),\n    \"Dschubba\": (16.01, -22.62, 2.32, \"Sco\"),\n    \"Graffias\": (16.09, -19.81, 2.62, \"Sco\"),\n    \"Epsilon Sco\": (16.84, -34.29, 2.29, \"Sco\"),\n    \"Mu1 Sco\": (16.86, -38.05, 3.04, \"Sco\"),\n    # Bootes\n    \"Arcturus\": (14.26, 19.18, -0.05, \"Boo\"),\n    \"Izar\": (14.75, 27.07, 2.37, \"Boo\"),\n    \"Muphrid\": (13.91, 18.40, 2.68, \"Boo\"),\n    \"Nekkar\": (15.03, 40.39, 3.50, \"Boo\"),\n    # Perseus\n    \"Mirfak\": (3.41, 49.86, 1.79, \"Per\"),\n    \"Algol\": (3.14, 40.96, 2.12, \"Per\"),\n    \"Zeta Per\": (3.90, 31.88, 2.85, \"Per\"),\n    \"Epsilon Per\": (3.96, 40.01, 2.89, \"Per\"),\n    \"Delta Per\": (3.72, 47.79, 3.01, \"Per\"),\n    # Auriga\n    \"Capella\": (5.28, 46.00, 0.08, \"Aur\"),\n    \"Menkalinan\": (5.99, 44.95, 1.90, \"Aur\"),\n    \"Theta Aur\": (5.99, 37.21, 2.62, \"Aur\"),\n    \"Iota Aur\": (4.95, 33.17, 2.69, \"Aur\"),\n    # Canis Major\n    \"Sirius\": (6.75, -16.72, -1.46, \"CMa\"),\n    \"Adhara\": (6.98, -28.97, 1.50, \"CMa\"),\n    \"Wezen\": (7.14, -26.39, 1.84, \"CMa\"),\n    \"Mirzam\": (6.38, -17.96, 1.98, \"CMa\"),\n    \"Aludra\": (7.40, -29.30, 2.45, \"CMa\"),\n    # Andromeda\n    \"Alpheratz\": (0.14, 29.09, 2.06, \"And\"),\n    \"Mirach\": (1.16, 35.62, 2.05, \"And\"),\n    \"Almach\": (2.06, 42.33, 2.17, \"And\"),\n}\n\nstar_names = list(stars.keys())\ndf = pd.DataFrame(\n    {\n        \"name\": star_names,\n        \"ra\": [stars[s][0] for s in star_names],\n        \"dec\": [stars[s][1] for s in star_names],\n        \"mag\": [stars[s][2] for s in star_names],\n        \"constellation\": [stars[s][3] for s in star_names],\n    }\n)\n\n# Add ~200 fainter background stars (mag <= 5.5 keeps the chart uncluttered)\nn_bg = 200\nbg_df = pd.DataFrame(\n    {\n        \"name\": [f\"BG{i}\" for i in range(n_bg)],\n        \"ra\": np.random.uniform(0, 24, n_bg),\n        \"dec\": np.random.uniform(-45, 70, n_bg),\n        \"mag\": np.random.uniform(3.5, 5.5, n_bg),\n        \"constellation\": \"---\",\n    }\n)\ndf = pd.concat([df, bg_df], ignore_index=True)\n\n# Constellation stick-figure edges (pairs of star names)\nedges = [\n    (\"Betelgeuse\", \"Bellatrix\"),\n    (\"Bellatrix\", \"Mintaka\"),\n    (\"Mintaka\", \"Alnilam\"),\n    (\"Alnilam\", \"Alnitak\"),\n    (\"Betelgeuse\", \"Alnitak\"),\n    (\"Bellatrix\", \"Rigel\"),\n    (\"Alnitak\", \"Saiph\"),\n    (\"Saiph\", \"Rigel\"),\n    (\"Dubhe\", \"Merak\"),\n    (\"Merak\", \"Phecda\"),\n    (\"Phecda\", \"Megrez\"),\n    (\"Megrez\", \"Alioth\"),\n    (\"Alioth\", \"Mizar\"),\n    (\"Mizar\", \"Alkaid\"),\n    (\"Megrez\", \"Dubhe\"),\n    (\"Caph\", \"Schedar\"),\n    (\"Schedar\", \"Gamma Cas\"),\n    (\"Gamma Cas\", \"Ruchbah\"),\n    (\"Ruchbah\", \"Segin\"),\n    (\"Regulus\", \"Eta Leo\"),\n    (\"Eta Leo\", \"Algieba\"),\n    (\"Algieba\", \"Zosma\"),\n    (\"Zosma\", \"Denebola\"),\n    (\"Regulus\", \"Chertan\"),\n    (\"Chertan\", \"Denebola\"),\n    (\"Deneb\", \"Sadr\"),\n    (\"Sadr\", \"Gienah Cyg\"),\n    (\"Sadr\", \"Delta Cyg\"),\n    (\"Sadr\", \"Albireo\"),\n    (\"Castor\", \"Pollux\"),\n    (\"Castor\", \"Mebsuta\"),\n    (\"Mebsuta\", \"Tejat\"),\n    (\"Pollux\", \"Wasat\"),\n    (\"Wasat\", \"Alhena\"),\n    (\"Tejat\", \"Alhena\"),\n    (\"Aldebaran\", \"Lambda Tau\"),\n    (\"Aldebaran\", \"Elnath\"),\n    (\"Aldebaran\", \"Alcyone\"),\n    (\"Elnath\", \"Tianguan\"),\n    (\"Vega\", \"Sheliak\"),\n    (\"Vega\", \"Sulafat\"),\n    (\"Sheliak\", \"Sulafat\"),\n    (\"Vega\", \"Delta2 Lyr\"),\n    (\"Altair\", \"Tarazed\"),\n    (\"Altair\", \"Alshain\"),\n    (\"Graffias\", \"Dschubba\"),\n    (\"Dschubba\", \"Antares\"),\n    (\"Antares\", \"Epsilon Sco\"),\n    (\"Epsilon Sco\", \"Mu1 Sco\"),\n    (\"Mu1 Sco\", \"Shaula\"),\n    (\"Shaula\", \"Sargas\"),\n    (\"Arcturus\", \"Izar\"),\n    (\"Arcturus\", \"Muphrid\"),\n    (\"Izar\", \"Nekkar\"),\n    (\"Mirfak\", \"Delta Per\"),\n    (\"Mirfak\", \"Epsilon Per\"),\n    (\"Epsilon Per\", \"Zeta Per\"),\n    (\"Mirfak\", \"Algol\"),\n    (\"Capella\", \"Menkalinan\"),\n    (\"Menkalinan\", \"Theta Aur\"),\n    (\"Theta Aur\", \"Iota Aur\"),\n    (\"Iota Aur\", \"Capella\"),\n    (\"Sirius\", \"Mirzam\"),\n    (\"Sirius\", \"Adhara\"),\n    (\"Adhara\", \"Wezen\"),\n    (\"Wezen\", \"Aludra\"),\n    (\"Alpheratz\", \"Mirach\"),\n    (\"Mirach\", \"Almach\"),\n]\n\n# Invert magnitude for sizing: brighter stars (lower mag) -> larger markers.\n# Named stars use the full size range; faint background stars stay small and subtle.\nnamed_mask = df[\"constellation\"] != \"---\"\nmag_min, mag_max = df[\"mag\"].min(), df[\"mag\"].max()\ndf[\"size\"] = np.interp(df[\"mag\"], [mag_min, mag_max], [430, 14])\ndf.loc[~named_mask, \"size\"] = np.interp(df.loc[~named_mask, \"mag\"], [3.5, 5.5], [40, 8])\n\n# Star lookup for edges, in RA-hour / Dec-degree space\nstar_lookup = df[named_mask].set_index(\"name\")[[\"ra\", \"dec\"]].to_dict(\"index\")\n\n# Constellation label positions (centroid of each constellation's stars)\nconstellation_names = {\n    \"Ori\": \"Orion\",\n    \"UMa\": \"Ursa Major\",\n    \"Cas\": \"Cassiopeia\",\n    \"Leo\": \"Leo\",\n    \"Cyg\": \"Cygnus\",\n    \"Gem\": \"Gemini\",\n    \"Tau\": \"Taurus\",\n    \"Lyr\": \"Lyra\",\n    \"Aql\": \"Aquila\",\n    \"Sco\": \"Scorpius\",\n    \"Boo\": \"Boötes\",\n    \"Per\": \"Perseus\",\n    \"Aur\": \"Auriga\",\n    \"CMa\": \"Canis Major\",\n    \"And\": \"Andromeda\",\n}\ncentroids = df[named_mask].groupby(\"constellation\")[[\"ra\", \"dec\"]].mean()\n\n# Plot - seaborn handles the theming and the magnitude-mapped star field\nsns.set_theme(\n    style=\"ticks\",\n    context=\"notebook\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"font.family\": \"sans-serif\",\n    },\n)\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\n# Milky Way band as a faint filled region (approximate galactic plane)\nmw_ra = np.linspace(0, 24, 200)\nmw_center_dec = 25 * np.sin(np.radians(mw_ra * 15 - 80)) + 5\nmw_width = 18\nax.fill_between(mw_ra, mw_center_dec - mw_width, mw_center_dec + mw_width, color=INK_MUTED, alpha=0.10, zorder=0)\n\n# Ecliptic (the Sun's apparent path) as a dashed amber curve\necl_ra = np.linspace(0, 24, 300)\necl_dec = 23.44 * np.sin(np.radians(ecl_ra * 15 - 90))\nax.plot(ecl_ra, ecl_dec, color=ANYPLOT_AMBER, linewidth=1.1, linestyle=\"--\", alpha=0.6, zorder=1)\n\n# Subtle RA/Dec coordinate grid\nfor ra_line in range(0, 25, 2):\n    ax.axvline(x=ra_line, color=INK, linewidth=0.4, alpha=0.10, zorder=0)\nfor dec_line in range(-45, 76, 15):\n    ax.axhline(y=dec_line, color=INK, linewidth=0.4, alpha=0.10, zorder=0)\n\n# Constellation stick-figure lines (thin, semi-transparent) with a soft glow\nfor s1, s2 in edges:\n    if s1 in star_lookup and s2 in star_lookup:\n        x1, y1 = star_lookup[s1][\"ra\"], star_lookup[s1][\"dec\"]\n        x2, y2 = star_lookup[s2][\"ra\"], star_lookup[s2][\"dec\"]\n        ax.plot([x1, x2], [y1, y2], color=INK_SOFT, linewidth=2.6, alpha=0.14, zorder=1)\n        ax.plot([x1, x2], [y1, y2], color=INK_SOFT, linewidth=0.9, alpha=0.55, zorder=1)\n\n# Background field stars via seaborn\nsns.scatterplot(\n    data=df[~named_mask],\n    x=\"ra\",\n    y=\"dec\",\n    s=df.loc[~named_mask, \"size\"],\n    color=INK_MUTED,\n    alpha=0.35,\n    edgecolor=\"none\",\n    legend=False,\n    ax=ax,\n    zorder=2,\n)\n\n# Named constellation stars via seaborn — colour encodes apparent magnitude (Imprint sequential)\nsns.scatterplot(\n    data=df[named_mask],\n    x=\"ra\",\n    y=\"dec\",\n    hue=\"mag\",\n    palette=imprint_seq,\n    hue_norm=(mag_min, mag_max),\n    s=df.loc[named_mask, \"size\"],\n    edgecolor=PAGE_BG,\n    linewidth=0.6,\n    alpha=0.95,\n    legend=False,\n    ax=ax,\n    zorder=3,\n)\n\n# Magnitude colourbar (brighter stars sit at the top)\nnorm = Normalize(vmin=mag_min, vmax=mag_max)\nsm = ScalarMappable(cmap=imprint_seq, norm=norm)\ncbar = fig.colorbar(sm, ax=ax, fraction=0.024, pad=0.015)\ncbar.set_label(\"Apparent magnitude\", fontsize=10, color=INK)\ncbar.ax.invert_yaxis()\ncbar.ax.tick_params(labelsize=8, color=INK_SOFT, labelcolor=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\ncbar.outline.set_linewidth(0.5)\n\n# Constellation name labels near each centroid\ntext_stroke = [pe.withStroke(linewidth=2.5, foreground=PAGE_BG)]\nlabel_offsets = {\n    \"CMa\": (0, -10),\n    \"Lyr\": (-1.1, 6),\n    \"Aql\": (0, -8),\n    \"Sco\": (0, -9),\n    \"Aur\": (1.1, -11),\n    \"Per\": (0.5, 10),\n    \"Ori\": (-1.2, -12),\n    \"Tau\": (-0.9, -6),\n    \"Gem\": (0, 7),\n    \"And\": (0, 6),\n    \"Boo\": (0, 7),\n}\nfor abbr, row in centroids.iterrows():\n    dx, dy = label_offsets.get(abbr, (0, 4))\n    ax.text(\n        row[\"ra\"] + dx,\n        row[\"dec\"] + dy,\n        constellation_names.get(abbr, abbr),\n        fontsize=9,\n        color=INK_SOFT,\n        alpha=0.95,\n        ha=\"center\",\n        va=\"bottom\",\n        fontweight=\"bold\",\n        fontstyle=\"italic\",\n        path_effects=text_stroke,\n        zorder=4,\n    )\n\n# Label the brightest stars (mag < 1.0)\nbrightest = df[(df[\"mag\"] < 1.0) & named_mask]\nstar_label_offsets = {\n    \"Sirius\": (-0.45, 4),\n    \"Vega\": (0.4, 4),\n    \"Capella\": (0.4, 4),\n    \"Arcturus\": (0.4, -3),\n    \"Altair\": (0.4, 4),\n    \"Aldebaran\": (0.4, -5),\n    \"Rigel\": (0.4, 4),\n    \"Betelgeuse\": (-0.35, -5),\n}\nfor _, star in brightest.iterrows():\n    dx, dy = star_label_offsets.get(star[\"name\"], (0.3, -3))\n    ax.text(\n        star[\"ra\"] + dx,\n        star[\"dec\"] + dy,\n        star[\"name\"],\n        fontsize=8,\n        color=INK,\n        alpha=0.9,\n        ha=\"left\",\n        va=\"top\",\n        fontweight=\"medium\",\n        path_effects=text_stroke,\n        zorder=4,\n    )\n\n# Style — RA shown in hours (matches the hour-labelled ticks), Dec in degrees\nax.set_xlim(24, 0)\nax.set_ylim(-50, 72)\nax.set_xlabel(\"Right Ascension (h)\", fontsize=11, color=INK)\nax.set_ylabel(\"Declination (°)\", fontsize=11, color=INK)\nax.set_title(\n    \"star-chart-constellation · python · seaborn · anyplot.ai\", fontsize=13, fontweight=\"medium\", color=INK, pad=18\n)\nax.text(\n    0.5,\n    1.01,\n    \"Equirectangular RA–Dec chart  ·  Ecliptic (dashed) & Milky Way (band)\",\n    transform=ax.transAxes,\n    fontsize=9,\n    color=INK_MUTED,\n    ha=\"center\",\n    va=\"bottom\",\n)\n\nax.set_xticks(np.arange(0, 25, 2))\nax.set_xticklabels([f\"{h}h\" for h in range(0, 25, 2)])\nax.set_yticks(np.arange(-45, 76, 15))\nax.set_yticklabels([f\"{d:+d}°\" for d in range(-45, 76, 15)])\nax.tick_params(axis=\"both\", labelsize=9)\n\nsns.despine(ax=ax)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}