{"spec_id":"scatter-hr-diagram","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nscatter-hr-diagram: Hertzsprung-Russell Diagram\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_text,\n    ggplot,\n    ggsize,\n    guides,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_log10,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Spectral type colors — closest Imprint palette members to astrophysical convention\nspectral_colors = {\n    \"O\": \"#C475FD\",  # lavender — nearest Imprint member to hot-violet O-type\n    \"B\": \"#4467A3\",  # blue\n    \"A\": \"#2ABCCD\",  # cyan — lighter blue-white A-type\n    \"F\": \"#BD8233\",  # ochre — nearest to yellow-white F-type\n    \"G\": \"#99B314\",  # lime — nearest to solar-yellow G-type\n    \"K\": \"#954477\",  # rose — closest Imprint member to orange K-type\n    \"M\": \"#AE3030\",  # matte red — cool red M-type\n}\n\n# Data — synthetic stellar populations\nnp.random.seed(42)\n\n# Main sequence (diagonal band from hot/bright to cool/dim)\nn_main = 200\nmain_temp = 10 ** np.random.uniform(np.log10(3000), np.log10(35000), n_main)\nmain_log_lum = 4.0 * (np.log10(main_temp) - np.log10(5778))\nmain_log_lum += np.random.normal(0, 0.25, n_main)\nmain_luminosity = 10**main_log_lum\n\n# Red giants (cool but bright)\nn_giants = 40\ngiant_temp = np.random.uniform(3200, 5500, n_giants)\ngiant_luminosity = 10 ** np.random.uniform(1.0, 3.5, n_giants)\n\n# Supergiants (very bright, wide temp range)\nn_super = 25\nsuper_temp = np.random.uniform(3500, 30000, n_super)\nsuper_luminosity = 10 ** np.random.uniform(3.5, 5.5, n_super)\n\n# White dwarfs (hot but very dim)\nn_dwarfs = 30\ndwarf_temp = np.random.uniform(5000, 30000, n_dwarfs)\ndwarf_luminosity = 10 ** np.random.uniform(-4, -1.5, n_dwarfs)\n\ntemperature = np.concatenate([main_temp, giant_temp, super_temp, dwarf_temp])\nluminosity = np.concatenate([main_luminosity, giant_luminosity, super_luminosity, dwarf_luminosity])\nregion = (\n    [\"Main Sequence\"] * n_main + [\"Red Giants\"] * n_giants + [\"Supergiants\"] * n_super + [\"White Dwarfs\"] * n_dwarfs\n)\n\nspectral_type = np.select(\n    [\n        temperature >= 30000,\n        temperature >= 10000,\n        temperature >= 7500,\n        temperature >= 6000,\n        temperature >= 5200,\n        temperature >= 3700,\n    ],\n    [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\"],\n    default=\"M\",\n)\n\ndf = pd.DataFrame(\n    {\"temperature\": temperature, \"luminosity\": luminosity, \"region\": region, \"spectral_type\": spectral_type}\n)\n\nsun_df = pd.DataFrame({\"temperature\": [5778], \"luminosity\": [1.0]})\nsun_label_df = pd.DataFrame({\"temperature\": [7800], \"luminosity\": [4.0], \"label\": [\"☉ Sun\"]})\n\nregion_labels = pd.DataFrame(\n    {\n        \"temperature\": [25000, 4500, 14000, 18000],\n        \"luminosity\": [0.012, 8000, 120000, 0.0005],\n        \"label\": [\"Main Sequence\", \"Red Giants\", \"Supergiants\", \"White Dwarfs\"],\n    }\n)\n\n# Spectral class markers — staggered y-positions to avoid bunching on the linear scale\nspectral_axis_labels = pd.DataFrame(\n    {\n        \"temperature\": [35000, 18000, 8500, 6800, 5500, 4200, 3100],\n        \"luminosity\": [1200000, 500000, 1200000, 500000, 1200000, 500000, 1200000],\n        \"label\": [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"],\n    }\n)\n\n# Plot\nTITLE = \"scatter-hr-diagram · python · letsplot · anyplot.ai\"\ntitle_size = round(16 * 67 / len(TITLE)) if len(TITLE) > 67 else 16\n\nplot = (\n    ggplot(df, aes(x=\"temperature\", y=\"luminosity\"))\n    + geom_point(\n        size=4.5,\n        alpha=0.75,\n        shape=21,\n        stroke=1.0,\n        mapping=aes(fill=\"spectral_type\"),\n        color=INK_MUTED,\n        tooltips=layer_tooltips()\n        .line(\"@region\")\n        .line(\"Temperature|@temperature K\")\n        .line(\"Luminosity|@luminosity L☉\")\n        .line(\"Spectral Type|@spectral_type\"),\n    )\n    + geom_point(\n        data=sun_df,\n        mapping=aes(x=\"temperature\", y=\"luminosity\"),\n        color=\"#FFD700\",\n        fill=\"#FFD700\",\n        size=9,\n        shape=21,\n        stroke=2.0,\n        inherit_aes=False,\n    )\n    + geom_text(\n        data=sun_label_df,\n        mapping=aes(x=\"temperature\", y=\"luminosity\", label=\"label\"),\n        size=5,\n        color=\"#FFD700\",\n        fontface=\"bold\",\n        inherit_aes=False,\n    )\n    + geom_text(\n        data=region_labels,\n        mapping=aes(x=\"temperature\", y=\"luminosity\", label=\"label\"),\n        size=4,\n        color=INK_MUTED,\n        fontface=\"bold_italic\",\n        inherit_aes=False,\n    )\n    + geom_text(\n        data=spectral_axis_labels,\n        mapping=aes(x=\"temperature\", y=\"luminosity\", label=\"label\"),\n        size=4.5,\n        color=INK_SOFT,\n        fontface=\"bold\",\n        inherit_aes=False,\n    )\n    + scale_x_continuous(\n        trans=\"reverse\",\n        name=\"Surface Temperature (K)\",\n        breaks=[40000, 30000, 20000, 10000, 5000, 3000],\n        labels=[\"40,000\", \"30,000\", \"20,000\", \"10,000\", \"5,000\", \"3,000\"],\n    )\n    + scale_y_log10(name=\"Luminosity (L☉)\", limits=[0.00005, 2000000])\n    + scale_fill_manual(\n        values=[spectral_colors[k] for k in [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"]],\n        limits=[\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"],\n        name=\"Spectral Type\",\n    )\n    + guides(color=\"none\")\n    + labs(title=TITLE)\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        plot_title=element_text(size=title_size, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_title=element_text(size=12, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_grid_major=element_line(color=INK_MUTED, size=0.2),\n        panel_grid_minor=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        axis_line=element_line(color=INK_SOFT),\n        axis_ticks=element_line(color=INK_SOFT, size=0.3),\n        plot_margin=[30, 40, 20, 20],\n    )\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}