Tanabe-Sugano Diagram for Crystal Field Theory in Apache ECharts (JavaScript)

The same plot in 14 other libraries — JavaScript: Chart.js, D3.js, Highcharts, MUI X Charts; Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl. Compare all 15 side by side: Tanabe-Sugano Diagram for Crystal Field Theory in Python, R, Julia and JavaScript.

A Tanabe-Sugano diagram plots the energies of the electronic term states of a transition-metal ion in an octahedral ligand field as continuous functions of the field strength. The x-axis is the reduced ligand-field strength Δ_o/B (octahedral splitting divided by the Racah parameter B) and the y-axis is the reduced term energy E/B measured from the ground term, which therefore runs along the x-axis itself as a horizontal line at E/B = 0. Each term — labeled with its full symbol including spin multiplicity and Mulliken label — is one curve, and for d⁴–d⁷ a vertical line marks the high-spin/low-spin crossover where the ground term changes and every curve kinks. Chemists read the chart in reverse: matching ratios of observed UV-Vis band energies against the curves yields both Δ_o and B for a real complex.

Tanabe-Sugano Diagram for Crystal Field Theory rendered with Apache ECharts

Renders

JavaScript source (Apache ECharts)

// anyplot.ai
// line-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory
// Library: echarts 6.1.0 | JavaScript 22.23.3
// Quality: 91/100 | Created: 2026-10-01
//# anyplot-orientation: square

const t = window.ANYPLOT_TOKENS;

// --- Data: d3 (Cr3+) term energies from the Tanabe-Sugano matrices ----------
// Energies are in units of the Racah parameter B, the field strength is
// Dq/B with the octahedral splitting delta_o = 10 Dq.

const B = 1;
const C = 4.5 * B; // C/B of the classic d3 diagram
const r2 = Math.SQRT2;
const r3 = Math.sqrt(3);

// Symmetric matrix from its diagonal plus the upper-triangle entries.
const symmetric = (diagonal, offDiagonal) => {
  const m = diagonal.map((d, i) => diagonal.map((_, j) => (i === j ? d : 0)));
  offDiagonal.forEach(([i, j, value]) => {
    m[i][j] = value;
    m[j][i] = value;
  });
  return m;
};

// Ascending eigenvalues by cyclic Jacobi rotation — the browser has no
// linear-algebra package, so the sweep is written out.
const eigenvalues = (matrix) => {
  const n = matrix.length;
  const a = matrix.map((row) => row.slice());
  for (let sweep = 0; sweep < 40; sweep += 1) {
    let offSquared = 0;
    for (let p = 0; p < n - 1; p += 1) {
      for (let q = p + 1; q < n; q += 1) offSquared += a[p][q] * a[p][q];
    }
    if (offSquared < 1e-20) break;
    for (let p = 0; p < n - 1; p += 1) {
      for (let q = p + 1; q < n; q += 1) {
        if (a[p][q] === 0) continue;
        const theta = (a[q][q] - a[p][p]) / (2 * a[p][q]);
        const tan =
          Math.sign(theta || 1) /
          (Math.abs(theta) + Math.sqrt(theta * theta + 1));
        const cos = 1 / Math.sqrt(tan * tan + 1);
        const sin = tan * cos;
        for (let k = 0; k < n; k += 1) {
          const kp = a[k][p];
          const kq = a[k][q];
          a[k][p] = cos * kp - sin * kq;
          a[k][q] = sin * kp + cos * kq;
        }
        for (let k = 0; k < n; k += 1) {
          const pk = a[p][k];
          const qk = a[q][k];
          a[p][k] = cos * pk - sin * qk;
          a[q][k] = sin * pk + cos * qk;
        }
      }
    }
  }
  return a.map((row, i) => row[i]).sort((x, y) => x - y);
};

// Term energies at one field strength, measured from the 4A2g ground term.
const termEnergies = (fieldStrength) => {
  const dq = fieldStrength / 10;
  const ground = -12 * dq - 15 * B; // t2g^3, 4A2g

  const quartetT1 = eigenvalues(
    symmetric([-2 * dq - 3 * B, 8 * dq - 12 * B], [[0, 1, 6 * B]]),
  );
  const doubletE = eigenvalues(
    symmetric(
      [
        -12 * dq - 6 * B + 3 * C,
        -2 * dq + 8 * B + 6 * C,
        -2 * dq - B + 3 * C,
        18 * dq - 8 * B + 4 * C,
      ],
      [
        [0, 1, -6 * r2 * B],
        [0, 2, -3 * r2 * B],
        [1, 2, 10 * B],
        [1, 3, r3 * (2 * B + C)],
        [2, 3, 2 * r3 * B],
      ],
    ),
  );
  const doubletT1 = eigenvalues(
    symmetric(
      [
        -12 * dq - 6 * B + 3 * C,
        -2 * dq + 3 * C,
        -2 * dq - 6 * B + 3 * C,
        8 * dq - 6 * B + 3 * C,
        8 * dq - 2 * B + 3 * C,
      ],
      [
        [0, 1, -3 * B],
        [0, 2, 3 * B],
        [0, 4, -2 * r3 * B],
        [1, 2, -3 * B],
        [1, 3, 3 * B],
        [1, 4, 3 * r3 * B],
        [2, 3, -3 * B],
        [2, 4, -r3 * B],
        [3, 4, 2 * r3 * B],
      ],
    ),
  );
  const doubletT2 = eigenvalues(
    symmetric(
      [
        -12 * dq + 5 * C,
        -2 * dq - 6 * B + 3 * C,
        -2 * dq + 4 * B + 3 * C,
        8 * dq + 6 * B + 5 * C,
        8 * dq - 2 * B + 3 * C,
      ],
      [
        [0, 1, -3 * r3 * B],
        [0, 2, -5 * r3 * B],
        [0, 3, 4 * B + 2 * C],
        [0, 4, 2 * B],
        [1, 2, 3 * B],
        [1, 3, -3 * r3 * B],
        [1, 4, -3 * r3 * B],
        [2, 3, -r3 * B],
        [2, 4, r3 * B],
        [3, 4, 10 * B],
      ],
    ),
  );

  return [
    0,
    -2 * dq - 15 * B - ground,
    quartetT1[0] - ground,
    quartetT1[1] - ground,
    doubletE[0] - ground,
    doubletT1[0] - ground,
    doubletT2[0] - ground,
    -2 * dq - 11 * B + 3 * C - ground,
  ];
};

