feat(math6): живой график y=kx / y=k/x (Гл.5 §3) — плавное перетекание при k
Math6Anim.plotLive: canvas-плоскость с сеткой/осями; кривая плавно «перетекает» (easing к целевому k). Переключатель прямая (y=kx, через начало) / обратная (y=k/x, две ветви). Слайдер k (−4..4, шаг 0,5) двигает кривую вживую. Вшито в Гл.5 §3 рядом со статичным графиком. Headless-safe. Тесты 19/19. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -236,4 +236,46 @@ M.carGraph = function (host, opts) {
|
||||
return { stop: L.stop };
|
||||
};
|
||||
|
||||
/* ============================ ДЕМО 6: ЖИВОЙ ГРАФИК y=kx / y=k/x ============================ */
|
||||
M.plotLive = function (host, opts) {
|
||||
opts = opts || {};
|
||||
var W0 = 360, H0 = 360; var sc = sceneCanvas(host, W0, H0);
|
||||
var st = { k: opts.k != null ? opts.k : 2, target: opts.k != null ? opts.k : 2, mode: opts.mode || 'kx' };
|
||||
var XMIN = -6, XMAX = 6, YMIN = -6, YMAX = 6, pad = 24;
|
||||
function X(x) { return pad + (x - XMIN) / (XMAX - XMIN) * (W0 - 2 * pad); }
|
||||
function Y(y) { return H0 - pad - (y - YMIN) / (YMAX - YMIN) * (H0 - 2 * pad); }
|
||||
function draw() {
|
||||
var ctx = sc.ctx; if (!ctx) return;
|
||||
var pri = cssVar('--pri', '#059669'), acc = cssVar('--pri2', '#047857'), bd = cssVar('--border', '#e2e8f0'), axc = cssVar('--text', '#0f172a');
|
||||
st.k += (st.target - st.k) * 0.12;
|
||||
ctx.clearRect(0, 0, W0, H0);
|
||||
ctx.strokeStyle = bd; ctx.lineWidth = 0.8;
|
||||
for (var gx = XMIN; gx <= XMAX; gx++) { if (gx === 0) continue; ctx.beginPath(); ctx.moveTo(X(gx), Y(YMIN)); ctx.lineTo(X(gx), Y(YMAX)); ctx.stroke(); }
|
||||
for (var gy = YMIN; gy <= YMAX; gy++) { if (gy === 0) continue; ctx.beginPath(); ctx.moveTo(X(XMIN), Y(gy)); ctx.lineTo(X(XMAX), Y(gy)); ctx.stroke(); }
|
||||
ctx.strokeStyle = axc; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(X(XMIN), Y(0)); ctx.lineTo(X(XMAX), Y(0)); ctx.moveTo(X(0), Y(YMIN)); ctx.lineTo(X(0), Y(YMAX)); ctx.stroke();
|
||||
ctx.fillStyle = axc; ctx.font = 'italic 13px serif'; ctx.textAlign = 'left'; ctx.fillText('x', X(XMAX) - 4, Y(0) + 16); ctx.fillText('y', X(0) + 8, Y(YMAX) + 12);
|
||||
ctx.strokeStyle = pri; ctx.lineWidth = 3; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
|
||||
if (st.mode === 'kx') {
|
||||
ctx.beginPath(); var started = false;
|
||||
for (var x = XMIN; x <= XMAX; x += 0.06) { var y = st.k * x; if (y < YMIN - 1 || y > YMAX + 1) { started = false; continue; } if (started) ctx.lineTo(X(x), Y(y)); else { ctx.moveTo(X(x), Y(y)); started = true; } }
|
||||
ctx.stroke();
|
||||
} else {
|
||||
[[XMIN, -0.12], [0.12, XMAX]].forEach(function (seg) {
|
||||
ctx.beginPath(); var s2 = false;
|
||||
for (var x2 = seg[0]; x2 <= seg[1]; x2 += 0.04) { if (Math.abs(x2) < 0.08) continue; var y2 = st.k / x2; if (y2 < YMIN - 1 || y2 > YMAX + 1) { s2 = false; continue; } if (s2) ctx.lineTo(X(x2), Y(y2)); else { ctx.moveTo(X(x2), Y(y2)); s2 = true; } }
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
ctx.fillStyle = acc; ctx.font = 'bold 16px Inter, sans-serif'; ctx.textAlign = 'left';
|
||||
var kk = Math.round(st.k * 10) / 10;
|
||||
ctx.fillText(st.mode === 'kx' ? ('y = ' + kf(kk) + ' · x') : ('y = ' + kf(kk) + ' / x'), pad + 4, pad + 10);
|
||||
}
|
||||
var L = loop(host, draw);
|
||||
return {
|
||||
stop: L.stop,
|
||||
setK: function (v) { st.target = v; if (st.mode === 'kdx' && Math.abs(v) < 0.5) st.target = (v < 0 ? -0.5 : 0.5); },
|
||||
setMode: function (m) { st.mode = m; if (m === 'kdx' && Math.abs(st.target) < 0.5) st.target = 0.5; }
|
||||
};
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
Reference in New Issue
Block a user