{"spec_id":"scatter-hr-diagram","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nscatter-hr-diagram: Hertzsprung-Russell Diagram\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this file's own directory from sys.path before importing pygal,\n# so the installed package is found rather than this script itself.\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 numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette — first series must be brand green per Imprint rule.\n# F/G Stars: lavender replaces amber #DDCC77 (reserved for warning/caution only).\nSPECTRAL_COLORS = (\n    \"#009E73\",  # O/B Stars — brand green (Imprint first-series rule)\n    \"#2ABCCD\",  # A Stars — Imprint cyan\n    \"#C475FD\",  # F/G Stars — Imprint lavender (replaces out-of-pool #DDCC77)\n    \"#BD8233\",  # K Stars — Imprint ochre (orange-cool)\n    \"#AE3030\",  # M Stars — Imprint matte red (cool, red)\n    INK,  # Sun ☉ — theme-adaptive ink (distinct reference marker)\n)\n\n# Data — synthetic stellar populations for the HR diagram\nnp.random.seed(42)\n\n# Main sequence stars (diagonal band: hot/bright to cool/dim)\nn_main = 200\nmain_temp = np.random.uniform(3000, 35000, n_main)\nmain_log_lum = np.interp(main_temp, [3000, 5000, 8000, 15000, 35000], [-2, -0.5, 1.5, 3.5, 5.5])\nmain_log_lum += np.random.normal(0, 0.3, n_main)\n\n# Red giants (cool but luminous)\nn_giants = 40\ngiant_temp = np.random.uniform(3200, 5500, n_giants)\ngiant_log_lum = np.random.uniform(1.5, 3.2, n_giants)\n\n# Supergiants (very luminous, range of temperatures)\nn_super = 15\nsuper_temp = np.random.uniform(3500, 25000, n_super)\nsuper_log_lum = np.random.uniform(4.0, 5.8, n_super)\n\n# White dwarfs (hot but very dim)\nn_wd = 30\nwd_temp = np.random.uniform(5000, 30000, n_wd)\nwd_log_lum = np.random.uniform(-4, -1.5, n_wd)\n\n# Sun as reference\nsun_temp = 5778.0\nsun_log_lum = 0.0\n\n# Combine all stars\nall_temps = np.concatenate([main_temp, giant_temp, super_temp, wd_temp])\nall_log_lums = np.concatenate([main_log_lum, giant_log_lum, super_log_lum, wd_log_lum])\n\n# Spectral type classification based on temperature\nspectral_bounds = [\n    (\"O/B Stars\", 10000, 50000),\n    (\"A Stars\", 7500, 10000),\n    (\"F/G Stars\", 5200, 7500),\n    (\"K Stars\", 3700, 5200),\n    (\"M Stars\", 2000, 3700),\n]\n\ngroups = {name: [] for name, _, _ in spectral_bounds}\nfor t, log_l in zip(all_temps, all_log_lums, strict=True):\n    for name, lo, hi in spectral_bounds:\n        if lo <= t < hi or (name == \"O/B Stars\" and t >= hi):\n            groups[name].append((-np.log10(float(t)), float(log_l)))\n            break\n\n# Style — Imprint palette with theme-adaptive chrome\nfont = \"DejaVu Sans, Helvetica, Arial, sans-serif\"\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    guide_stroke_color=INK_MUTED,\n    colors=SPECTRAL_COLORS,\n    font_family=font,\n    title_font_family=font,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    legend_font_family=font,\n    value_font_size=36,\n    stroke_width=2.5,\n    tooltip_font_size=28,\n    tooltip_font_family=font,\n    opacity=0.70,\n    opacity_hover=0.95,\n)\n\n# Custom x-axis labels: map -log10(T) values to human-readable temperatures\nx_label_temps = [40000, 25000, 10000, 7500, 5000, 3500, 2500]\nx_labels = [{\"value\": -np.log10(t), \"label\": f\"{t:,} K\"} for t in x_label_temps]\n\n# Dot sizes per spectral group (M Stars increased from 5→7 for better visibility)\ndot_sizes = {\"O/B Stars\": 10, \"A Stars\": 9, \"F/G Stars\": 8, \"K Stars\": 7, \"M Stars\": 7}\n\n# Chart — XY scatter; -log10(T) x-axis reverses direction and spreads cool stars\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"scatter-hr-diagram · python · pygal · anyplot.ai\",\n    x_title=\"Hot  ←  Surface Temperature (K)  →  Cool\",\n    y_title=\"log₁₀ Luminosity (L☉)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=6,\n    legend_box_size=22,\n    stroke=False,\n    dots_size=8,\n    show_x_guides=True,\n    show_y_guides=True,\n    x_labels=x_labels,\n    x_label_rotation=-30,\n    xrange=(-np.log10(45000), -np.log10(2200)),\n    range=(-5, 7.5),\n    x_value_formatter=lambda x: f\"{10 ** abs(x):,.0f} K\",\n    value_formatter=lambda y: f\"{y:.1f}\",\n    margin_bottom=120,\n    margin_left=90,\n    margin_right=60,\n    margin_top=60,\n    truncate_legend=-1,\n    print_labels=True,\n    print_values=False,\n    css=[\n        \"file://style.css\",\n        \"file://graph.css\",\n        (\n            f\"inline:\"\n            f\".label{{font-size:38px !important; font-weight:bold !important;\"\n            f\" font-family:DejaVu Sans, sans-serif !important;\"\n            f\" fill:{INK} !important; paint-order:stroke fill;\"\n            f\" stroke:{PAGE_BG} !important; stroke-width:5px !important;}}\"\n        ),\n        # Soften grid: solid thin lines, low opacity; remove full box frame.\n        (\n            \"inline:\"\n            \".guide{stroke-dasharray:none !important;\"\n            \" stroke-width:1.5px !important;\"\n            \" stroke-opacity:0.18 !important;}\"\n            \".background,.chart-background\"\n            \"{stroke:none !important;}\"\n        ),\n    ],\n    js=[],\n)\n\n# Add each spectral group as a separate series\nseries_order = [\"O/B Stars\", \"A Stars\", \"F/G Stars\", \"K Stars\", \"M Stars\"]\nfor stype in series_order:\n    pts = groups.get(stype, [])\n    chart.add(stype, pts, stroke=False, dots_size=dot_sizes[stype])\n\n# Add the Sun as a distinct reference point (6th color = INK, theme-adaptive)\nchart.add(\n    \"Sun ☉\",\n    [{\"value\": (-np.log10(sun_temp), sun_log_lum), \"label\": \"The Sun (5,778 K, 1 L☉)\"}],\n    stroke=False,\n    dots_size=16,\n)\n\n# Region labels placed in low-density zones to minimise data overlap\nregion_labels = [\n    (\"Supergiants\", -np.log10(8000), 5.8),\n    (\"Main Sequence\", -np.log10(6000), 2.5),\n    (\"Red Giants\", -np.log10(4000), 2.6),\n    (\"White Dwarfs\", -np.log10(15000), -2.8),\n]\nfor region_name, rx, ry in region_labels:\n    chart.add(None, [{\"value\": (rx, ry), \"label\": region_name}], stroke=False, dots_size=2, show_dots=True)\n\n# Save — theme-suffixed output files (always in this script's directory)\nchart.render_to_png(os.path.join(_here, f\"plot-{THEME}.png\"))\nwith open(os.path.join(_here, f\"plot-{THEME}.html\"), \"wb\") as f:\n    f.write(chart.render())\n"}