/
45case
/
ptable
Обзор
Документация
Войти
/
45case
/
ptable
Код
Запросы
3
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
dev
src/entities/electrons/lib/buildConfigBlocks.ts
221 строка
6 KB
Olya
Страница Электроны + сайдбар с характеритстиками (Степень окисления, Конфигурация, Развёрнутая, Уровни энергии, ВЗМО)
24 июл 2026, 11:54
24 июл 2026, 11:54
3cf1d48
Код
Авторство
О чём код?
export type OrbitalLetter = "s" | "p" | "d" | "f"; export type ElectronSpinCount = 0 | 1 | 2; export interface ConfigOrbitalBox { /** 1 = One (↑), 2 = Two (↑↓), 0 = empty */ electrons: ElectronSpinCount; isActive: boolean; } export interface ConfigSubshellRow { boxes: ConfigOrbitalBox[]; label: string; n: number; type: OrbitalLetter; } export interface ConfigBlockColumn { key: OrbitalLetter; rows: ConfigSubshellRow[]; } /** Highest principal quantum number shown in ConfigBlocks (1s…7s / 7p). */ export const CONFIG_BLOCK_MAX_N = 7; /** * Vertical grid row for a subshell by principal quantum number n only * (not Madelung / Aufbau energy order). 0 = top (n = maxN), maxN-1 = bottom (n = 1). * CSS grid is 1-based: use `getConfigBlockGridRow(n)`. */ export function getConfigBlockRow(n: number): number { return CONFIG_BLOCK_MAX_N - n; } /** 1-based CSS grid-row: n=7 → 1 (top), n=1 → 7 (bottom). */ export function getConfigBlockGridRow(n: number): number { return getConfigBlockRow(n) + 1; } /** Full orbital grid as on ptable.com ConfigBlocks (rows placed by n via CSS grid). */ export const CONFIG_BLOCK_TEMPLATE: Record<OrbitalLetter, ReadonlyArray<{ n: number; label: string }>> = { s: [ { n: 1, label: "1s" }, { n: 2, label: "2s" }, { n: 3, label: "3s" }, { n: 4, label: "4s" }, { n: 5, label: "5s" }, { n: 6, label: "6s" }, { n: 7, label: "7s" }, ], p: [ { n: 2, label: "2p" }, { n: 3, label: "3p" }, { n: 4, label: "4p" }, { n: 5, label: "5p" }, { n: 6, label: "6p" }, { n: 7, label: "7p" }, ], d: [ { n: 3, label: "3d" }, { n: 4, label: "4d" }, { n: 5, label: "5d" }, { n: 6, label: "6d" }, ], f: [ { n: 4, label: "4f" }, { n: 5, label: "5f" }, ], }; export const CONFIG_BLOCK_ORDER: OrbitalLetter[] = ["s", "p", "d", "f"]; const ORBITAL_BOX_COUNT: Record<OrbitalLetter, number> = { s: 1, p: 3, d: 5, f: 7, }; const SUBSHELL_PATTERN = /(\d+)([spdf])(\d+)/gi; export function getOrbitalBoxCount(type: OrbitalLetter): number { return ORBITAL_BOX_COUNT[type]; } /** Hund + Pauli: ups across orbitals, then downs. */ export function fillSubshellElectronCounts( type: OrbitalLetter, electronCount: number, ): ElectronSpinCount[] { const boxCount = ORBITAL_BOX_COUNT[type]; const maxElectrons = boxCount * 2; const count = Math.max(0, Math.min(electronCount, maxElectrons)); const boxes: ElectronSpinCount[] = Array.from({ length: boxCount }, () => 0); let remaining = count; for (let index = 0; index < boxCount && remaining > 0; index += 1) { boxes[index] = 1; remaining -= 1; } for (let index = 0; index < boxCount && remaining > 0; index += 1) { boxes[index] = 2; remaining -= 1; } return boxes; } export function parseExpandedConfigurationCounts( config: string | null | undefined, ): Map<string, number> { const map = new Map<string, number>(); if (!config?.trim()) { return map; } for (const match of config.matchAll(SUBSHELL_PATTERN)) { const label = `${match[1]}${match[2]!.toLowerCase()}`; const electrons = Number.parseInt(match[3]!, 10); if (!Number.isNaN(electrons)) { map.set(label, electrons); } } return map; } export function buildConfigBlocks( expandedConfiguration: string | null | undefined, activeOrbital?: { l: number; m: number; n: number } | null, ): ConfigBlockColumn[] { const filled = parseExpandedConfigurationCounts(expandedConfiguration); const letterFromL: OrbitalLetter[] = ["s", "p", "d", "f"]; return CONFIG_BLOCK_ORDER.map((type) => { const rows = CONFIG_BLOCK_TEMPLATE[type].map(({ n, label }) => { const electronCount = filled.get(label) ?? 0; const counts = fillSubshellElectronCounts(type, electronCount); const boxes: ConfigOrbitalBox[] = counts.map((electrons, boxIndex) => { let isActive = false; if ( activeOrbital && activeOrbital.n === n && letterFromL[activeOrbital.l] === type ) { const activeIndex = activeOrbital.m + activeOrbital.l; isActive = boxIndex === activeIndex && electrons > 0; } return { electrons, isActive }; }); // Fallback: if quantum missing, mark last occupied box of last occupied subshell later return { n, type, label, boxes }; }); return { key: type, rows }; }).map((column) => { if (activeOrbital) { return column; } // Fallback Active: highest-n occupied subshell in this column is not enough — // mark globally last filled orbital across energy. Simpler: last non-empty box // in the highest occupied subshell of the whole diagram. return column; }); } /** Mark HOAO Active when quantum is absent: last box that has at least one electron in aufbau-ish scan of filled labels. */ export function applyFallbackActive(columns: ConfigBlockColumn[]): ConfigBlockColumn[] { let last: { columnIndex: number; rowIndex: number; boxIndex: number } | null = null; columns.forEach((column, columnIndex) => { column.rows.forEach((row, rowIndex) => { row.boxes.forEach((box, boxIndex) => { if (box.electrons > 0) { last = { columnIndex, rowIndex, boxIndex }; } }); }); }); if (!last) { return columns; } return columns.map((column, columnIndex) => ({ ...column, rows: column.rows.map((row, rowIndex) => ({ ...row, boxes: row.boxes.map((box, boxIndex) => ({ ...box, isActive: columnIndex === last!.columnIndex && rowIndex === last!.rowIndex && boxIndex === last!.boxIndex, })), })), })); } export function buildConfigBlocksForElement( expandedConfiguration: string | null | undefined, quantum: { l: number; m: number; n: number } | null | undefined, ): ConfigBlockColumn[] { const columns = buildConfigBlocks(expandedConfiguration, quantum ?? null); const hasActive = columns.some((column) => column.rows.some((row) => row.boxes.some((box) => box.isActive)), ); if (hasActive || !expandedConfiguration) { return columns; } return applyFallbackActive(columns); }