{"spec_id":"scatter-hr-diagram","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nscatter-hr-diagram: Hertzsprung-Russell Diagram\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent script directory from shadowing stdlib (matplotlib.py sibling in same dir)\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\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\"\n\n# Spectral type colors: astrophysical convention mapped to closest Imprint palette members.\n# O-type (first categorical series) uses #4467A3 (Imprint blue) — O/B-type stars are blue-hot;\n# using brand green #009E73 as first-series default would violate the strong spectral convention.\n# A-type uses #F0EFE8 (Imprint near-white anchor); marker edge coloring provides contrast on light bg.\n# F-type uses #99B314 (Imprint lime) as closest warm Imprint member to white-yellow F stars.\nspectral_colors = {\n    \"O\": \"#4467A3\",  # Imprint blue — hottest stars; first-series #009E73 exception: spectral convention\n    \"B\": \"#2ABCCD\",  # Imprint cyan — hot blue stars\n    \"A\": \"#F0EFE8\",  # Imprint near-white anchor — blue-white A-type; edges provide contrast\n    \"F\": \"#99B314\",  # Imprint lime — warm-white F-type, closest warm Imprint member\n    \"G\": \"#DDCC77\",  # Imprint amber — solar yellow, exact astrophysical convention match\n    \"K\": \"#BD8233\",  # Imprint ochre — orange-brown K-type\n    \"M\": \"#AE3030\",  # Imprint matte red — cool red M-type, exact convention match\n}\n\n# Theme-adaptive edge: dark outline on light bg for pale stars, page bg on dark\nEDGE_COLOR = INK_SOFT if THEME == \"light\" else PAGE_BG\nEDGE_WIDTH = 0.8 if THEME == \"light\" else 0.5\n\nsns.set_theme(\n    style=\"ticks\",\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        \"grid.color\": INK,\n        \"grid.alpha\": 0.12,\n        \"grid.linewidth\": 0.5,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data\nnp.random.seed(42)\n\nmain_seq_temp = np.concatenate(\n    [\n        np.random.uniform(25000, 40000, 15),\n        np.random.uniform(10000, 25000, 40),\n        np.random.uniform(6000, 10000, 60),\n        np.random.uniform(3500, 6000, 80),\n        np.random.uniform(2000, 3500, 55),\n    ]\n)\nmain_seq_lum = 10 ** (np.log10(main_seq_temp / 5778) * 3.5 + np.random.normal(0, 0.3, len(main_seq_temp)))\n\nrg_temp = np.random.uniform(3000, 5500, 35)\nrg_lum = 10 ** np.random.uniform(1.5, 3.5, 35)\n\nsg_temp = np.random.uniform(3000, 30000, 20)\nsg_lum = 10 ** np.random.uniform(3.5, 5.5, 20)\n\nwd_temp = np.random.uniform(5000, 30000, 25)\nwd_lum = 10 ** np.random.uniform(-4, -1.5, 25)\n\ntemperatures = np.concatenate([main_seq_temp, rg_temp, sg_temp, wd_temp])\nluminosities = np.concatenate([main_seq_lum, rg_lum, sg_lum, wd_lum])\nregions = (\n    [\"Main Sequence\"] * len(main_seq_temp)\n    + [\"Red Giants\"] * len(rg_temp)\n    + [\"Supergiants\"] * len(sg_temp)\n    + [\"White Dwarfs\"] * len(wd_temp)\n)\n\nspectral_types = np.select(\n    [\n        temperatures >= 30000,\n        temperatures >= 10000,\n        temperatures >= 7500,\n        temperatures >= 6000,\n        temperatures >= 5200,\n        temperatures >= 3700,\n    ],\n    [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\"],\n    default=\"M\",\n)\n\ndf = pd.DataFrame(\n    {\n        \"Temperature (K)\": temperatures,\n        \"Luminosity (L☉)\": luminosities,\n        \"Region\": regions,\n        \"Spectral Type\": spectral_types,\n    }\n)\n\n# Plot — figsize=(8, 4.5) at dpi=400 → exactly 3200×1800 px\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\nfig.subplots_adjust(top=0.86)  # breathing room for secondary spectral-class axis + title\n\nspectral_order = [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"]\npalette = [spectral_colors[s] for s in spectral_order]\n\nregion_markers = {\"Main Sequence\": \"o\", \"Red Giants\": \"D\", \"Supergiants\": \"s\", \"White Dwarfs\": \"v\"}\n\nsns.scatterplot(\n    data=df,\n    x=\"Temperature (K)\",\n    y=\"Luminosity (L☉)\",\n    hue=\"Spectral Type\",\n    hue_order=spectral_order,\n    palette=palette,\n    style=\"Region\",\n    markers=region_markers,\n    s=80,\n    alpha=0.65,\n    edgecolor=EDGE_COLOR,\n    linewidth=EDGE_WIDTH,\n    ax=ax,\n    legend=\"full\",\n)\n\n# KDE density contours along the main sequence — Imprint blue, seaborn distinctive feature\nms_df = df[df[\"Region\"] == \"Main Sequence\"]\nsns.kdeplot(\n    data=ms_df,\n    x=\"Temperature (K)\",\n    y=\"Luminosity (L☉)\",\n    levels=4,\n    color=\"#4467A3\",\n    alpha=0.45,\n    linewidths=1.0,\n    ax=ax,\n    log_scale=True,\n)\n\n# Sun reference — hexagon marker differentiates this impl from other library implementations\nax.scatter(5778, 1, s=280, color=\"#DDCC77\", edgecolors=INK_SOFT, linewidth=1.5, zorder=10, marker=\"h\")\nax.annotate(\n    \"Sun\",\n    (5778, 1),\n    textcoords=\"offset points\",\n    xytext=(10, -6),\n    fontsize=8,\n    color=INK,\n    fontweight=\"bold\",\n    path_effects=[pe.withStroke(linewidth=2.5, foreground=PAGE_BG)],\n)\n\n# Region labels — positioned in sparsely occupied zones to avoid KDE overlap\ntext_style = {\n    \"fontsize\": 8,\n    \"color\": INK_MUTED,\n    \"fontstyle\": \"italic\",\n    \"path_effects\": [pe.withStroke(linewidth=2, foreground=PAGE_BG)],\n}\nax.text(5500, 2e4, \"Supergiants\", ha=\"center\", **text_style)\nax.text(3600, 600, \"Red Giants\", ha=\"center\", **text_style)\nax.text(22000, 3e-4, \"White Dwarfs\", ha=\"center\", **text_style)\n# Main Sequence label placed in sparse upper-left region of the diagonal (O/B star zone)\nax.text(28000, 400, \"Main Sequence\", ha=\"center\", rotation=-42, **text_style)\n\n# Style — log scale, reversed x-axis per astrophysical convention\nax.set_xscale(\"log\")\nax.set_yscale(\"log\")\nax.invert_xaxis()\nax.set_xlim(45000, 1800)\nax.set_ylim(1e-5, 1e6)\n\ntitle = \"scatter-hr-diagram · python · seaborn · anyplot.ai\"\nax.set_xlabel(\"Surface Temperature (K)\", fontsize=10, color=INK)\nax.set_ylabel(\"Luminosity (L☉)\", fontsize=10, color=INK)\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=12)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.12, linewidth=0.5, color=INK)\nax.xaxis.grid(True, alpha=0.12, linewidth=0.5, color=INK)\n\n# Secondary spectral class axis\nspec_boundaries = {\"O\": 35000, \"B\": 17000, \"A\": 8500, \"F\": 6500, \"G\": 5500, \"K\": 4200, \"M\": 2800}\nax2 = ax.twiny()\nax2.set_xscale(\"log\")\nax2.set_xlim(ax.get_xlim())\nax2.set_xticks(list(spec_boundaries.values()))\nax2.set_xticklabels(list(spec_boundaries.keys()))\nax2.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT, length=0)\nax2.spines[\"top\"].set_color(INK_SOFT)\nax2.spines[\"right\"].set_visible(False)\nax2.set_xlabel(\"Spectral Class\", fontsize=8, color=INK_MUTED, labelpad=8)\n\n# Separate spectral type and region legends\nhandles, labels = ax.get_legend_handles_labels()\nspectral_handles = [(h, lab) for h, lab in zip(handles, labels, strict=False) if lab in spectral_order]\nregion_handles = [(h, lab) for h, lab in zip(handles, labels, strict=False) if lab in region_markers]\n\nleg1 = ax.legend(\n    [h for h, _ in spectral_handles],\n    [lab for _, lab in spectral_handles],\n    title=\"Spectral Type\",\n    fontsize=8,\n    title_fontsize=9,\n    loc=\"lower left\",\n    framealpha=0.85,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    labelcolor=INK_SOFT,\n)\nleg1.get_title().set_color(INK)\n\nleg2 = ax.legend(\n    [h for h, _ in region_handles],\n    [lab for _, lab in region_handles],\n    title=\"Region\",\n    fontsize=8,\n    title_fontsize=9,\n    loc=\"upper right\",\n    framealpha=0.85,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    labelcolor=INK_SOFT,\n)\nleg2.get_title().set_color(INK)\nax.add_artist(leg1)\n\n# Save — bbox_inches must stay default (None) to preserve exact 3200×1800 px\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}