{"spec_id":"energy-level-atomic","library":"muix","language":"javascript","code":"// anyplot.ai\n// energy-level-atomic: Atomic Energy Level Diagram\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-08-25\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useYScale, useDrawingArea } from \"@mui/x-charts/hooks\";\nimport { Box, Typography } from \"@mui/material\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Hydrogen atom (Z = 1) bound-state energies from the Rydberg formula,\n// E_n = -13.6 eV / n^2. Levels n = 1..6 span the \"5-15 levels\" range while\n// keeping the diagram legible.\nconst RYDBERG_EV = 13.6;\nconst levels = [\n  { n: 1, label: \"n = 1 (1s) — ground state\" },\n  { n: 2, label: \"n = 2 (2s, 2p)\" },\n  { n: 3, label: \"n = 3 (3s, 3p, 3d)\" },\n  { n: 4, label: \"n = 4 (4s–4f)\" },\n  { n: 5, label: \"n = 5 (5s–5g)\" },\n  { n: 6, label: \"n = 6 (6s–6h)\" },\n].map((level) => ({ ...level, energy: -RYDBERG_EV / (level.n * level.n) }));\nconst energyOf = (n) => levels.find((level) => level.n === n).energy;\n\nconst LYMAN = t.palette[0]; // brand green — always first categorical series\nconst BALMER = t.palette[1];\nconst ABSORPTION = t.palette[2];\nconst EXCITED_ABSORPTION = t.palette[3];\n\n// Downward arrows = emission (photon out); upward arrows = absorption (photon in).\nconst transitions = [\n  { from: 2, to: 1, x: 2.9, color: LYMAN, group: \"Lyman\", wavelength: \"121.6 nm\" },\n  { from: 3, to: 1, x: 3.44, color: LYMAN, group: \"Lyman\", wavelength: \"102.6 nm\" },\n  { from: 4, to: 1, x: 3.99, color: LYMAN, group: \"Lyman\", wavelength: \"97.3 nm\" },\n  { from: 3, to: 2, x: 4.53, color: BALMER, group: \"Balmer\", wavelength: \"656.3 nm\" },\n  { from: 4, to: 2, x: 5.07, color: BALMER, group: \"Balmer\", wavelength: \"486.1 nm\" },\n  { from: 1, to: 3, x: 5.61, color: ABSORPTION, group: \"Absorption\", wavelength: \"102.6 nm\" },\n  { from: 1, to: 4, x: 6.16, color: ABSORPTION, group: \"Absorption\", wavelength: \"97.3 nm\" },\n  { from: 2, to: 6, x: 6.7, color: EXCITED_ABSORPTION, group: \"Excited absorption\", wavelength: \"410.2 nm\" },\n];\n\nconst LEGEND_ITEMS = [\n  { color: LYMAN, label: \"Lyman series · emission → n = 1 (UV) ↓\" },\n  { color: BALMER, label: \"Balmer series · emission → n = 2 (visible) ↓\" },\n  { color: ABSORPTION, label: \"Ground-state absorption ↑\" },\n  { color: EXCITED_ABSORPTION, label: \"Excited-state absorption (n=2→n=6) ↑\" },\n];\n\n// Levels bunch up near the ionization limit under a raw eV scale, so the\n// vertical axis maps energy through a compressive power transform (still\n// strictly monotonic, so ordering and relative magnitude stay legible) —\n// see the spec's guidance to use a nonlinear scale near convergence.\nconst COMPRESS_POWER = 0.35;\nconst compress = (energyEv) => (energyEv >= 0 ? energyEv : -((-energyEv) ** COMPRESS_POWER));\nconst Y_DOMAIN_MIN = compress(-RYDBERG_EV);\nconst Y_DOMAIN_MAX = 0.3;\nconst Y_TICKS = [0, -1, -3, -6, -13.6];\n\nconst LEVEL_X1 = 2.3;\nconst LEVEL_X2 = 7.1;\nconst X_DOMAIN_MAX = 8.3;\n\n// --- Axis: manual ticks + gridlines in the compressed energy space ---------\nfunction EnergyAxis() {\n  const yScale = useYScale();\n  const { left, top, width, height } = useDrawingArea();\n  return (\n    <g>\n      <line x1={left} x2={left} y1={top} y2={top + height} stroke={t.inkSoft} strokeWidth={1.5} />\n      {Y_TICKS.map((value) => {\n        const y = yScale(compress(value));\n        return (\n          <g key={value}>\n            <line x1={left} x2={left + width} y1={y} y2={y} stroke={t.grid} strokeWidth={1} />\n            <line x1={left - 8} x2={left} y1={y} y2={y} stroke={t.inkSoft} strokeWidth={1.5} />\n            <text x={left - 14} y={y} textAnchor=\"end\" dominantBaseline=\"central\" fontSize={14} fill={t.inkSoft}>\n              {value} eV\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Energy levels: partial-width horizontal lines, labeled both sides -----\nfunction EnergyLevels() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  return (\n    <g>\n      {levels.map((level) => {\n        const y = yScale(compress(level.energy));\n        const x1 = xScale(LEVEL_X1);\n        const x2 = xScale(LEVEL_X2);\n        return (\n          <g key={level.n}>\n            <line x1={x1} x2={x2} y1={y} y2={y} stroke={t.ink} strokeWidth={3} strokeLinecap=\"round\" />\n            <text x={x1 - 16} y={y} textAnchor=\"end\" dominantBaseline=\"central\" fontSize={15} fill={t.ink}>\n              {level.label}\n            </text>\n            <text x={x2 + 16} y={y} textAnchor=\"start\" dominantBaseline=\"central\" fontSize={14} fill={t.inkSoft}>\n              {level.energy.toFixed(2)} eV\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Transitions: color-by-series vertical arrows, wavelength alongside ----\nfunction Transitions() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const arrowColors = [LYMAN, BALMER, ABSORPTION, EXCITED_ABSORPTION];\n  return (\n    <g>\n      <defs>\n        {arrowColors.map((color) => (\n          <marker\n            key={color}\n            id={`arrowhead-${color.replace(\"#\", \"\")}`}\n            viewBox=\"0 0 10 10\"\n            refX={8}\n            refY={5}\n            markerWidth={7}\n            markerHeight={7}\n            orient=\"auto\"\n          >\n            <path d=\"M0,0 L10,5 L0,10 z\" fill={color} />\n          </marker>\n        ))}\n      </defs>\n      {transitions.map((transition, index) => {\n        const x = xScale(transition.x);\n        const y1 = yScale(compress(energyOf(transition.from)));\n        const y2 = yScale(compress(energyOf(transition.to)));\n        const midY = (y1 + y2) / 2;\n        return (\n          <g key={index}>\n            <line\n              x1={x}\n              x2={x}\n              y1={y1}\n              y2={y2}\n              stroke={transition.color}\n              strokeWidth={2.5}\n              markerEnd={`url(#arrowhead-${transition.color.replace(\"#\", \"\")})`}\n            />\n            <text\n              x={x + 15}\n              y={midY}\n              fontSize={13}\n              fill={transition.color}\n              textAnchor=\"middle\"\n              transform={`rotate(-90 ${x + 15} ${midY})`}\n            >\n              {transition.wavelength}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\nconst TITLE_H = 56;\nconst CONTEXT_H = 32;\nconst LEGEND_H = 36;\nconst Y_LABEL_W = 36;\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const chartAreaHeight = height - TITLE_H - CONTEXT_H - LEGEND_H;\n  const chartWidth = width - Y_LABEL_W;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        boxSizing: \"border-box\",\n        display: \"flex\",\n        flexDirection: \"column\",\n        fontFamily: \"'Roboto', 'Helvetica Neue', Arial, sans-serif\",\n      }}\n    >\n      <Box sx={{ height: TITLE_H, display: \"flex\", alignItems: \"center\", pl: \"28px\" }}>\n        <Typography sx={{ color: t.ink, fontSize: 27, fontWeight: 600 }}>\n          energy-level-atomic · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n      <Box sx={{ height: CONTEXT_H, display: \"flex\", alignItems: \"center\", pl: \"28px\" }}>\n        <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>\n          Hydrogen atom (Z = 1), E_n = −13.6 eV / n² · vertical axis uses a compressed scale so levels near the\n          ionization limit stay distinguishable\n        </Typography>\n      </Box>\n      <Box sx={{ height: LEGEND_H, display: \"flex\", alignItems: \"center\", gap: \"24px\", pl: \"28px\" }}>\n        {LEGEND_ITEMS.map((item) => (\n          <Box key={item.label} sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n            <Box sx={{ width: 14, height: 14, bgcolor: item.color, borderRadius: \"3px\", flexShrink: 0 }} />\n            <Typography sx={{ color: t.inkSoft, fontSize: 13 }}>{item.label}</Typography>\n          </Box>\n        ))}\n      </Box>\n\n      <Box sx={{ display: \"flex\", flexDirection: \"row\", height: chartAreaHeight }}>\n        <Box sx={{ width: Y_LABEL_W, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n          <Typography sx={{ fontSize: 16, color: t.ink, whiteSpace: \"nowrap\", transform: \"rotate(-90deg)\" }}>\n            Energy (eV)\n          </Typography>\n        </Box>\n        <ChartContainer\n          width={chartWidth}\n          height={chartAreaHeight}\n          skipAnimation\n          series={[]}\n          xAxis={[{ id: \"x\", min: 0, max: X_DOMAIN_MAX, scaleType: \"linear\" }]}\n          yAxis={[{ id: \"y\", min: Y_DOMAIN_MIN, max: Y_DOMAIN_MAX, scaleType: \"linear\" }]}\n          margin={{ top: 40, right: 24, bottom: 28, left: 64 }}\n        >\n          <EnergyAxis />\n          <ChartsReferenceLine\n            y={0}\n            axisId=\"y\"\n            label=\"Ionization limit (E = 0 eV)\"\n            labelAlign=\"end\"\n            lineStyle={{ stroke: t.amber, strokeWidth: 2, strokeDasharray: \"6 4\" }}\n            labelStyle={{ fill: t.amber, fontSize: 13, fontWeight: 600 }}\n          />\n          <EnergyLevels />\n          <Transitions />\n        </ChartContainer>\n      </Box>\n    </Box>\n  );\n}\n"}