diff --git a/frontend/js/labs/trigcircle.js b/frontend/js/labs/trigcircle.js index d609728..4674e03 100644 --- a/frontend/js/labs/trigcircle.js +++ b/frontend/js/labs/trigcircle.js @@ -53,6 +53,7 @@ class TrigCircleSim { this.graphFn = 'sin'; this.snapToNotable = true; this.animating = false; + this.eq = null; // режим уравнения: { fn:'sin'|'cos'|'tg', a:Number, sols:[рад] } | null this._cx = 0; this._cy = 0; this._r = 0; this._gx = 0; this._gw = 0; this._gh = 0; this._gy = 0; @@ -96,6 +97,7 @@ class TrigCircleSim { this._drawBg(c); this._drawCircle(c); + if (this.eq) this._drawEquation(c); if (this.showGraph) { this._drawDivider(c); this._drawGraph(c); } this._drawParticles(c); if (window.LabFX) LabFX.particles.draw(c); @@ -116,6 +118,41 @@ class TrigCircleSim { this._layout(); this.draw(); } + /* Режим уравнения: подсветить на окружности все решения fn(x)=a. */ + setEquation(fn, a, sols) { + this.eq = { fn, a, sols: sols || [] }; + if (this.eq.sols.length) this.angle = this.eq.sols[0]; // встать на первое решение + this.draw(); + } + clearEquation() { this.eq = null; this.draw(); } + + _drawEquation(c) { + const cx = this._cx, cy = this._cy, r = this._r; + const { fn, a, sols } = this.eq; + const accent = fn === 'sin' ? _TC.sin : fn === 'cos' ? _TC.cos : _TC.tan; + c.save(); + /* направляющая линия значения */ + c.strokeStyle = _tcRgba(accent, 0.55); c.lineWidth = 1.5; c.setLineDash([6, 5]); + c.beginPath(); + if (fn === 'sin') { const y = cy - r * a; c.moveTo(cx - r - 22, y); c.lineTo(cx + r + 22, y); } + else if (fn === 'cos') { const x = cx + r * a; c.moveTo(x, cy - r - 22); c.lineTo(x, cy + r + 22); } + else { const ang = sols.length ? sols[0] : Math.atan(a); const dx = Math.cos(ang), dy = Math.sin(ang), L = r + 24; + c.moveTo(cx - L * dx, cy + L * dy); c.lineTo(cx + L * dx, cy - L * dy); } + c.stroke(); c.setLineDash([]); + /* точки-решения + подписи градусов */ + c.font = 'bold 11px Manrope,sans-serif'; + sols.forEach(ang => { + const x = cx + r * Math.cos(ang), y = cy - r * Math.sin(ang); + c.fillStyle = accent; c.shadowColor = accent; c.shadowBlur = 12; + c.beginPath(); c.arc(x, y, 6, 0, Math.PI * 2); c.fill(); c.shadowBlur = 0; + c.fillStyle = 'rgba(255,255,255,0.92)'; c.beginPath(); c.arc(x, y, 2.2, 0, Math.PI * 2); c.fill(); + const lr = r + 18, lx = cx + lr * Math.cos(ang), ly = cy - lr * Math.sin(ang); + c.fillStyle = accent; c.textAlign = 'center'; c.textBaseline = 'middle'; + c.fillText(Math.round(ang * 180 / Math.PI) + '°', lx, ly); + }); + c.restore(); + } + goToAngle(rad) { this._animTarget = this._norm(rad); if (!this.animating) this._startAnim(); @@ -1058,6 +1095,80 @@ if (typeof window !== 'undefined') window.TrigCircleSim = TrigCircleSim; if (fns) fns.style.display = on ? '' : 'none'; } + /* ── Уравнения: решения fn(x)=a на [0,2π) ── */ + function _trigSolveAngles(fn, a) { + const TAU = 2 * Math.PI, norm = x => ((x % TAU) + TAU) % TAU; + let raw; + if (fn === 'sin') { if (Math.abs(a) > 1) return []; const b = Math.asin(a); raw = [b, Math.PI - b]; } + else if (fn === 'cos') { if (Math.abs(a) > 1) return []; const b = Math.acos(a); raw = [b, -b]; } + else { const b = Math.atan(a); raw = [b, b + Math.PI]; } // tg — всегда есть решения + const out = []; + raw.map(norm).forEach(x => { if (!out.some(y => Math.abs(y - x) < 1e-6 || Math.abs(y - x - TAU) < 1e-6)) out.push(x); }); + return out.sort((p, q) => p - q); + } + /* Радиан → LaTeX красивой π-доли (или null). Покрывает главные значения arcsin/arccos/arctg. */ + function _radLatex(rad) { + const P = Math.PI; + const T = [[0, '0'], [P/6, '\\tfrac{\\pi}{6}'], [P/4, '\\tfrac{\\pi}{4}'], [P/3, '\\tfrac{\\pi}{3}'], + [P/2, '\\tfrac{\\pi}{2}'], [2*P/3, '\\tfrac{2\\pi}{3}'], [3*P/4, '\\tfrac{3\\pi}{4}'], + [5*P/6, '\\tfrac{5\\pi}{6}'], [P, '\\pi']]; + for (const [v, l] of T) { + if (Math.abs(rad - v) < 1e-6) return l; + if (v > 0 && Math.abs(rad + v) < 1e-6) return '-' + l; + } + return null; + } + /* Общая формула решения (LaTeX) или {none:true}. */ + function _trigEqFormulaLatex(fn, a) { + if ((fn === 'sin' || fn === 'cos') && Math.abs(a) > 1) return { none: true }; + if (fn === 'sin') { + const p = _radLatex(Math.asin(a)) || ('\\arcsin ' + _latexVal(a)); + return { latex: `x = (-1)^{n}\\,${p} + \\pi n,\\ n\\in\\mathbb{Z}` }; + } + if (fn === 'cos') { + const p = _radLatex(Math.acos(a)) || ('\\arccos ' + _latexVal(a)); + return { latex: `x = \\pm ${p} + 2\\pi n,\\ n\\in\\mathbb{Z}` }; + } + const p = _radLatex(Math.atan(a)) || ('\\operatorname{arctg} ' + _latexVal(a)); + return { latex: `x = ${p} + \\pi n,\\ n\\in\\mathbb{Z}` }; + } + + var trigEqFn = 'sin'; + function trigSetEqFn(fn, btn) { + trigEqFn = fn; + document.querySelectorAll('.trig-eq-fn').forEach(b => b.classList.remove('active')); + if (btn) btn.classList.add('active'); + } + function trigSolve() { + if (!trigSim) return; + const inp = document.getElementById('trig-eq-input'); + const a = parseFloat(String(inp && inp.value || '').replace(',', '.')); + const fnTex = { sin: '\\sin', cos: '\\cos', tg: '\\operatorname{tg}' }[trigEqFn]; + const fEl = document.getElementById('trig-eq-formula'); + const sEl = document.getElementById('trig-eq-sols'); + if (!isFinite(a)) { if (fEl) fEl.innerHTML = 'Введите значение a'; if (sEl) sEl.textContent = ''; return; } + const sols = _trigSolveAngles(trigEqFn, a); + trigSim.setEquation(trigEqFn, a, sols); + const K = window.katex; + const tex = l => (K ? K.renderToString(l, { throwOnError: false, strict: false, displayMode: false }) : l); + const eqHead = tex(`${fnTex} x = ${_latexVal(a)}`); + const f = _trigEqFormulaLatex(trigEqFn, a); + if (fEl) { + fEl.innerHTML = `
${eqHead}
` + + (f.none ? '
Нет решений (|a| > 1)
' : `
${tex(f.latex)}
`); + } + if (sEl) sEl.textContent = sols.length + ? 'На [0, 2π): ' + sols.map(x => Math.round(x * 180 / Math.PI) + '°').join(', ') + : ''; + } + function trigClearEq() { + if (!trigSim) return; + trigSim.clearEquation(); + const fEl = document.getElementById('trig-eq-formula'); if (fEl) fEl.innerHTML = ''; + const sEl = document.getElementById('trig-eq-sols'); if (sEl) sEl.textContent = ''; + } + function trigEqKey(e) { if (e && (e.key === 'Enter' || e.keyCode === 13)) trigSolve(); } + function _trigUpdateUI(s) { const _f = v => { if (v === undefined) return '—'; diff --git a/frontend/labs-bodies.html b/frontend/labs-bodies.html index 9a4ed74..9dcffdd 100644 --- a/frontend/labs-bodies.html +++ b/frontend/labs-bodies.html @@ -582,6 +582,23 @@
Точные значения · приведение
+ +
Уравнение
+
+ + + + x = + +
+
+ + +
+
+
+
Табличные углы