The same plot in 14 other libraries — JavaScript: D3.js, Apache ECharts, 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.

// anyplot.ai
// line-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory
// Library: chartjs 4.4.7 | JavaScript 22.23.3
// Quality: 90/100 | Created: 2026-10-01
//# anyplot-orientation: square
const t = window.ANYPLOT_TOKENS;
// --- Data ------------------------------------------------------------------
// d² (V³⁺) in an octahedral field at C/B = 4.42. Term energies come from the
// Tanabe-Sugano matrices for d², in units of B with the Racah A dropped and
// Dq = Δo/10: every cubic term is a 1x1 or 2x2 block that is diagonalised
// exactly, so terms of the same symmetry and multiplicity (the two ³T₁g, the
// ¹Eg/¹T₂g pairs) stay as avoided crossings instead of crossing.
const RACAH_C = 4.42;
const eigenvalues = (upper, lower, coupling) => {
const mean = (upper + lower) / 2;
const half = Math.hypot((upper - lower) / 2, coupling);
return [mean - half, mean + half];
};
// ³T₁g: t₂g²(-5B) and t₂g¹eg¹(+4B) mix through 6B; its lower root is the
// ground term for every field strength, so d² has no high-spin/low-spin
// crossover and the diagram needs no crossover line.
const groundTerm = (dq) => eigenvalues(-5 - 8 * dq, 4 + 2 * dq, 6)[0];
const terms = [
{ label: "³T₁g(F)", spinAllowed: true, energy: groundTerm },
{ label: "³T₂g", spinAllowed: true, energy: (dq) => -8 + 2 * dq },
{ label: "³T₁g(P)", spinAllowed: true, energy: (dq) => eigenvalues(-5 - 8 * dq, 4 + 2 * dq, 6)[1] },
{ label: "³A₂g", spinAllowed: true, energy: (dq) => -8 + 12 * dq },
{
label: "¹Eg",
spinAllowed: false,
energy: (dq) => eigenvalues(1 + 2 * RACAH_C - 8 * dq, 2 * RACAH_C + 12 * dq, 2 * Math.sqrt(3))[0],
},
{
label: "¹T₂g",
spinAllowed: false,
energy: (dq) => eigenvalues(1 + 2 * RACAH_C - 8 * dq, 2 * RACAH_C + 2 * dq, 2 * Math.sqrt(3))[0],
},
{ label: "¹T₁g", spinAllowed: false, energy: (dq) => 4 + 2 * RACAH_C + 2 * dq },
{
label: "¹A₁g",
spinAllowed: false,
energy: (dq) =>
eigenvalues(10 + 5 * RACAH_C - 8 * dq, 8 + 4 * RACAH_C + 12 * dq, Math.sqrt(6) * (2 + RACAH_C))[0],
},
];
// Δo/B from 0 to 40 in 321 steps — dense enough to keep avoided crossings smooth
const fieldStrengths = Array.from({ length: 321 }, (_, i) => i / 8);
// Imprint palette positions 1-8 in canonical order; spin-allowed terms (the ones
// sharing the ground term's triplet multiplicity, so the strong absorptions) are
// thick and solid, spin-forbidden singlets thin and dashed.
const datasets = terms.map((term, i) => ({
label: term.label,
data: fieldStrengths.map((fieldStrength) => ({
x: fieldStrength,
y: term.energy(fieldStrength / 10) - groundTerm(fieldStrength / 10),
})),
borderColor: t.palette[i],
borderWidth: term.spinAllowed ? 6 : 3,
borderDash: term.spinAllowed ? [] : [13, 10],
pointRadius: 0,
// the flat ground term runs along the x axis — let its full stroke draw
clip: 8,
}));
// --- Mount -----------------------------------------------------------------
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);
// Term symbols sit at the right end of their own curve instead of in a legend,
// which keeps eight curves readable; labels are nudged apart vertically where
// curves converge (¹Eg and ¹T₂g end ~0.2 E/B apart).
const termLabels = {
id: "termLabels",
afterDatasetsDraw(chart) {
const { ctx, chartArea, scales } = chart;
const labels = chart.data.datasets
.map((dataset) => ({
text: dataset.label,
color: dataset.borderColor,
y: scales.y.getPixelForValue(dataset.data[dataset.data.length - 1].y),
}))
.sort((a, b) => a.y - b.y);
labels.forEach((label, i) => {
if (i > 0) label.y = Math.max(label.y, labels[i - 1].y + 28);
});
ctx.save();
ctx.font = "600 20px Helvetica, Arial, sans-serif";
ctx.textBaseline = "middle";
labels.forEach((label) => {
ctx.fillStyle = label.color;
ctx.fillText(label.text, chartArea.right + 14, label.y);
});
ctx.restore();
},
};
// --- Chart -----------------------------------------------------------------
new Chart(canvas, {
type: "line",
data: { datasets },
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
layout: { padding: { right: 120, top: 6, bottom: 6, left: 6 } },
plugins: {
title: {
display: true,
text: "line-tanabe-sugano · javascript · chartjs · anyplot.ai",
color: t.ink,
font: { size: 26, weight: "500" },
padding: { bottom: 4 },
},
subtitle: {
display: true,
text: "d² (V³⁺) in Oₕ, C/B = 4.42 · solid: spin-allowed triplets · dashed: spin-forbidden singlets",
color: t.inkSoft,
font: { size: 18 },
padding: { bottom: 20 },
},
legend: { display: false },
},
scales: {
x: {
type: "linear",
min: 0,
max: 40,
title: { display: true, text: "Δₒ/B — reduced ligand-field strength", color: t.ink, font: { size: 19 } },
ticks: { color: t.inkSoft, font: { size: 16 }, stepSize: 5, padding: 8 },
grid: { color: t.grid, tickLength: 0 },
border: { color: t.grid },
},
y: {
min: 0,
max: 80,
title: { display: true, text: "E/B — term energy above ground term", color: t.ink, font: { size: 19 } },
ticks: { color: t.inkSoft, font: { size: 16 }, stepSize: 10, padding: 8 },
grid: { color: t.grid, tickLength: 0 },
border: { color: t.grid },
},
},
},
plugins: [termLabels],
});
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/line-tanabe-sugano/chartjs/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": "chartjs",
"page": "https://anyplot.ai/line-tanabe-sugano/javascript/chartjs",
"hub": "https://anyplot.ai/line-tanabe-sugano",
"code_json": "https://api.anyplot.ai/specs/line-tanabe-sugano/chartjs/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/chartjs/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/chartjs/plot-dark.png",
"interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/chartjs/plot-light.html",
"interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/javascript/chartjs/plot-dark.html",
"quality_score": 90.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Tanabe-Sugano Diagram for Crystal Field Theory on anyplot.ai.