{"spec_id":"scatter-hr-diagram","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nscatter-hr-diagram: Hertzsprung-Russell Diagram\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (Imprint palette + 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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Domain-appropriate spectral colors using closest Imprint palette members (semantic exception to canonical order)\nANYPLOT_AMBER = \"#DDCC77\"  # warm-yellow anchor — used for G-type (sun-like golden)\nspectral_config = {\n    \"O\": {\"temp\": (30000, 40000), \"color\": \"#C475FD\", \"n\": 15},  # Imprint lavender (violet, hottest)\n    \"B\": {\"temp\": (10000, 30000), \"color\": \"#4467A3\", \"n\": 40},  # Imprint blue\n    \"A\": {\"temp\": (7500, 10000), \"color\": \"#2ABCCD\", \"n\": 45},  # Imprint cyan (blue-white)\n    \"F\": {\"temp\": (6000, 7500), \"color\": \"#99B314\", \"n\": 50},  # Imprint lime (white-yellow)\n    \"G\": {\"temp\": (5200, 6000), \"color\": ANYPLOT_AMBER, \"n\": 55},  # amber anchor (golden, sun-like)\n    \"K\": {\"temp\": (3700, 5200), \"color\": \"#BD8233\", \"n\": 50},  # Imprint ochre (orange)\n    \"M\": {\"temp\": (2400, 3700), \"color\": \"#AE3030\", \"n\": 45},  # Imprint matte red (coolest)\n}\n\n# Data\nnp.random.seed(42)\n\ntemperatures = []\nluminosities = []\nspectral_types = []\n\n# Main sequence stars (L ~ T^4 relationship with scatter)\nfor stype, cfg in spectral_config.items():\n    n = cfg[\"n\"]\n    temp = np.random.uniform(cfg[\"temp\"][0], cfg[\"temp\"][1], n)\n    log_lum_base = 4.0 * np.log10(temp / 5778)\n    log_lum = log_lum_base + np.random.normal(0, 0.3, n)\n    temperatures.extend(temp)\n    luminosities.extend(10**log_lum)\n    spectral_types.extend([stype] * n)\n\n# Red giants (cool but bright)\nn_rg = 35\nrg_temp = np.random.uniform(3000, 5200, n_rg)\nrg_lum = 10 ** np.random.uniform(1.5, 3.5, n_rg)\ntemperatures.extend(rg_temp)\nluminosities.extend(rg_lum)\nspectral_types.extend(np.where(rg_temp > 3700, \"K\", \"M\").tolist())\n\n# Supergiants (bright across temperatures) — classify without helper function\nn_sg = 20\nsg_temp = np.random.uniform(3500, 30000, n_sg)\nsg_lum = 10 ** np.random.uniform(4.0, 5.8, n_sg)\ntemperatures.extend(sg_temp)\nluminosities.extend(sg_lum)\nspectral_types.extend(\n    np.select(\n        [sg_temp > 30000, sg_temp > 10000, sg_temp > 7500, sg_temp > 6000, sg_temp > 5200, sg_temp > 3700],\n        [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\"],\n        default=\"M\",\n    ).tolist()\n)\n\n# White dwarfs (hot but dim) — classify without helper function\nn_wd = 30\nwd_temp = np.random.uniform(7000, 30000, n_wd)\nwd_lum = 10 ** np.random.uniform(-4, -1.5, n_wd)\ntemperatures.extend(wd_temp)\nluminosities.extend(wd_lum)\nspectral_types.extend(\n    np.select(\n        [wd_temp > 30000, wd_temp > 10000, wd_temp > 7500, wd_temp > 6000, wd_temp > 5200, wd_temp > 3700],\n        [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\"],\n        default=\"M\",\n    ).tolist()\n)\n\ntemperatures = np.array(temperatures)\nluminosities = np.array(luminosities)\nspectral_types = np.array(spectral_types)\n\nspectral_colors = {k: v[\"color\"] for k, v in spectral_config.items()}\n\n# Plot\nfig = go.Figure()\n\nspectral_order = [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"]\nfor stype in spectral_order:\n    mask = spectral_types == stype\n    # A (#2ABCCD) and F (#99B314) get thicker stroke for extra contrast on light background\n    stroke_width = 1.5 if stype in (\"A\", \"F\") else 0.5\n    fig.add_trace(\n        go.Scatter(\n            x=temperatures[mask],\n            y=luminosities[mask],\n            mode=\"markers\",\n            name=stype,\n            marker={\n                \"size\": 11,\n                \"color\": spectral_colors[stype],\n                \"line\": {\"width\": stroke_width, \"color\": INK_SOFT},\n                \"opacity\": 0.55,\n            },\n            hovertemplate=(\n                f\"Spectral Type: {stype}<br>Temperature: %{{x:,.0f}} K<br>Luminosity: %{{y:.4g}} L☉<br><extra></extra>\"\n            ),\n        )\n    )\n\n# Sun reference point — star focal point using G-type color\nfig.add_trace(\n    go.Scatter(\n        x=[5778],\n        y=[1.0],\n        mode=\"markers\",\n        name=\"☉ Sun\",\n        marker={\"size\": 22, \"color\": ANYPLOT_AMBER, \"line\": {\"width\": 2, \"color\": \"#BD8233\"}, \"symbol\": \"star\"},\n        hovertemplate=\"The Sun<br>Temperature: 5,778 K<br>Luminosity: 1.0 L☉<br><extra></extra>\",\n    )\n)\n\n# Sun label annotation with arrow (log10 coords required for log-type axes in Plotly)\nfig.add_annotation(\n    x=np.log10(5778),\n    y=np.log10(1.0),\n    xref=\"x\",\n    yref=\"y\",\n    text=\"<b>☉ Sun</b>\",\n    showarrow=True,\n    arrowhead=0,\n    arrowwidth=1.5,\n    arrowcolor=INK_SOFT,\n    ax=-55,\n    ay=-40,\n    font={\"size\": 14, \"color\": INK},\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderpad=4,\n    opacity=0.9,\n)\n\n# Region label annotations (theme-adaptive backgrounds)\nregion_labels = {\n    \"Main Sequence\": {\"x\": 15000, \"y\": 50},\n    \"Red Giants\": {\"x\": 3800, \"y\": 800},\n    \"Supergiants\": {\"x\": 10000, \"y\": 200000},\n    \"White Dwarfs\": {\"x\": 15000, \"y\": 0.0003},\n}\n\nfor label, pos in region_labels.items():\n    fig.add_annotation(\n        x=np.log10(pos[\"x\"]),\n        y=np.log10(pos[\"y\"]),\n        xref=\"x\",\n        yref=\"y\",\n        text=f\"<b>{label}</b>\",\n        showarrow=False,\n        font={\"size\": 14, \"color\": INK},\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n        borderpad=6,\n        opacity=0.9,\n    )\n\n# Secondary x-axis with spectral class labels\nspectral_temps = {\"O\": 35000, \"B\": 20000, \"A\": 8750, \"F\": 6750, \"G\": 5600, \"K\": 4450, \"M\": 3050}\n\n# Empty trace to activate xaxis2\nfig.add_trace(go.Scatter(x=[], y=[], xaxis=\"x2\", showlegend=False, hoverinfo=\"skip\"))\n\ntitle = \"scatter-hr-diagram · python · plotly · anyplot.ai\"\n\n# Layout — canvas 800×450 at scale=4 → 3200×1800 px output\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Surface Temperature (K)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"type\": \"log\",\n        \"autorange\": \"reversed\",\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n        \"tickvals\": [2500, 5000, 10000, 20000, 40000],\n        \"ticktext\": [\"2,500\", \"5,000\", \"10,000\", \"20,000\", \"40,000\"],\n    },\n    yaxis={\n        \"title\": {\"text\": \"Luminosity (L☉)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"type\": \"log\",\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n    },\n    xaxis2={\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"overlaying\": \"x\",\n        \"side\": \"top\",\n        \"type\": \"log\",\n        \"range\": [np.log10(45000), np.log10(2000)],\n        \"tickvals\": list(spectral_temps.values()),\n        \"ticktext\": list(spectral_temps.keys()),\n        \"showgrid\": False,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"matches\": \"x\",\n    },\n    legend={\n        \"title\": {\"text\": \"Spectral Type\", \"font\": {\"size\": 10, \"color\": INK}},\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"x\": 0.98,\n        \"y\": 0.02,\n        \"xanchor\": \"right\",\n        \"yanchor\": \"bottom\",\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n)\n\n# Save — 3200×1800 landscape (width=800, height=450, scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}