{"spec_id":"line-tanabe-sugano","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory\n// Library: muix 7.29.1 | JavaScript 22.23.3\n// Quality: 90/100 | Created: 2026-10-01\n//# anyplot-orientation: square\n// anyplot.ai\n// line-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-10-01\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsTooltip } from \"@mui/x-charts/ChartsTooltip\";\nimport { useDrawingArea, useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width: WIDTH, height: HEIGHT } = window.ANYPLOT_SIZE;\nconst MARGIN = { top: 96, right: 172, bottom: 88, left: 96 };\n\n// Data — d³ (Cr³⁺) in an octahedral field at C/B = 4.5, energies in units of B\nconst C_OVER_B = 4.5;\nconst FIELD_MAX = 40;\nconst ENERGY_MAX = 90;\nconst fieldStrengths = Array.from({ length: 321 }, (_, i) => (i * FIELD_MAX) / 320);\n\n// ⁴T₁g is the only Tanabe-Sugano block of d³ bigger than 1×1. In the weak-field\n// basis (⁴F and ⁴P parents), relative to the ⁴A₂g ground term, with d = Δo/B:\n//     [ 1.8d         0.4d  ]\n//     [ 0.4d    15 + 1.2d  ]\n// Both ⁴T₁g curves are eigenvalues of that symmetric matrix, so the avoided\n// crossing is the 12B gap the matrix itself keeps at d = 9 — never a spline.\nconst t1gEigenvalues = (d) => {\n  const [t1gF, t1gP, mixing] = [1.8 * d, 15 + 1.2 * d, 0.4 * d];\n  const centre = (t1gF + t1gP) / 2;\n  const splitting = Math.hypot((t1gF - t1gP) / 2, mixing);\n  return [centre - splitting, centre + splitting];\n};\n\n// Term symbols as [kind, text] runs, typeset below into true super/subscripts.\n// The spin-forbidden doublets both belong to the same t₂g³ configuration as the\n// ⁴A₂g ground term, so their Racah energies carry no first-order Dq term at all\n// and the curves run horizontally — 9B + 3C and 15B + 3C.\nconst TERMS = [\n  { id: \"a2g\", key: \"⁴A₂g\", runs: [[\"sup\", \"4\"], [\"base\", \"A\"], [\"sub\", \"2g\"]],\n    spinAllowed: true, energy: () => 0 },\n  { id: \"t2g\", key: \"⁴T₂g\", runs: [[\"sup\", \"4\"], [\"base\", \"T\"], [\"sub\", \"2g\"]],\n    spinAllowed: true, energy: (d) => d },\n  { id: \"t1gF\", key: \"⁴T₁g(F)\", runs: [[\"sup\", \"4\"], [\"base\", \"T\"], [\"sub\", \"1g\"], [\"base\", \"(F)\"]],\n    spinAllowed: true, energy: (d) => t1gEigenvalues(d)[0] },\n  { id: \"t1gP\", key: \"⁴T₁g(P)\", runs: [[\"sup\", \"4\"], [\"base\", \"T\"], [\"sub\", \"1g\"], [\"base\", \"(P)\"]],\n    spinAllowed: true, energy: (d) => t1gEigenvalues(d)[1] },\n  { id: \"eg\", key: \"²Eg\", runs: [[\"sup\", \"2\"], [\"base\", \"E\"], [\"sub\", \"g\"]],\n    spinAllowed: false, energy: () => 9 + 3 * C_OVER_B },\n  { id: \"t2gDoublet\", key: \"²T₂g\", runs: [[\"sup\", \"2\"], [\"base\", \"T\"], [\"sub\", \"2g\"]],\n    spinAllowed: false, energy: () => 15 + 3 * C_OVER_B },\n];\n\n// [Cr(H₂O)₆]³⁺: Δo = 17 400 cm⁻¹ against B = 700 cm⁻¹ — where a chemist reads\n// the diagram off after matching two observed UV-Vis bands.\nconst EXAMPLE_FIELD = 17400 / 700;\n\n// Typography — the library's own markup is SVG tspans, so multiplicities become\n// real superscripts and the numeral plus the g a real subscript.\nconst SHIFT = { base: 0, sup: -0.42, sub: 0.26 };\nconst typeset = (runs, size) => {\n  let offset = 0;\n  return runs.map(([kind, text], i) => {\n    const dy = (SHIFT[kind] - offset) * size;\n    offset = SHIFT[kind];\n    return (\n      <tspan key={i} dy={dy} fontSize={kind === \"base\" ? size : size * 0.66}>\n        {text}\n      </tspan>\n    );\n  });\n};\n\n// Curve labels sit just outside the right edge at each term's final energy, with\n// a greedy vertical declutter so converging labels stay readable (the curves\n// themselves never move off their computed energies).\nfunction TermLabels() {\n  const { left, width } = useDrawingArea();\n  const yScale = useYScale();\n  let lowest = -Infinity;\n  return (\n    <g>\n      {TERMS.map((term, i) => ({ term, color: t.palette[i], y: yScale(term.energy(FIELD_MAX)) }))\n        .sort((a, b) => a.y - b.y)\n        .map(({ term, color, y }) => {\n          const placed = Math.max(y, lowest + 32);\n          lowest = placed;\n          return (\n            <text\n              key={term.id}\n              x={left + width + 14}\n              y={placed}\n              fill={color}\n              fontSize={20}\n              fontWeight={term.spinAllowed ? 600 : 500}\n              dominantBaseline=\"middle\"\n            >\n              {typeset(term.runs, 20)}\n            </text>\n          );\n        })}\n    </g>\n  );\n}\n\n// The marker the Notes offer as optional: a vertical line at the Δo/B a chemist\n// reads off for [Cr(H₂O)₆]³⁺, with its two observed d–d bands as arrows running\n// from the ground term up to the terms they reach.\nfunction ExampleComplex() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const x = xScale(EXAMPLE_FIELD);\n  const bands = [\n    { id: \"nu1\", runs: [[\"base\", \"ν\"], [\"sub\", \"1\"]], term: TERMS[1], dx: -12 },\n    { id: \"nu2\", runs: [[\"base\", \"ν\"], [\"sub\", \"2\"]], term: TERMS[2], dx: 12 },\n  ];\n  return (\n    <g>\n      <defs>\n        <marker id=\"band-arrow\" markerUnits=\"userSpaceOnUse\" markerWidth={14} markerHeight={14}\n                refX={7} refY={7} orient=\"auto\">\n          <path d=\"M0,0 L14,7 L0,14 z\" fill={t.inkSoft} />\n        </marker>\n      </defs>\n      {bands.map(({ id, runs, term, dx }) => (\n        <g key={id}>\n          <line\n            x1={x + dx} y1={yScale(0)} x2={x + dx} y2={yScale(term.energy(EXAMPLE_FIELD)) + 7}\n            stroke={t.inkSoft} strokeWidth={2} markerEnd=\"url(#band-arrow)\"\n          />\n          <text\n            x={x + dx + Math.sign(dx) * 9} y={(yScale(0) + yScale(term.energy(EXAMPLE_FIELD))) / 2}\n            fill={t.inkSoft} fontSize={17} textAnchor={dx < 0 ? \"end\" : \"start\"} dominantBaseline=\"middle\"\n          >\n            {typeset(runs, 17)}\n          </text>\n        </g>\n      ))}\n      <text x={0} y={0} fill={t.inkSoft} fontSize={15} textAnchor=\"end\"\n            transform={`translate(${x - 12} ${yScale(ENERGY_MAX) + 16}) rotate(-90)`}>\n        {typeset([[\"base\", \"[Cr(H\"], [\"sub\", \"2\"], [\"base\", \"O)\"], [\"sub\", \"6\"], [\"base\", \"]\"], [\"sup\", \"3+\"], [\"base\", \" · Δ\"], [\"sub\", \"o\"], [\"base\", \" / B = 24.9\"]], 15)}\n      </text>\n    </g>\n  );\n}\n\n// Chart\nexport default function Chart() {\n  return (\n    <ChartContainer\n      width={WIDTH}\n      height={HEIGHT}\n      margin={MARGIN}\n      colors={t.palette}\n      series={TERMS.map((term) => ({\n        type: \"line\",\n        id: term.id,\n        label: term.key,\n        showMark: false,\n        curve: \"linear\",\n        data: fieldStrengths.map(term.energy),\n      }))}\n      xAxis={[{ id: \"field\", data: fieldStrengths, scaleType: \"linear\", min: 0, max: FIELD_MAX, tickNumber: 9 }]}\n      yAxis={[{ id: \"energy\", min: 0, max: ENERGY_MAX, tickNumber: 9 }]}\n      sx={Object.fromEntries(\n        TERMS.map((term) => [\n          `& .MuiLineElement-series-${term.id}`,\n          term.spinAllowed\n            ? { strokeWidth: 4.2 }\n            : { strokeWidth: 2.4, strokeDasharray: \"13 8\" },\n        ]),\n      )}\n    >\n      <ChartsGrid vertical horizontal />\n      <ChartsReferenceLine\n        x={EXAMPLE_FIELD}\n        axisId=\"field\"\n        lineStyle={{ stroke: t.inkSoft, strokeWidth: 2, strokeDasharray: \"6 7\" }}\n      />\n      <LinePlot skipAnimation />\n      <ChartsXAxis axisId=\"field\" tickLabelStyle={{ fontSize: 15, fill: t.inkSoft }} tickSize={7} />\n      <ChartsYAxis axisId=\"energy\" tickLabelStyle={{ fontSize: 15, fill: t.inkSoft }} tickSize={7} />\n      <ExampleComplex />\n      <TermLabels />\n      <ChartsTooltip trigger=\"axis\" />\n\n      {/* Title, subtitle and axis titles — SVG text so every term symbol and\n          formula keeps its super- and subscripts. */}\n      <text x={MARGIN.left + (WIDTH - MARGIN.left - MARGIN.right) / 2} y={40} fill={t.ink}\n            fontSize={22} fontWeight={600} textAnchor=\"middle\">\n        line-tanabe-sugano · javascript · muix · anyplot.ai\n      </text>\n      <text x={MARGIN.left + (WIDTH - MARGIN.left - MARGIN.right) / 2} y={70} fill={t.inkSoft}\n            fontSize={15} textAnchor=\"middle\">\n        {typeset([[\"base\", \"d\"], [\"sup\", \"3\"], [\"base\", \" (Cr\"], [\"sup\", \"3+\"],\n          [\"base\", `) in an octahedral field · C/B = ${C_OVER_B} · spin-allowed quartets solid, spin-forbidden doublets dashed`]], 15)}\n      </text>\n      <text x={MARGIN.left + (WIDTH - MARGIN.left - MARGIN.right) / 2} y={HEIGHT - 26} fill={t.ink}\n            fontSize={17} textAnchor=\"middle\">\n        {typeset([[\"base\", \"Ligand-field strength Δ\"], [\"sub\", \"o\"], [\"base\", \" / B\"]], 17)}\n      </text>\n      <text x={30} y={MARGIN.top + (HEIGHT - MARGIN.top - MARGIN.bottom) / 2} fill={t.ink}\n            fontSize={17} textAnchor=\"middle\"\n            transform={`rotate(-90 30 ${MARGIN.top + (HEIGHT - MARGIN.top - MARGIN.bottom) / 2})`}>\n        Term energy E / B\n      </text>\n    </ChartContainer>\n  );\n}\n"}