{"spec_id":"scatter-hr-diagram","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nscatter-hr-diagram: Hertzsprung-Russell Diagram\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as mticker\nimport numpy as np\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 (semantic exception from Imprint categorical order)\n# A-type (#c8ddf0, blue-white) is now clearly distinct from B-type (#4488cc, blue)\nspectral_colors = {\n    \"O\": \"#2244aa\",  # blue-violet (hottest)\n    \"B\": \"#4488cc\",  # blue\n    \"A\": \"#c8ddf0\",  # blue-white (distinctly lighter than B)\n    \"F\": \"#e8e8b0\",  # yellow-white\n    \"G\": \"#ffcc00\",  # yellow\n    \"K\": \"#ee7711\",  # orange\n    \"M\": \"#cc2200\",  # red (coolest)\n}\n\ntemp_ranges = {\n    \"O\": (30000, 40000),\n    \"B\": (10000, 30000),\n    \"A\": (7500, 10000),\n    \"F\": (6000, 7500),\n    \"G\": (5200, 6000),\n    \"K\": (3700, 5200),\n    \"M\": (2400, 3700),\n}\n\n# Data\nnp.random.seed(42)\n\n# Main sequence stars (250)\nms_counts = {\"O\": 8, \"B\": 20, \"A\": 30, \"F\": 35, \"G\": 45, \"K\": 55, \"M\": 57}\nall_temps, all_lums, all_types = [], [], []\n\nfor sp, (t_lo, t_hi) in temp_ranges.items():\n    n = ms_counts[sp]\n    temps = np.random.uniform(t_lo, t_hi, n)\n    log_lums = 4.0 * np.log10(temps / 5778) + np.random.normal(0, 0.3, n)\n    all_temps.extend(temps)\n    all_lums.extend(10**log_lums)\n    all_types.extend([sp] * n)\n\n# Red giants (50)\nrg_temps = np.random.uniform(3000, 5200, 50)\nrg_lums = 10 ** np.random.uniform(1.0, 3.0, 50)\nall_temps.extend(rg_temps)\nall_lums.extend(rg_lums)\nall_types.extend(np.where(rg_temps >= 3700, \"K\", \"M\").tolist())\n\n# Supergiants (35) — np.select replaces nested ternaries for readability\nsg_temps = np.random.uniform(3500, 30000, 35)\nsg_lums = 10 ** np.random.uniform(3.5, 5.5, 35)\nall_temps.extend(sg_temps)\nall_lums.extend(sg_lums)\nall_types.extend(\n    np.select(\n        [sg_temps < 3700, sg_temps < 5200, sg_temps < 6000, sg_temps < 7500, sg_temps < 10000, sg_temps < 30000],\n        [\"M\", \"K\", \"G\", \"F\", \"A\", \"B\"],\n        default=\"O\",\n    ).tolist()\n)\n\n# White dwarfs (30)\nwd_temps = np.random.uniform(5000, 30000, 30)\nwd_lums = 10 ** np.random.uniform(-4.0, -1.5, 30)\nall_temps.extend(wd_temps)\nall_lums.extend(wd_lums)\nall_types.extend(\n    np.select(\n        [wd_temps < 6000, wd_temps < 7500, wd_temps < 10000, wd_temps < 30000], [\"G\", \"F\", \"A\", \"B\"], default=\"O\"\n    ).tolist()\n)\n\nall_temps = np.array(all_temps)\nall_lums = np.array(all_lums)\n\n# Plot — figsize=(8, 4.5) dpi=400 → exactly 3200×1800 px (no bbox_inches='tight')\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nfor sp in [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"]:\n    mask = np.array([t == sp for t in all_types])\n    if mask.any():\n        ax.scatter(\n            all_temps[mask],\n            all_lums[mask],\n            c=spectral_colors[sp],\n            label=sp,\n            s=80,\n            alpha=0.6,\n            edgecolors=PAGE_BG,\n            linewidth=0.4,\n            zorder=3,\n        )\n\n# Sun reference point — star marker with shadow glow\nax.scatter(\n    5778,\n    1.0,\n    c=\"#ffcc00\",\n    s=350,\n    edgecolors=INK,\n    linewidth=1.5,\n    zorder=5,\n    marker=\"*\",\n    path_effects=[pe.withSimplePatchShadow(offset=(1, -1), shadow_rgbFace=\"#ccaa00\", alpha=0.4)],\n)\nax.annotate(\n    \"Sun\",\n    (5778, 1.0),\n    textcoords=\"offset points\",\n    xytext=(12, -10),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=INK,\n    fontfamily=\"serif\",\n    path_effects=[pe.withStroke(linewidth=2, foreground=PAGE_BG)],\n)\n\n# Region labels with elevated background boxes\nregion_style = {\"fontsize\": 7, \"fontstyle\": \"italic\", \"color\": INK_SOFT, \"fontfamily\": \"serif\", \"ha\": \"center\"}\ntext_effect = [pe.withStroke(linewidth=3, foreground=PAGE_BG)]\n\nax.annotate(\n    \"Main Sequence\",\n    xy=(15000, 200),\n    rotation=-42,\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"fc\": ELEVATED_BG, \"ec\": \"none\", \"alpha\": 0.85},\n    path_effects=text_effect,\n    **region_style,\n)\nax.annotate(\n    \"Red Giants\",\n    xy=(3400, 300),\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"fc\": ELEVATED_BG, \"ec\": \"none\", \"alpha\": 0.85},\n    path_effects=text_effect,\n    **region_style,\n)\nax.annotate(\n    \"Supergiants\",\n    xy=(8000, 400000),\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"fc\": ELEVATED_BG, \"ec\": \"none\", \"alpha\": 0.85},\n    path_effects=text_effect,\n    **region_style,\n)\nax.annotate(\n    \"White Dwarfs\",\n    xy=(15000, 0.00008),\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"fc\": ELEVATED_BG, \"ec\": \"none\", \"alpha\": 0.85},\n    path_effects=text_effect,\n    **region_style,\n)\n\n# Style\nax.set_xscale(\"log\")\nax.set_yscale(\"log\")\nax.set_xlim(45000, 2000)\nax.set_ylim(1e-5, 2e6)\n\nax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f\"{int(x):,}\"))\n\ntitle = \"scatter-hr-diagram · python · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\n\nax.set_xlabel(\"Surface Temperature (K)\", fontsize=10, color=INK, fontfamily=\"serif\")\nax.set_ylabel(\"Luminosity (L/L$_\\\\odot$)\", fontsize=10, color=INK, fontfamily=\"serif\")\nax.set_title(\n    title,\n    fontsize=title_fontsize,\n    fontweight=\"medium\",\n    color=INK,\n    fontfamily=\"serif\",\n    pad=12,\n    path_effects=[pe.withStroke(linewidth=2, foreground=PAGE_BG)],\n)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nlegend = ax.legend(\n    title=\"Spectral Type\",\n    fontsize=7,\n    title_fontsize=8,\n    loc=\"lower left\",\n    framealpha=0.9,\n    edgecolor=INK_SOFT,\n    facecolor=ELEVATED_BG,\n    borderpad=0.8,\n)\nlegend.get_frame().set_facecolor(ELEVATED_BG)\nlegend.get_frame().set_edgecolor(INK_SOFT)\nlegend.get_title().set_fontfamily(\"serif\")\nlegend.get_title().set_color(INK)\nplt.setp(legend.get_texts(), color=INK_SOFT)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_linewidth(0.8)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_linewidth(0.8)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.yaxis.grid(True, alpha=0.12, linewidth=0.8, which=\"both\", color=INK)\n\n# Secondary x-axis for spectral classes\nax2 = ax.twiny()\nspectral_positions = [35000, 20000, 8750, 6750, 5600, 4450, 3050]\nspectral_labels_top = [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"]\nax2.set_xscale(\"log\")\nax2.set_xlim(ax.get_xlim())\nax2.set_xticks(spectral_positions)\nax2.set_xticklabels(spectral_labels_top, fontsize=8, fontfamily=\"serif\", color=INK_SOFT)\nax2.set_xlabel(\"Spectral Class\", fontsize=10, labelpad=8, fontfamily=\"serif\", color=INK)\nax2.tick_params(axis=\"x\", length=0, colors=INK_SOFT)\nfor spine in ax2.spines.values():\n    spine.set_visible(False)\n\nfig.subplots_adjust(left=0.10, right=0.97, top=0.79, bottom=0.13)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}