feat(trigcircle): KaTeX-оверлей для подписей на canvas (координаты, значения, угол)
На <canvas> KaTeX не рисуется (fillText), поэтому подписи, которые были юникод-текстом (√2/2, координаты точки, π/4, значение на графике), переведены на HTML-оверлей #trig-overlay поверх холста с KaTeX-рендером и точным позиционированием (transform по CSS-px = координаты canvas). Переведены: координатная подсказка (cos; sin), бейджи значений sin/cos, метка угла у дуги, бейдж значения на графике. Подписи-слова sin/cos/tg/ctg и мелкие точки табличных углов остаются на canvas (не математика / 16 мелких меток). Механика: _ov/_ovLabel/_ovClearUnused — кэш по ключу (ре-рендер только при смене LaTeX), KaTeX лишь для дробей/корней, простые числа — текстом (быстро при перетаскивании), неис- пользованные за кадр подписи прячутся. Старые canvas-методы _badge/_tooltip больше не зовутся. Verified: node --check; headless-смоук оверлея 12/12 (coord/vsin/vcos/angle/gval создаются, KaTeX-LaTeX для √2/2 и π/4, позиционирование/плашка, десятичные как текст, скрытие при выкл. слоя/графика). Эмодзи нет. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,7 @@ class TrigCircleSim {
|
||||
if (window.LabFX) LabFX.particles.draw(c);
|
||||
|
||||
c.restore();
|
||||
this._ovClearUnused();
|
||||
this._fireUpdate();
|
||||
}
|
||||
|
||||
@@ -171,6 +172,51 @@ class TrigCircleSim {
|
||||
c.restore();
|
||||
}
|
||||
|
||||
/* ═══ KaTeX-оверлей: HTML-подписи поверх canvas (на canvas KaTeX не рисуется) ══════ */
|
||||
_ov() {
|
||||
if (this._ovEl === undefined) this._ovEl = (typeof document !== 'undefined' && document.getElementById) ? document.getElementById('trig-overlay') : null;
|
||||
return this._ovEl;
|
||||
}
|
||||
/* key — стабильный id подписи; latex — LaTeX (дробь/корень → KaTeX, иначе текст);
|
||||
x,y — CSS-px над canvas; anchor: c|l|r|t|b; boxed — тёмная плашка (для координат). */
|
||||
_ovLabel(key, latex, x, y, color, anchor, boxed) {
|
||||
const ov = this._ov(); if (!ov) return;
|
||||
this._ovMap = this._ovMap || {};
|
||||
this._ovUsed = this._ovUsed || {};
|
||||
let rec = this._ovMap[key];
|
||||
if (!rec) {
|
||||
const el = document.createElement('div');
|
||||
el.style.position = 'absolute'; el.style.whiteSpace = 'nowrap'; el.style.pointerEvents = 'none';
|
||||
el.style.willChange = 'transform';
|
||||
ov.appendChild(el);
|
||||
rec = this._ovMap[key] = { el, last: null, boxed: null };
|
||||
}
|
||||
if (rec.last !== latex) {
|
||||
const useK = /\\tfrac|\\sqrt|\\left|\\frac/.test(latex) && (typeof window !== 'undefined' && window.katex);
|
||||
if (useK) rec.el.innerHTML = window.katex.renderToString(latex, { throwOnError: false, strict: false, displayMode: false });
|
||||
else rec.el.textContent = latex;
|
||||
rec.last = latex;
|
||||
}
|
||||
if (rec.boxed !== !!boxed) {
|
||||
rec.el.style.cssText += boxed
|
||||
? ';background:rgba(12,12,22,0.82);border:1px solid rgba(155,93,229,0.3);border-radius:8px;padding:3px 9px'
|
||||
: ';background:none;border:none;padding:0';
|
||||
rec.boxed = !!boxed;
|
||||
}
|
||||
rec.el.style.color = color || '#fff';
|
||||
const a = anchor || 'c';
|
||||
const tr = a === 'r' ? 'translate(-100%,-50%)' : a === 'l' ? 'translate(0,-50%)'
|
||||
: a === 't' ? 'translate(-50%,0)' : a === 'b' ? 'translate(-50%,-100%)' : 'translate(-50%,-50%)';
|
||||
rec.el.style.transform = `translate(${x}px,${y}px) ${tr}`;
|
||||
rec.el.style.display = '';
|
||||
this._ovUsed[key] = true;
|
||||
}
|
||||
_ovClearUnused() {
|
||||
if (!this._ovMap) return;
|
||||
for (const k in this._ovMap) if (!(this._ovUsed && this._ovUsed[k])) this._ovMap[k].el.style.display = 'none';
|
||||
this._ovUsed = {};
|
||||
}
|
||||
|
||||
goToAngle(rad) {
|
||||
this._animTarget = this._norm(rad);
|
||||
if (!this.animating) this._startAnim();
|
||||
@@ -354,11 +400,10 @@ class TrigCircleSim {
|
||||
ag.addColorStop(1, _tcRgba(_TC.violet, 0.0));
|
||||
c.strokeStyle = ag; c.lineWidth = 2.5;
|
||||
c.beginPath(); c.arc(cx, cy, ar, 0, -a, true); c.stroke();
|
||||
/* label */
|
||||
const mid = a / 2, lr = ar + 18;
|
||||
c.font = 'bold 12px Manrope,sans-serif'; c.fillStyle = _TC.violet;
|
||||
c.textAlign = 'center'; c.textBaseline = 'middle';
|
||||
c.fillText(this._radLbl(a), cx + lr * Math.cos(-mid), cy + lr * Math.sin(-mid));
|
||||
/* label (KaTeX overlay: π-доля для табличных, иначе текст) */
|
||||
const mid = a / 2, lr = ar + 20;
|
||||
this._ovLabel('angle', _angleLatex(a) || this._radLbl(a),
|
||||
cx + lr * Math.cos(-mid), cy + lr * Math.sin(-mid), _TC.violet, 'c');
|
||||
}
|
||||
|
||||
/* ── radius ── */
|
||||
@@ -452,9 +497,9 @@ class TrigCircleSim {
|
||||
|
||||
/* ── axis value badges ── */
|
||||
if (this.showSin && Math.abs(sinA) > 0.04)
|
||||
this._badge(c, cx - 12, py, this._fmt(sinA), _TC.sin, 'right', 'middle');
|
||||
this._ovLabel('vsin', _latexVal(sinA), cx - 14, py, _TC.sin, 'r');
|
||||
if (this.showCos && Math.abs(cosA) > 0.04)
|
||||
this._badge(c, projX, cy + 17, this._fmt(cosA), _TC.cos, 'center', 'top');
|
||||
this._ovLabel('vcos', _latexVal(cosA), projX, cy + 20, _TC.cos, 't');
|
||||
|
||||
/* ── main point ── */
|
||||
const ps = this._hover || this._drag ? 10 : 8;
|
||||
@@ -470,8 +515,9 @@ class TrigCircleSim {
|
||||
c.strokeStyle = 'rgba(255,255,255,0.50)'; c.lineWidth = 2;
|
||||
c.beginPath(); c.arc(px, py, ps, 0, Math.PI*2); c.stroke();
|
||||
|
||||
/* ── coordinate tooltip ── */
|
||||
this._tooltip(c, px, py, cosA, sinA);
|
||||
/* ── coordinate tooltip (KaTeX overlay) ── */
|
||||
this._ovLabel('coord', `\\left(${_latexVal(cosA)};\\ ${_latexVal(sinA)}\\right)`,
|
||||
px + (cosA >= 0 ? 18 : -18), py + (sinA >= 0 ? -20 : 20), '#fff', cosA >= 0 ? 'l' : 'r', true);
|
||||
|
||||
/* ── quadrant roman numeral ── */
|
||||
const qOff = r * 0.46;
|
||||
@@ -751,17 +797,8 @@ class TrigCircleSim {
|
||||
c.shadowBlur = 0;
|
||||
c.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
c.beginPath(); c.arc(mx, my, 2, 0, Math.PI*2); c.fill();
|
||||
/* value badge */
|
||||
const txt = this._fmt(curY);
|
||||
c.font = 'bold 11px Manrope,sans-serif';
|
||||
const tm = c.measureText(txt);
|
||||
const bx2 = mx+10, by2 = my-22, bw2 = tm.width+14, bh2 = 20;
|
||||
c.fillStyle='rgba(12,12,22,0.85)';
|
||||
c.beginPath(); c.roundRect(bx2, by2-bh2/2, bw2, bh2, 6); c.fill();
|
||||
c.strokeStyle = _tcRgba(col, 0.4); c.lineWidth = 1;
|
||||
c.beginPath(); c.roundRect(bx2, by2-bh2/2, bw2, bh2, 6); c.stroke();
|
||||
c.fillStyle = col; c.textAlign='left'; c.textBaseline='middle';
|
||||
c.fillText(txt, bx2+7, by2);
|
||||
/* value badge (KaTeX overlay) */
|
||||
this._ovLabel('gval', _latexVal(curY), mx + 12, my - 20, col, 'l', true);
|
||||
}
|
||||
|
||||
c.restore();
|
||||
|
||||
@@ -655,8 +655,10 @@
|
||||
</div><!-- /.proj-panel -->
|
||||
|
||||
<!-- canvas -->
|
||||
<div class="proj-canvas-outer">
|
||||
<div class="proj-canvas-outer" style="position:relative">
|
||||
<canvas id="trigcircle-canvas"></canvas>
|
||||
<!-- KaTeX overlay: подписи значений/координат/угла над canvas -->
|
||||
<div id="trig-overlay" style="position:absolute;inset:0;pointer-events:none;overflow:hidden;font-size:0.82rem"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.sim-body-wrap -->
|
||||
|
||||
Reference in New Issue
Block a user