// Spin-allowed terms share the quartet multiplicity of the 4A2g ground term.
const terms = [
  { sup: "4", core: "A", sub: "2g", tail: "", allowed: true, shift: 0 },
  { sup: "4", core: "T", sub: "2g", tail: "", allowed: true, shift: 0 },
  { sup: "4", core: "T", sub: "1g", tail: "(F)", allowed: true, shift: 0 },
  { sup: "4", core: "T", sub: "1g", tail: "(P)", allowed: true, shift: 0 },
  { sup: "2", core: "E", sub: "g", tail: "", allowed: false, shift: 17 },
  { sup: "2", core: "T", sub: "1g", tail: "", allowed: false, shift: -17 },
  { sup: "2", core: "T", sub: "2g", tail: "", allowed: false, shift: 0 },
  { sup: "2", core: "A", sub: "1g", tail: "", allowed: false, shift: 0 },
];

const samples = 321;
const curves = terms.map(() => []);
for (let i = 0; i < samples; i += 1) {
  const fieldStrength = (i * 40) / (samples - 1);
  termEnergies(fieldStrength).forEach((energy, k) =>
    curves[k].push([fieldStrength, energy]),
  );
}

// --- Plot -------------------------------------------------------------------
const chart = echarts.init(document.getElementById("container"));

const axis = {
  type: "value",
  nameLocation: "middle",
  nameTextStyle: { color: t.ink, fontSize: 17 },
  axisLabel: { color: t.inkSoft, fontSize: 15 },
  axisLine: { lineStyle: { color: t.inkSoft, width: 1.5 } },
  axisTick: { show: false },
  splitLine: { lineStyle: { color: t.grid, width: 1 } },
};

chart.setOption({
  animation: false,
  color: t.palette,
  backgroundColor: "transparent",
  title: {
    text: "line-tanabe-sugano · javascript · echarts · anyplot.ai",
    subtext:
      "d³ (Cr³⁺) in an octahedral field · C/B = 4.5\n" +
      "solid = spin-allowed (quartet) · dashed = spin-forbidden (doublet)",
    left: "center",
    top: 18,
    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },
    subtextStyle: { color: t.inkSoft, fontSize: 15, lineHeight: 23 },
  },
  grid: { left: 104, right: 136, top: 158, bottom: 94 },
  xAxis: {
    ...axis,
    min: 0,
    max: 40,
    interval: 5,
    name: "Δₒ / B   ligand-field strength",
    nameGap: 46,
  },
  yAxis: {
    ...axis,
    min: 0,
    max: 90,
    interval: 10,
    name: "E / B   term energy above ground",
    nameGap: 60,
  },
  series: terms.map((term, i) => {
    const color = t.palette[i];
    return {
      type: "line",
      name: `${term.sup}${term.core}${term.sub}${term.tail}`,
      data: curves[i],
      showSymbol: false,
      clip: false,
      lineStyle: {
        color,
        width: term.allowed ? 4.5 : 2.4,
        type: term.allowed ? "solid" : [11, 7],
      },
      endLabel: {
        show: true,
        distance: 12,
        offset: [0, term.shift],
        color,
        fontSize: 20,
        formatter: `{s|${term.sup}}${term.core}{b|${term.sub}}${term.tail}`,
        rich: {
          s: { color, fontSize: 13, verticalAlign: "top" },
          b: { color, fontSize: 13, verticalAlign: "bottom" },
        },
      },
    };
  }),
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/line-tanabe-sugano/echarts/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "line-tanabe-sugano",
  "language": "javascript",
  "library": "echarts",
  "page": "https://anyplot.ai/line-tanabe-sugano/javascript/echarts",
  "hub": "https://anyplot.ai/line-tanabe-sugano",
  "code_json": "https://api.anyplot.ai/specs/line-tanabe-sugano/echarts/code",
  "spec_json": "https://api.anyplot.ai/specs/line-tanabe-sugano",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/echarts/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/echarts/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/echarts/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/echarts/plot-dark.html",
  "quality_score": 91.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Tanabe-Sugano Diagram for Crystal Field Theory on anyplot.ai.

Other implementations