feat(math6): термометр (Гл.4 §1) — ±числа и модуль наглядно
Math6Anim.thermometer: вертикальный термометр на canvas, ртуть плавно поднимается/опускается к значению (easing), выше нуля — красный, ниже — синий; подпись поясняет знак и |x| как расстояние до нуля. Ползунок −10..10. Вшит в Гл.4 §1. Headless-safe. Тесты math6: 20/20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -278,6 +278,33 @@ M.plotLive = function (host, opts) {
|
||||
};
|
||||
};
|
||||
|
||||
/* ============================ ДЕМО 7: ТЕРМОМЕТР (±числа, модуль) ============================ */
|
||||
M.thermometer = function (host, opts) {
|
||||
opts = opts || {}; var W0 = 220, H0 = 320; var sc = sceneCanvas(host, W0, H0); var cap = caption(host, '');
|
||||
var st = { v: opts.value != null ? opts.value : 5, target: opts.value != null ? opts.value : 5 };
|
||||
var MIN = -10, MAX = 10, cx = 64, top = 26, bot = H0 - 56, bulbR = 18;
|
||||
function Y(v) { return bot - (v - MIN) / (MAX - MIN) * (bot - top); }
|
||||
function draw() {
|
||||
var ctx = sc.ctx; if (!ctx) return; st.v += (st.target - st.v) * 0.15;
|
||||
var mut = cssVar('--muted', '#64748b'), txt = cssVar('--text', '#0f172a');
|
||||
ctx.clearRect(0, 0, W0, H0);
|
||||
ctx.strokeStyle = mut; ctx.lineWidth = 2;
|
||||
ctx.beginPath(); ctx.moveTo(cx - 9, top); ctx.arc(cx, top, 9, Math.PI, 0); ctx.lineTo(cx + 9, bot); ctx.moveTo(cx - 9, top); ctx.lineTo(cx - 9, bot); ctx.stroke();
|
||||
ctx.beginPath(); ctx.arc(cx, bot + bulbR - 4, bulbR, 0, 2 * Math.PI); ctx.fillStyle = '#fee2e2'; ctx.fill(); ctx.strokeStyle = mut; ctx.stroke();
|
||||
ctx.font = '11px JetBrains Mono, monospace'; ctx.textAlign = 'left';
|
||||
for (var v = MIN; v <= MAX; v += 2) { var y = Y(v); ctx.strokeStyle = mut; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx + 12, y); ctx.lineTo(cx + 18, y); ctx.stroke(); ctx.fillStyle = (v === 0 ? txt : mut); ctx.fillText(v + '°', cx + 22, y + 4); }
|
||||
var col = st.v >= 0 ? '#dc2626' : '#2563eb', y0 = Y(0), yv = Y(st.v);
|
||||
ctx.fillStyle = col; ctx.fillRect(cx - 5, Math.min(y0, yv), 10, Math.abs(yv - y0));
|
||||
ctx.beginPath(); ctx.arc(cx, bot + bulbR - 4, bulbR - 3, 0, 2 * Math.PI); ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(cx, yv, 4, 0, 2 * Math.PI); ctx.fill();
|
||||
ctx.fillStyle = txt; ctx.font = 'bold 17px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.fillText(Math.round(st.v) + '°', cx, H0 - 14);
|
||||
}
|
||||
var L = loop(host, draw);
|
||||
function setCap(v) { if (!cap) return; cap.innerHTML = '$' + v + '°$ — это ' + (v > 0 ? 'тепло, выше нуля' : (v < 0 ? 'мороз, ниже нуля' : 'ровно ноль')) + '. Модуль $|' + v + '| = ' + Math.abs(v) + '$ — это расстояние до нуля.'; if (W.renderMathInElement) try { W.renderMathInElement(cap, { delimiters: [{ left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {} }
|
||||
setCap(st.target);
|
||||
return { stop: L.stop, set: function (v) { st.target = v; setCap(v); } };
|
||||
};
|
||||
|
||||
/* ============================ КОМПОНЕНТ: ПОШАГОВЫЙ ПЛЕЕР (DOM, не canvas) ============================ */
|
||||
M.stepPlayer = function (host, opts) {
|
||||
opts = opts || {}; var steps = opts.steps || []; if (!steps.length) return { stop: function () {} };
|
||||
|
||||
Reference in New Issue
Block a user