The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Plotly, plotnine, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. 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: matplotlib 3.11.2 | Python 3.13.15
Quality: 90/100 | Created: 2026-10-01
"""
import os
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D
# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
SPIN_ALLOWED = "#009E73" # Imprint palette position 1 — ALWAYS first series
SPIN_FORBIDDEN = "#C475FD" # Imprint palette position 2
# Data — octahedral Tanabe-Sugano matrices for d² (V³⁺), energies in units of the
# Racah parameter B with the Racah A dropped as an additive constant. Each block
# holds the terms of one symmetry; diagonalizing it gives the avoided crossings.
c_over_b = 4.5
field_strength = np.linspace(0.0, 40.0, 361) # Δ_o / B
flat = np.ones_like(field_strength)
off_e = 2 * np.sqrt(3) * flat
off_a1 = np.sqrt(6) * (2 + c_over_b) * flat
blocks = [
(["$^{3}T_{1g}(F)$", "$^{3}T_{1g}(P)$"], True, [[-5 * flat, 6 * flat], [6 * flat, 4 + field_strength]]),
(["$^{3}T_{2g}$"], True, [[-8 + field_strength]]),
(["$^{3}A_{2g}$"], True, [[-8 + 2 * field_strength]]),
(
["$^{1}E_{g}(D)$", "$^{1}E_{g}(G)$"],
False,
[[(1 + 2 * c_over_b) * flat, off_e], [off_e, 2 * c_over_b + 2 * field_strength]],
),
(
["$^{1}T_{2g}(D)$", "$^{1}T_{2g}(G)$"],
False,
[[(1 + 2 * c_over_b) * flat, off_e], [off_e, 2 * c_over_b + field_strength]],
),
(["$^{1}T_{1g}$"], False, [[4 + 2 * c_over_b + field_strength]]),
(
["$^{1}A_{1g}(G)$", "$^{1}A_{1g}(S)$"],
False,
[[(8 + 4 * c_over_b) * flat, off_a1], [off_a1, 10 + 5 * c_over_b + 2 * field_strength]],
),
]
terms = []
for labels, spin_allowed, rows in blocks:
roots = np.linalg.eigvalsh(np.moveaxis(np.array(rows), -1, 0))
for root, label in zip(roots.T, labels, strict=True):
terms.append((label, spin_allowed, root))
ground_energy = np.min([root for _, _, root in terms], axis=0)
# Plot — square canvas, near-square plot area with a gutter for the term labels
field_max, energy_max = 40.0, 80.0
label_nudge = {"$^{3}T_{1g}(F)$": 2.6, "$^{1}E_{g}(D)$": 2.5, "$^{1}T_{2g}(D)$": -2.5}
fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
fig.subplots_adjust(left=0.095, right=0.975, bottom=0.085, top=0.90)
for label, spin_allowed, root in terms:
reduced_energy = root - ground_energy
ax.plot(
field_strength,
reduced_energy,
color=SPIN_ALLOWED if spin_allowed else SPIN_FORBIDDEN,
linewidth=2.8 if spin_allowed else 1.4,
linestyle="-" if spin_allowed else (0, (5, 3)),
zorder=3,
)
# Term label with a leader line, at the right edge or where the curve leaves the frame
last = np.flatnonzero(reduced_energy <= energy_max)[-1]
if last == field_strength.size - 1:
anchor = (field_max, reduced_energy[-1])
text_at = (field_max + 1.6, reduced_energy[-1] + label_nudge.get(label, 0.0))
ha, va = "left", "center"
else:
anchor = (field_strength[last], energy_max)
text_at = (field_strength[last] - 2.2, energy_max - 0.5)
ha, va = "right", "top"
ax.annotate(
label,
xy=anchor,
xytext=text_at,
ha=ha,
va=va,
fontsize=9,
color=INK,
arrowprops={"arrowstyle": "-", "linewidth": 0.7, "color": INK_SOFT, "shrinkA": 2},
)
# Style
ax.set_xlim(0, 46.5)
ax.set_ylim(0, energy_max)
ax.set_xticks(np.arange(0, 41, 10))
ax.set_yticks(np.arange(0, 81, 20))
ax.set_xlabel("Ligand-Field Strength $\\Delta_o/B$", fontsize=10, color=INK)
ax.set_ylabel("Term Energy $E/B$", fontsize=10, color=INK)
ax.set_title(
"line-tanabe-sugano · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK, pad=26
)
ax.text(
0.5,
1.012,
"$d^{2}$ (V$^{3+}$) in an octahedral field, $C/B$ = 4.5",
transform=ax.transAxes,
ha="center",
va="bottom",
fontsize=10,
color=INK_SOFT,
)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for side in ("left", "bottom"):
ax.spines[side].set_color(INK_SOFT)
ax.set_axisbelow(True)
ax.grid(True, alpha=0.15, linewidth=0.8, color=INK)
handles = [
Line2D([], [], color=SPIN_ALLOWED, linewidth=2.8, label="Spin-allowed (triplet)"),
Line2D([], [], color=SPIN_FORBIDDEN, linewidth=1.4, linestyle=(0, (5, 3)), label="Spin-forbidden (singlet)"),
]
legend = ax.legend(handles=handles, loc="lower right", bbox_to_anchor=(0.865, 0.02), fontsize=8)
legend.get_frame().set_facecolor(ELEVATED_BG)
legend.get_frame().set_edgecolor(INK_SOFT)
plt.setp(legend.get_texts(), color=INK_SOFT)
# Save
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/line-tanabe-sugano/matplotlib/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": "python",
"library": "matplotlib",
"page": "https://anyplot.ai/line-tanabe-sugano/python/matplotlib",
"hub": "https://anyplot.ai/line-tanabe-sugano",
"code_json": "https://api.anyplot.ai/specs/line-tanabe-sugano/matplotlib/code",
"spec_json": "https://api.anyplot.ai/specs/line-tanabe-sugano",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/python/matplotlib/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/line-tanabe-sugano/python/matplotlib/plot-dark.png",
"quality_score": 90.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Tanabe-Sugano Diagram for Crystal Field Theory on anyplot.ai.