{"spec_id":"scatter-hr-diagram","library":"echarts","language":"javascript","code":"// anyplot.ai\n// scatter-hr-diagram: Hertzsprung-Russell Diagram\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-26\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\nconst muted = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\"; // theme-adaptive neutral (Imprint anchor, not exposed via tokens)\n\n// --- Deterministic PRNG (small LCG — the browser has no seeded Math.random) -\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction uniform(min, max) {\n  return min + rand() * (max - min);\n}\n\n// --- Spectral classification from surface temperature (Kelvin) -------------\nfunction spectralType(temp) {\n  if (temp >= 30000) return \"O\";\n  if (temp >= 10000) return \"B\";\n  if (temp >= 7500) return \"A\";\n  if (temp >= 6000) return \"F\";\n  if (temp >= 5200) return \"G\";\n  if (temp >= 3700) return \"K\";\n  return \"M\";\n}\n\n// --- Data: four HR-diagram regions, temperature (K) vs. luminosity (Lsun) --\nconst stars = [];\n\n// Main sequence: diagonal band from hot/luminous down to cool/dim.\nfor (let i = 0; i < 130; i++) {\n  const logTemp = uniform(3.3, 4.55);\n  const logLum = 3.6 * (logTemp - Math.log10(5778)) + uniform(-0.35, 0.35);\n  stars.push({ temp: Math.pow(10, logTemp), lum: Math.pow(10, logLum) });\n}\n\n// Red giants: cool surfaces but far more luminous than the main sequence.\nfor (let i = 0; i < 30; i++) {\n  const logTemp = uniform(Math.log10(3500), Math.log10(5000));\n  const logLum = uniform(1, 3);\n  stars.push({ temp: Math.pow(10, logTemp), lum: Math.pow(10, logLum) });\n}\n\n// Supergiants: wide temperature range, extreme luminosity.\nfor (let i = 0; i < 16; i++) {\n  const logTemp = uniform(Math.log10(3200), Math.log10(24000));\n  const logLum = uniform(4, 5.85);\n  stars.push({ temp: Math.pow(10, logTemp), lum: Math.pow(10, logLum) });\n}\n\n// White dwarfs: hot surfaces but tiny, so very faint.\nfor (let i = 0; i < 26; i++) {\n  const logTemp = uniform(Math.log10(8000), Math.log10(40000));\n  const logLum = uniform(-3.85, -2);\n  stars.push({ temp: Math.pow(10, logTemp), lum: Math.pow(10, logLum) });\n}\n\nstars.forEach((star) => {\n  star.type = spectralType(star.temp);\n});\n\n// --- Color by spectral type, following the conventional stellar palette ----\n// (blue-hot O/B -> near-white A -> yellow F/G -> orange K -> red-cool M).\n// AMBER_PALE is t.amber lightened toward white: F-type stars are white-yellow,\n// one shade paler than G's amber, so the two stay in the same yellow family\n// (per the domain-color semantic exception) while remaining distinguishable.\nconst AMBER_PALE = \"#EDE4B8\";\nconst SPEC_ORDER = [\"O\", \"B\", \"A\", \"F\", \"G\", \"K\", \"M\"];\nconst SPEC_COLOR = {\n  O: t.palette[2], // blue\n  B: t.palette[5], // cyan-blue\n  A: muted, // near-white\n  F: AMBER_PALE, // white-yellow\n  G: t.amber, // yellow, Sun-like\n  K: t.palette[3], // ochre / orange\n  M: t.palette[4], // matte red, coolest\n};\n\nconst seriesByType = SPEC_ORDER.map((type) => ({\n  name: type,\n  type: \"scatter\",\n  symbolSize: 12,\n  itemStyle: { color: SPEC_COLOR[type], opacity: 0.7 },\n  data: stars.filter((star) => star.type === type).map((star) => [star.temp, star.lum]),\n}));\n\n// --- The Sun as a distinct reference point ----------------------------------\nconst sunSeries = {\n  name: \"Sun\",\n  type: \"scatter\",\n  symbol: \"diamond\",\n  symbolSize: 24,\n  itemStyle: { color: t.palette[0], borderColor: t.ink, borderWidth: 1.5 },\n  label: { show: true, formatter: \"Sun\", position: \"top\", color: t.ink, fontSize: 15 },\n  data: [[5778, 1]],\n};\n\n// --- Region labels, positioned via the same log/log domain as the axes -----\n// (echarts markPoint text doesn't survive a log-scaled inverse axis reliably,\n// so the four region captions are placed directly with the graphic component).\nconst GRID = { left: 110, top: 130, right: 70, bottom: 150 };\nconst MOUNT = window.ANYPLOT_SIZE;\nconst X_DOMAIN = [Math.log10(2500), Math.log10(45000)]; // must match xAxis min/max below\nconst Y_DOMAIN = [Math.log10(1e-4), Math.log10(1e6)]; // must match yAxis min/max below\n\nfunction regionLabelPos(temp, lum) {\n  const plotW = MOUNT.width - GRID.left - GRID.right;\n  const plotH = MOUNT.height - GRID.top - GRID.bottom;\n  const fracX = (Math.log10(temp) - X_DOMAIN[0]) / (X_DOMAIN[1] - X_DOMAIN[0]);\n  const fracY = (Math.log10(lum) - Y_DOMAIN[0]) / (Y_DOMAIN[1] - Y_DOMAIN[0]);\n  return {\n    left: GRID.left + (1 - fracX) * plotW, // x-axis is inverse: hot (high temp) sits on the left\n    top: GRID.top + (1 - fracY) * plotH, // luminosity increases upward, graphic \"top\" grows downward\n  };\n}\n\nconst regionLabels = [\n  { text: \"Main sequence\", temp: 11000, lum: 60 },\n  { text: \"Red giants\", temp: 4600, lum: 500 },\n  { text: \"Supergiants\", temp: 14000, lum: 220000 },\n  { text: \"White dwarfs\", temp: 23000, lum: 0.0012 },\n].map((region) => ({\n  type: \"text\",\n  ...regionLabelPos(region.temp, region.lum),\n  style: { text: region.text, fill: muted, fontSize: 16, fontWeight: 500 },\n}));\n\n// --- Secondary spectral-class labels, placed at each class's true log-domain\n// midpoint (an evenly spaced category axis would misalign against the\n// non-uniform temperature boundaries below) -----------------------------------\n// Boundaries mirror spectralType() above; keep both in sync.\nconst SPEC_BOUNDS = {\n  O: [30000, 45000],\n  B: [10000, 30000],\n  A: [7500, 10000],\n  F: [6000, 7500],\n  G: [5200, 6000],\n  K: [3700, 5200],\n  M: [2500, 3700],\n};\n\nfunction specTickLeft(low, high) {\n  const midTemp = Math.sqrt(low * high); // geometric mean = midpoint in log space\n  const plotW = MOUNT.width - GRID.left - GRID.right;\n  const fracX = (Math.log10(midTemp) - X_DOMAIN[0]) / (X_DOMAIN[1] - X_DOMAIN[0]);\n  return GRID.left + (1 - fracX) * plotW; // x-axis is inverse: hot (high temp) sits on the left\n}\n\nconst specLabels = SPEC_ORDER.map((type) => ({\n  type: \"text\",\n  left: specTickLeft(...SPEC_BOUNDS[type]),\n  top: GRID.top - 26,\n  style: { text: type, fill: t.inkSoft, fontSize: 14, fontWeight: 500, align: \"center\" },\n}));\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"scatter-hr-diagram · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink },\n    formatter: (p) =>\n      p.seriesName === \"Sun\"\n        ? p.seriesName\n        : `${p.seriesName}-type<br/>T: ${Math.round(p.value[0])} K<br/>L: ${p.value[1].toFixed(4)} L☉`,\n  },\n  legend: {\n    data: [...SPEC_ORDER, \"Sun\"],\n    bottom: 12,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 16,\n    itemHeight: 12,\n  },\n  grid: { left: 110, right: 70, top: 130, bottom: 150 },\n  xAxis: {\n    type: \"log\",\n    inverse: true,\n    min: 2500,\n    max: 45000,\n    name: \"Surface Temperature (K)\",\n    nameLocation: \"middle\",\n    nameGap: 45,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: (v) => (v >= 1000 ? `${Math.round(v / 1000)}k` : `${v}`),\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"log\",\n    min: 1e-4,\n    max: 1e6,\n    name: \"Luminosity (L☉)\",\n    nameLocation: \"middle\",\n    nameGap: 65,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: (v) => v.toExponential(0) },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [...seriesByType, sunSeries],\n  graphic: [...regionLabels, ...specLabels],\n});\n"}