{"spec_id":"scatter-hr-diagram","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// scatter-hr-diagram: Hertzsprung-Russell Diagram\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-08-26\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic PRNG (LCG, fixed seed) -----------------------------------\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction gauss(mean, std) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return mean + std * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- Spectral classification (temperature -> letter, boundary in Kelvin) ---\n// Imprint has no true \"orange\"/\"white\" hues, so only the two strongest,\n// unambiguous conventions (hot O/B = blue, cool M = red) get their semantic\n// match; the mid-range classes take distinguishable Imprint members instead\n// of an invented custom hex — see prompts/default-style-guide.md \"Semantic\n// exception\". Every class is spelled out in the legend, so the mapping reads\n// as a key, not a literal color claim.\nconst SPECTRAL_BOUNDARIES = [\n  { letter: \"O\", min: 30000, color: \"#2ABCCD\" },\n  { letter: \"B\", min: 10000, color: \"#4467A3\" },\n  { letter: \"A\", min: 7500, color: \"#C475FD\" },\n  { letter: \"F\", min: 6000, color: \"#BD8233\" },\n  { letter: \"G\", min: 5200, color: \"#009E73\" },\n  { letter: \"K\", min: 3700, color: \"#954477\" },\n  { letter: \"M\", min: 0, color: \"#AE3030\" },\n];\nfunction spectralClass(temperature) {\n  return SPECTRAL_BOUNDARIES.find((b) => temperature >= b.min);\n}\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Main sequence: log-spread temperatures, luminosity follows the empirical\n// mass-luminosity relation (L ~ T^5.3, calibrated so the Sun sits on the curve).\nfunction starPoint(temperature, luminosity, name) {\n  const spectral = spectralClass(temperature);\n  return {\n    x: temperature,\n    y: luminosity,\n    color: spectral.color,\n    spectral_type: spectral.letter,\n    star_name: name,\n  };\n}\n\nconst mainSequence = [];\nfor (let i = 0; i < 220; i++) {\n  const logT = gauss(3.72, 0.28); // ~3000-30000 K, skewed cool (M dwarfs dominate)\n  const temperature = Math.min(33000, Math.max(2800, Math.pow(10, logT)));\n  const baseLum = Math.pow(temperature / 5778, 5.3);\n  const luminosity = baseLum * Math.pow(10, gauss(0, 0.15));\n  mainSequence.push(starPoint(temperature, luminosity, `MS-${i + 1}`));\n}\n\n// Red giants: cool, but 10-1000x solar luminosity.\nconst redGiants = [];\nfor (let i = 0; i < 38; i++) {\n  const temperature = Math.max(3200, Math.min(5200, gauss(4300, 500)));\n  const luminosity = Math.pow(10, gauss(1.8, 0.35));\n  redGiants.push(starPoint(temperature, luminosity, `RG-${i + 1}`));\n}\n\n// Supergiants: wide temperature range, extreme luminosity.\nconst supergiants = [];\nfor (let i = 0; i < 16; i++) {\n  const temperature = Math.max(3000, Math.min(28000, gauss(8000, 5500)));\n  const luminosity = Math.pow(10, gauss(4.7, 0.4));\n  supergiants.push(starPoint(temperature, luminosity, `SG-${i + 1}`));\n}\n\n// White dwarfs: hot but tiny, so faint.\nconst whiteDwarfs = [];\nfor (let i = 0; i < 26; i++) {\n  const temperature = Math.max(6000, Math.min(38000, gauss(15000, 6000)));\n  const luminosity = Math.pow(10, gauss(-2.7, 0.4));\n  whiteDwarfs.push(starPoint(temperature, luminosity, `WD-${i + 1}`));\n}\n\nconst sun = {\n  ...starPoint(5778, 1.0, \"Sun\"),\n  marker: { symbol: \"circle\", radius: 9, lineWidth: 2, lineColor: t.ink },\n  dataLabels: {\n    enabled: true,\n    format: \"☉ Sun\",\n    align: \"left\",\n    x: 12,\n    y: 4,\n    style: { color: t.ink, fontSize: \"14px\", fontWeight: \"600\", textOutline: \"none\" },\n  },\n};\n\n// Legend-only entries: one swatch per spectral class so hue (temperature\n// class) reads independently from marker shape (region), which is encoded\n// per-series below.\nconst spectralLegend = SPECTRAL_BOUNDARIES.map((s) => ({\n  name: s.letter,\n  color: s.color,\n  data: [],\n  marker: { symbol: \"circle\", radius: 6 },\n  enableMouseTracking: false,\n  showInLegend: true,\n}));\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"scatter-hr-diagram · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Synthetic stellar population · marker color = spectral type, shape = region\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    title: {\n      text: \"Surface Temperature (K)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    type: \"logarithmic\",\n    reversed: true,\n    min: 2800,\n    max: 33000,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: {\n      text: \"Luminosity (L☉, log scale)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    type: \"logarithmic\",\n    min: 0.00005,\n    max: 300000,\n    gridLineColor: t.grid,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    title: {\n      text: \"Region (shape)      Spectral type (color)\",\n      style: { color: t.inkSoft, fontSize: \"13px\" },\n    },\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    pointFormat: \"{point.star_name} ({point.spectral_type})<br/>T: {point.x:.0f} K<br/>L: {point.y:.4f} L☉\",\n  },\n  // Region series keep one neutral swatch (chrome, not data) since shape\n  // already encodes region; per-point `color` (set in starPoint) carries the\n  // spectral-type hue instead, so the two channels never compete.\n  colors: [t.inkSoft, t.inkSoft, t.inkSoft, t.inkSoft, t.inkSoft],\n  plotOptions: {\n    series: {\n      animation: false,\n      marker: { lineWidth: 0.5, lineColor: t.pageBg },\n    },\n  },\n  series: [\n    {\n      name: \"Main sequence\",\n      data: mainSequence,\n      marker: { symbol: \"circle\", radius: 5 },\n    },\n    {\n      name: \"Red giants\",\n      data: redGiants,\n      marker: { symbol: \"diamond\", radius: 7 },\n    },\n    {\n      name: \"Supergiants\",\n      data: supergiants,\n      marker: { symbol: \"triangle\", radius: 8 },\n    },\n    {\n      name: \"White dwarfs\",\n      data: whiteDwarfs,\n      marker: { symbol: \"square\", radius: 5 },\n    },\n    {\n      name: \"Sun (reference)\",\n      data: [sun],\n      marker: { symbol: \"circle\", radius: 9 },\n      showInLegend: true,\n    },\n    ...spectralLegend,\n  ],\n});\n"}