/* math6_svg.js — SVG-хелперы учебника «Математика 6». window.Math6. * Самодостаточно (без зависимостей). Все функции возвращают строку SVG, * готовую к вставке в innerHTML. Координаты SVG: y растёт вниз — учтено. * * Готово (Phase 0 / Глава 1): fmt, box, numberLine (прямая/луч с метками и точками), * plane (декартова плоскость — фундамент для Главы 5). * Будет добавлено при наполнении глав 5–6: plotFn, circleFig, triangleFig, solid, net, * reflectPoint/reflectLine (симметрия). */ (function () { 'use strict'; if (window.Math6 && window.Math6.__installed) return; var M = window.Math6 = window.Math6 || {}; M.__installed = true; /* Русская запись числа: десятичная запятая, без хвостовых нулей */ M.fmt = function (n) { if (n == null || !isFinite(n)) return '' + n; var s = (Math.round(n * 1e9) / 1e9).toString(); return s.replace('.', ','); }; function esc(s) { return String(s).replace(/&/g, '&').replace(//g, '>'); } /* Обёртка с фоном */ M.box = function (w, h, inner, opts) { opts = opts || {}; return '
' + '' + (inner || '') + '
'; }; /* === ЧИСЛОВАЯ ПРЯМАЯ / КООРДИНАТНЫЙ ЛУЧ === * opts: {min,max, minor, major, ray(bool), marks:[{v,label,color,open,above}], * segments:[{from,to,color}], width,height, title} */ M.numberLine = function (opts) { opts = opts || {}; var min = opts.min != null ? opts.min : 0; var max = opts.max != null ? opts.max : 10; var minor = opts.minor || 1; var major = opts.major || minor; var W = opts.width || 540, H = opts.height || 92; var pad = 34; var axisY = Math.round(H * 0.58); var x0 = pad, x1 = W - pad; function X(v) { return x0 + (v - min) / (max - min) * (x1 - x0); } var col = opts.color || 'var(--pri,#4f46e5)'; var s = ''; /* выделенные отрезки/интервалы (рисуем под осью) */ (opts.segments || []).forEach(function (seg) { var a = X(seg.from), b = X(seg.to); s += ''; }); /* ось со стрелками */ s += ''; s += ''; if (!opts.ray) s += ''; /* деления и подписи */ var nMinor = Math.round((max - min) / minor); for (var i = 0; i <= nMinor; i++) { var v = min + i * minor; v = Math.round(v * 1e6) / 1e6; var x = X(v); var isMajor = Math.abs(Math.round((v - min) / major) - (v - min) / major) < 1e-6; var len = isMajor ? 9 : 5; s += ''; if (isMajor && opts.labels !== false) { s += '' + esc(M.fmt(v)) + ''; } } /* точки-маркеры */ (opts.marks || []).forEach(function (mk) { var x = X(mk.v); var c = mk.color || '#e11d48'; if (mk.open) s += ''; else s += ''; if (mk.label) { var ly = mk.above === false ? axisY + 38 : axisY - 14; s += '' + esc(mk.label) + ''; } }); return M.box(W, H, s, { maxw: opts.maxw || W, bg: opts.bg }); }; /* === ДЕКАРТОВА ПЛОСКОСТЬ (фундамент для Главы 5) === * opts: {xmin,xmax,ymin,ymax, points:[{x,y,label,color,open}], * segments:[{from:{x,y},to:{x,y},color,dash}], quadrants(bool), * plot:{fn,color,samples,from,to}, size, unitLabels(bool)} */ M.plane = function (opts) { opts = opts || {}; var xmin = opts.xmin != null ? opts.xmin : -5, xmax = opts.xmax != null ? opts.xmax : 5; var ymin = opts.ymin != null ? opts.ymin : -5, ymax = opts.ymax != null ? opts.ymax : 5; var S = opts.size || 360, pad = 22; function X(x) { return pad + (x - xmin) / (xmax - xmin) * (S - 2 * pad); } function Y(y) { return S - pad - (y - ymin) / (ymax - ymin) * (S - 2 * pad); } var axc = 'var(--text,#0f172a)'; var s = ''; if (opts.quadrants) { var cx = X(0), cy = Y(0); s += ''; s += ''; } /* сетка */ for (var gx = Math.ceil(xmin); gx <= Math.floor(xmax); gx++) { s += ''; } for (var gy = Math.ceil(ymin); gy <= Math.floor(ymax); gy++) { s += ''; } /* оси */ s += ''; s += ''; s += ''; s += ''; s += 'x'; s += 'y'; s += '0'; /* график функции */ if (opts.plot && typeof opts.plot.fn === 'function') { var pl = opts.plot, from = pl.from != null ? pl.from : xmin, to = pl.to != null ? pl.to : xmax; var N = pl.samples || 120, pts = [], started = false, d = ''; for (var k = 0; k <= N; k++) { var xv = from + (to - from) * k / N, yv = pl.fn(xv); if (yv == null || !isFinite(yv) || yv < ymin - 1 || yv > ymax + 1) { started = false; continue; } d += (started ? ' L ' : ' M ') + X(xv) + ' ' + Y(yv); started = true; } s += ''; } /* отрезки */ (opts.segments || []).forEach(function (sg) { s += ''; }); /* точки */ (opts.points || []).forEach(function (p) { var c = p.color || '#e11d48'; if (p.open) s += ''; else s += ''; if (p.label) s += '' + esc(p.label) + ''; }); return M.box(S, S, s, { maxw: opts.maxw || S, bg: opts.bg }); }; })();