fix(phys-fx): EnergyLevels — разрывная шкала, верхняя зона n=2..6 растянута, маркер разрыва оси
This commit is contained in:
+80
-71
@@ -2025,119 +2025,128 @@ class EnergyLevels {
|
||||
update(){}
|
||||
render(){
|
||||
if (!this.el) return;
|
||||
const W = this.W, H = this.H;
|
||||
const padTop = 36, padBot = 30;
|
||||
const W = this.W;
|
||||
/* Внутренняя высота увеличена для читаемости */
|
||||
const H = Math.max(this.H, 440);
|
||||
const padTop = 36, padBot = 32;
|
||||
const bandW = 16, bandLeft = 4;
|
||||
const leftLine = 170, rightLine = W - 80;
|
||||
const leftLine = 170, rightLine = W - 82;
|
||||
const nMax = 6;
|
||||
const nFrom = this.n_from, nTo = this.n_to;
|
||||
|
||||
function E(n){ return -13.6 / (n * n); }
|
||||
|
||||
/* Разрывная шкала:
|
||||
— верхняя зона (70% высоты): n=2..6 + ионизация, линейная по E
|
||||
— нижняя зона (30% высоты): только n=1, сжата с маркером разрыва */
|
||||
const upperH = Math.round((H - padTop - padBot) * 0.72);
|
||||
const splitY = padTop + upperH; /* граница зон в пикселях */
|
||||
const y1fixed = H - padBot - 16; /* фиксированная y для n=1 */
|
||||
const E2 = E(2), maxE = 0.4;
|
||||
|
||||
function yE(En){
|
||||
const minE = -14.2, maxE = 0.4;
|
||||
return padTop + (maxE - En) / (maxE - minE) * (H - padTop - padBot);
|
||||
if (En <= E2 - 0.05){
|
||||
/* Нижняя зона: линейная E(1)→E(2) → y1fixed→splitY */
|
||||
const t = (En - E(1)) / (E2 - E(1));
|
||||
return y1fixed - t * (y1fixed - splitY);
|
||||
}
|
||||
/* Верхняя зона: линейная E(2)→maxE → splitY→padTop */
|
||||
return padTop + (maxE - En) / (maxE - E2) * upperH;
|
||||
}
|
||||
|
||||
/* SVG открытие: белый фон, рамка */
|
||||
/* SVG */
|
||||
let svg = '<svg width="' + W + '" height="' + H + '" viewBox="0 0 ' + W + ' ' + H + '" xmlns="http://www.w3.org/2000/svg">';
|
||||
svg += '<rect x="0" y="0" width="' + W + '" height="' + H + '" rx="14" fill="#fff" stroke="#e2e8f0" stroke-width="1.2"/>';
|
||||
|
||||
/* Заголовок */
|
||||
svg += '<text x="' + (W / 2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">Eₙ = −13,6/n² эВ</text>';
|
||||
svg += '<text x="' + (W / 2) + '" y="23" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11.5" fill="#64748b" font-weight="600">Eₙ = −13,6 / n² эВ (атом водорода)</text>';
|
||||
|
||||
/* Серии — цветные полосы между уровнями */
|
||||
const seriesBands = [
|
||||
{ n1: 1, n2: 2, fill: '#ede9fe', textFill: '#6d28d9', label: 'Лайман' },
|
||||
{ n1: 2, n2: 3, fill: '#dbeafe', textFill: '#1d4ed8', label: 'Бальмер' },
|
||||
{ n1: 3, n2: 6, fill: '#d1fae5', textFill: '#065f46', label: 'Пашен' },
|
||||
/* Цветные полосы серий */
|
||||
const seriesData = [
|
||||
{ yTop: yE(E2), yBot: y1fixed, fill: '#ede9fe', tc: '#6d28d9', label: 'Лайман' },
|
||||
{ yTop: yE(E(3)), yBot: yE(E2), fill: '#dbeafe', tc: '#1d4ed8', label: 'Бальмер' },
|
||||
{ yTop: padTop, yBot: yE(E(3)),fill: '#d1fae5', tc: '#065f46', label: 'Пашен' },
|
||||
];
|
||||
for (const b of seriesBands){
|
||||
const yTop = yE(E(b.n2));
|
||||
const yBot = yE(E(b.n1));
|
||||
const bx = bandLeft;
|
||||
const bh = yBot - yTop;
|
||||
svg += '<rect x="' + bx + '" y="' + yTop.toFixed(1) + '" width="' + bandW + '" height="' + bh.toFixed(1) + '" rx="3" fill="' + b.fill + '"/>';
|
||||
/* Вертикальная метка внутри полосы */
|
||||
const ymid = ((yTop + yBot) / 2).toFixed(1);
|
||||
svg += '<text x="' + (bx + bandW / 2) + '" y="' + ymid + '" text-anchor="middle" dominant-baseline="middle" font-family="JetBrains Mono,monospace" font-size="8.5" fill="' + b.textFill + '" font-weight="700" transform="rotate(-90,' + (bx + bandW / 2) + ',' + ymid + ')">' + b.label + '</text>';
|
||||
for (const b of seriesData){
|
||||
const bh = b.yBot - b.yTop;
|
||||
if (bh < 6) continue;
|
||||
svg += '<rect x="' + bandLeft + '" y="' + b.yTop.toFixed(1) + '" width="' + bandW + '" height="' + bh.toFixed(1) + '" rx="3" fill="' + b.fill + '"/>';
|
||||
const ym = ((b.yTop + b.yBot) / 2).toFixed(1);
|
||||
const bx2 = bandLeft + bandW / 2;
|
||||
svg += '<text x="' + bx2 + '" y="' + ym + '" text-anchor="middle" dominant-baseline="middle" font-family="JetBrains Mono,monospace" font-size="8.5" fill="' + b.tc + '" font-weight="700" transform="rotate(-90,' + bx2 + ',' + ym + ')">' + b.label + '</text>';
|
||||
}
|
||||
|
||||
/* Вертикальная ось слева от уровней */
|
||||
/* Вертикальная ось — верхняя часть */
|
||||
const axisX = leftLine - 3;
|
||||
svg += '<line x1="' + axisX + '" y1="' + padTop + '" x2="' + axisX + '" y2="' + (H - padBot) + '" stroke="#cbd5e1" stroke-width="1"/>';
|
||||
svg += '<line x1="' + axisX + '" y1="' + padTop + '" x2="' + axisX + '" y2="' + (splitY - 6) + '" stroke="#cbd5e1" stroke-width="1"/>';
|
||||
/* Ось нижней части */
|
||||
svg += '<line x1="' + axisX + '" y1="' + (splitY + 22) + '" x2="' + axisX + '" y2="' + (H - padBot) + '" stroke="#cbd5e1" stroke-width="1"/>';
|
||||
|
||||
/* Линия ионизации E=0 */
|
||||
/* Маркер разрыва оси */
|
||||
const bMid = (splitY + splitY + 22) / 2;
|
||||
for (const dy of [-5, 5]){
|
||||
const my = bMid + dy;
|
||||
svg += '<line x1="' + (axisX - 5) + '" y1="' + (my - 5) + '" x2="' + (axisX + 5) + '" y2="' + (my + 5) + '" stroke="#94a3b8" stroke-width="1.4" stroke-linecap="round"/>';
|
||||
}
|
||||
/* Подпись разрыва */
|
||||
svg += '<text x="' + (axisX + 8) + '" y="' + (bMid + 4) + '" font-family="JetBrains Mono,monospace" font-size="8" fill="#94a3b8">∿ масштаб</text>';
|
||||
|
||||
/* Линия ионизации */
|
||||
const y0 = yE(0);
|
||||
svg += '<line x1="' + leftLine + '" y1="' + y0.toFixed(1) + '" x2="' + rightLine + '" y2="' + y0.toFixed(1) + '" stroke="#dc2626" stroke-width="1.4" stroke-dasharray="5 3"/>';
|
||||
svg += '<text x="' + (leftLine + 4) + '" y="' + (y0 - 3).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="9" fill="#dc2626" font-weight="700">E = 0 (ионизация)</text>';
|
||||
svg += '<line x1="' + leftLine + '" y1="' + y0.toFixed(1) + '" x2="' + rightLine + '" y2="' + y0.toFixed(1) + '" stroke="#ef4444" stroke-width="1.4" stroke-dasharray="6 3"/>';
|
||||
svg += '<text x="' + (leftLine + 5) + '" y="' + (y0 - 4).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="9" fill="#ef4444" font-weight="600">E = 0 (ионизация)</text>';
|
||||
|
||||
/* Уровни n=1..6 */
|
||||
for (let n = 1; n <= nMax; n++){
|
||||
const En = E(n);
|
||||
const yL = yE(En);
|
||||
const isFrom = (n === nFrom);
|
||||
const isTo = (n === nTo);
|
||||
const isFrom = (n === nFrom), isTo = (n === nTo);
|
||||
const active = isFrom || isTo;
|
||||
const lineColor = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#94a3b8';
|
||||
const lc = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#94a3b8';
|
||||
const sw = active ? 2.4 : 1;
|
||||
|
||||
/* Glow под активными */
|
||||
if (active){
|
||||
svg += '<line x1="' + leftLine + '" y1="' + yL.toFixed(1) + '" x2="' + rightLine + '" y2="' + yL.toFixed(1) + '" stroke="' + lineColor + '" stroke-width="7" opacity="0.08"/>';
|
||||
svg += '<line x1="' + leftLine + '" y1="' + yL.toFixed(1) + '" x2="' + rightLine + '" y2="' + yL.toFixed(1) + '" stroke="' + lc + '" stroke-width="8" opacity="0.07"/>';
|
||||
}
|
||||
svg += '<line x1="' + leftLine + '" y1="' + yL.toFixed(1) + '" x2="' + rightLine + '" y2="' + yL.toFixed(1) + '" stroke="' + lineColor + '" stroke-width="' + sw + '"/>';
|
||||
|
||||
/* Метка n= слева от оси */
|
||||
const labelX = axisX - 4;
|
||||
const labelY = yL.toFixed(1);
|
||||
const labelColor = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#475569';
|
||||
const labelFW = active ? '800' : '500';
|
||||
svg += '<line x1="' + leftLine + '" y1="' + yL.toFixed(1) + '" x2="' + rightLine + '" y2="' + yL.toFixed(1) + '" stroke="' + lc + '" stroke-width="' + sw + '"/>';
|
||||
/* Метка n= */
|
||||
const lx = axisX - 4;
|
||||
const textC = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#475569';
|
||||
if (active){
|
||||
svg += '<rect x="' + (labelX - 24) + '" y="' + (yL - 8).toFixed(1) + '" width="26" height="14" rx="3" fill="' + lineColor + '" opacity="0.12"/>';
|
||||
svg += '<rect x="' + (lx - 24) + '" y="' + (yL - 8).toFixed(1) + '" width="26" height="15" rx="3" fill="' + lc + '" opacity="0.12"/>';
|
||||
}
|
||||
svg += '<text x="' + labelX + '" y="' + (yL + 4).toFixed(1) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="' + labelColor + '" font-weight="' + labelFW + '">n=' + n + '</text>';
|
||||
|
||||
/* Значения энергии справа */
|
||||
const eColor = active ? '#334155' : '#94a3b8';
|
||||
const eFW = active ? '600' : '400';
|
||||
svg += '<text x="' + (rightLine + 5) + '" y="' + (yL + 4).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="9.5" fill="' + eColor + '" font-weight="' + eFW + '">' + En.toFixed(2) + 'эВ</text>';
|
||||
svg += '<text x="' + lx + '" y="' + (yL + 4).toFixed(1) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="' + textC + '" font-weight="' + (active ? '800' : '500') + '">n=' + n + '</text>';
|
||||
/* Значение энергии */
|
||||
const ec = active ? '#334155' : '#94a3b8';
|
||||
svg += '<text x="' + (rightLine + 5) + '" y="' + (yL + 4).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="9.5" fill="' + ec + '" font-weight="' + (active ? '600' : '400') + '">' + En.toFixed(2) + ' эВ</text>';
|
||||
}
|
||||
|
||||
/* Стрелка перехода */
|
||||
if (nFrom !== nTo){
|
||||
const Ef = E(nFrom), Et = E(nTo);
|
||||
const yf = yE(Ef), yt = yE(Et);
|
||||
const emission = Ef > Et; /* испускание: падаем вниз по энергии */
|
||||
const arrowColor = emission ? '#ef4444' : '#16a34a';
|
||||
const emission = Ef > Et;
|
||||
const ac = emission ? '#ef4444' : '#16a34a';
|
||||
const xT = leftLine + (rightLine - leftLine) * 0.42;
|
||||
|
||||
/* Линия стрелки */
|
||||
svg += '<line x1="' + xT.toFixed(1) + '" y1="' + yf.toFixed(1) + '" x2="' + xT.toFixed(1) + '" y2="' + yt.toFixed(1) + '" stroke="' + arrowColor + '" stroke-width="2.2"/>';
|
||||
|
||||
/* Наконечник в точке назначения yt */
|
||||
const dir = yt < yf ? -1 : 1; /* направление стрелки */
|
||||
const tip = yt.toFixed(1);
|
||||
const tipBase = (yt + 10 * dir).toFixed(1);
|
||||
svg += '<polygon points="' + xT.toFixed(1) + ',' + tip + ' ' + (xT - 6).toFixed(1) + ',' + tipBase + ' ' + (xT + 6).toFixed(1) + ',' + tipBase + '" fill="' + arrowColor + '"/>';
|
||||
|
||||
svg += '<line x1="' + xT.toFixed(1) + '" y1="' + yf.toFixed(1) + '" x2="' + xT.toFixed(1) + '" y2="' + yt.toFixed(1) + '" stroke="' + ac + '" stroke-width="2.4" stroke-linecap="round"/>';
|
||||
const dir = yt < yf ? -1 : 1;
|
||||
svg += '<polygon points="' + xT + ',' + yt.toFixed(1) + ' ' + (xT - 7) + ',' + (yt + 11 * dir).toFixed(1) + ' ' + (xT + 7) + ',' + (yt + 11 * dir).toFixed(1) + '" fill="' + ac + '"/>';
|
||||
/* Info-box */
|
||||
const dE = Math.abs(Ef - Et);
|
||||
const lam = 1240 / dE;
|
||||
const boxW = 88, boxH = lam >= 380 && lam <= 700 ? 48 : 40;
|
||||
const bxW = 94, bxH = lam >= 380 && lam <= 700 ? 50 : 42;
|
||||
const yMid = (yf + yt) / 2;
|
||||
let bx = xT + 10;
|
||||
if (bx + boxW > W - 4) bx = xT - boxW - 10;
|
||||
const boxFill = emission ? '#fff5f5' : '#f0fdf4';
|
||||
const boxStroke = emission ? '#fca5a5' : '#86efac';
|
||||
svg += '<rect x="' + bx.toFixed(1) + '" y="' + (yMid - boxH / 2).toFixed(1) + '" width="' + boxW + '" height="' + boxH + '" rx="6" fill="' + boxFill + '" stroke="' + boxStroke + '" stroke-width="1.2"/>';
|
||||
svg += '<text x="' + (bx + 7).toFixed(1) + '" y="' + (yMid - boxH / 2 + 15).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11.5" fill="' + arrowColor + '" font-weight="800">hν = ' + dE.toFixed(3) + ' эВ</text>';
|
||||
svg += '<text x="' + (bx + 7).toFixed(1) + '" y="' + (yMid - boxH / 2 + 29).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="' + arrowColor + '">λ ≈ ' + lam.toFixed(0) + ' нм</text>';
|
||||
/* Цветовая метка для видимого диапазона */
|
||||
let bxLeft = xT + 12;
|
||||
if (bxLeft + bxW > W - 4) bxLeft = xT - bxW - 12;
|
||||
const bf = emission ? '#fff5f5' : '#f0fdf4';
|
||||
const bs = emission ? '#fca5a5' : '#86efac';
|
||||
svg += '<rect x="' + bxLeft.toFixed(1) + '" y="' + (yMid - bxH / 2).toFixed(1) + '" width="' + bxW + '" height="' + bxH + '" rx="7" fill="' + bf + '" stroke="' + bs + '" stroke-width="1.2"/>';
|
||||
svg += '<text x="' + (bxLeft + 8).toFixed(1) + '" y="' + (yMid - bxH / 2 + 16).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11.5" fill="' + ac + '" font-weight="800">hν = ' + dE.toFixed(3) + ' эВ</text>';
|
||||
svg += '<text x="' + (bxLeft + 8).toFixed(1) + '" y="' + (yMid - bxH / 2 + 31).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="' + ac + '">λ ≈ ' + lam.toFixed(0) + ' нм</text>';
|
||||
if (lam >= 380 && lam <= 700){
|
||||
const hue = Math.round(270 - (lam - 380) / (700 - 380) * 270);
|
||||
const cy = yMid - boxH / 2 + 39;
|
||||
svg += '<defs><linearGradient id="vis_g" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="hsl(' + hue + ',90%,55%)"/><stop offset="100%" stop-color="hsl(' + (hue - 20) + ',90%,60%)"/></linearGradient></defs>';
|
||||
svg += '<rect x="' + (bx + 7).toFixed(1) + '" y="' + cy.toFixed(1) + '" width="' + (boxW - 14) + '" height="6" rx="3" fill="url(#vis_g)"/>';
|
||||
const hue = Math.round(270 - (lam - 380) / 320 * 270);
|
||||
const cy = yMid - bxH / 2 + 40;
|
||||
svg += '<defs><linearGradient id="vg"><stop offset="0%" stop-color="hsl(' + hue + ',88%,54%)"/><stop offset="100%" stop-color="hsl(' + (hue - 18) + ',88%,58%)"/></linearGradient></defs>';
|
||||
svg += '<rect x="' + (bxLeft + 8).toFixed(1) + '" y="' + cy.toFixed(1) + '" width="' + (bxW - 16) + '" height="6" rx="3" fill="url(#vg)"/>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user