From e3f4c193236a0d5a24320891a0710875e47ec649 Mon Sep 17 00:00:00 2001 From: SerbaC6 Date: Mon, 17 Aug 2026 19:41:50 +0300 Subject: [PATCH] Made changes for interfaces Signed-off-by: SerbaC6 --- .../content/InterfataCuadraturi.tsx | 13 +- .../content/InterfataDerivareNumerica.tsx | 92 ++--- .../content/InterfataEliminareGaussiana.tsx | 27 +- .../content/InterfataInterpolare.tsx | 329 +++++++----------- .../content/InterfataNewtonCotes.tsx | 21 +- src/components/viz/MatrixGrid.tsx | 13 +- 6 files changed, 211 insertions(+), 284 deletions(-) diff --git a/src/components/content/InterfataCuadraturi.tsx b/src/components/content/InterfataCuadraturi.tsx index 852d856..9494601 100644 --- a/src/components/content/InterfataCuadraturi.tsx +++ b/src/components/content/InterfataCuadraturi.tsx @@ -45,7 +45,7 @@ import { esantioneaza, sparge } from "@/lib/plot-esantionare"; type Fila = "adaptiv" | "gauss"; const FILE: { id: Fila; eticheta: string }[] = [ - { id: "adaptiv", eticheta: "Cuadraturi adaptive" }, + { id: "adaptiv", eticheta: "Cuadraturi Adaptive" }, { id: "gauss", eticheta: "Cuadraturi Gaussiene" }, ]; @@ -68,12 +68,11 @@ const FUNCTIE_IMPLICITA: Record = { // Vârful ascuțit lângă zero și coada plată: exact cazul care cere panouri de // lățimi diferite. adaptiv: "oscilanta", - // Sinusul pe [0, π]. Cu patru noduri alese, formula greșește cu 1,6e-5; cu - // patru echidistante, cu 4,1e-2 — de două mii de ori mai mult, și se vede pe - // desen, fiindcă polinomul prin nodurile echidistante taie vizibil vârful. - // Pe funcția adaptivului, amândouă variantele greșesc grosolan și butoanele - // nu mai arată nimic. - gauss: "sinus", + // Radicalul pe [0, 1]. Tangenta verticală din zero e chiar cazul în care se + // vede de ce contează unde cad nodurile: cele echidistante trebuie să pună + // unul exact în capătul unde funcția urcă vertical, iar polinomul prin ele + // pleacă în jos înainte să urce. Cele alese stau înăuntru și nu ating capătul. + gauss: "radical", }; /** diff --git a/src/components/content/InterfataDerivareNumerica.tsx b/src/components/content/InterfataDerivareNumerica.tsx index 3225998..a2c9813 100644 --- a/src/components/content/InterfataDerivareNumerica.tsx +++ b/src/components/content/InterfataDerivareNumerica.tsx @@ -217,15 +217,20 @@ export function InterfataDerivareNumerica() { + {/* Graficul ia toată lățimea ramei, iar parametrii trec dedesubt, ca la + celelalte pagini de integrare. Într-o coloană laterală, desenul rămânea + îngust tocmai unde se compară două înclinări, iar panoul de alături îl + împingea din mijlocul ramei. */}
-
-
+
+
@@ -293,53 +298,48 @@ export function InterfataDerivareNumerica() { setPas(PAS_IMPLICIT); }} incorporat - className="border-bordura min-w-0 border-t lg:border-t-0 lg:border-l" + className="border-bordura min-w-0 border-t" > - {/* Dintr-o listă, nu dintr-un șir de butoane: butoanele se rupeau pe - trei rânduri și cereau citite toate ca să alegi unul. */} -
- - +
+ {/* Dintr-o listă, nu dintr-un șir de butoane: butoanele se rupeau + pe trei rânduri și cereau citite toate ca să alegi unul. */} +
+ + +
+ + + +
- - - - {/* Fiecare cifră în chenarul ei: într-o listă simplă, „exact" și „aproximare" — două numere cu opt zecimale — se citeau ca un singur bloc, tocmai unde diferența dintre ele e subiectul. */} -
+
{[ ["h folosit acum", stiintific(h, 2)], ["f′(x₀) exact", zecimale(exact, 8)], @@ -360,7 +360,9 @@ export function InterfataDerivareNumerica() { ))}
-

{functie.ceArata}

+

+ {functie.ceArata} +

diff --git a/src/components/content/InterfataEliminareGaussiana.tsx b/src/components/content/InterfataEliminareGaussiana.tsx index 3fff61f..f544aa5 100644 --- a/src/components/content/InterfataEliminareGaussiana.tsx +++ b/src/components/content/InterfataEliminareGaussiana.tsx @@ -146,7 +146,11 @@ export function InterfataEliminareGaussiana() {
-
+ {/* `items-stretch`, nu `items-start`: coloana de scoruri își ia + înălțimea de la matrice, iar rândurile ei se împart în părți + egale — altfel scorul liniei a treia ajungea cu paisprezece + pixeli mai jos decât linia lui. */} +
j < coeficienti ? `C${j + 1}` : "b", )} - corp="mare" + corp="foarte-mare" formateaza={cifra} descriere={descriereMatricei(pas, rezultat.pasi.length, derulare.pas)} /> @@ -371,11 +375,18 @@ function ColoanaScoruri({ strategie === "partiala" ? "|aᵢₚ|" : strategie === "scalata" ? "|aᵢₚ| / sᵢ" : "max |aᵢⱼ|"; return ( -
-
+
+ {/* Antetul ține exact înălțimea rândului de nume al matricei — 16 px de + rând plus `pb-0.5` —, ca boxurile de dedesubt să pornească de unde + pornesc liniile. Simbolul e mai mare, dar cu `leading-4`: crește + litera, nu rândul. */} +
{antet}
-
    +
      {Array.from({ length: linii }, (_, i) => { const scor = alegere?.scoruri[i]; // Când toate scorurile sunt nule, nu există câștigător: linia „aleasă" @@ -386,7 +397,7 @@ function ColoanaScoruri({ return (
    • diff --git a/src/components/content/InterfataInterpolare.tsx b/src/components/content/InterfataInterpolare.tsx index 053e12d..a736004 100644 --- a/src/components/content/InterfataInterpolare.tsx +++ b/src/components/content/InterfataInterpolare.tsx @@ -46,10 +46,14 @@ import { cn } from "@/lib/utils"; /* ───────────────────────── alegerile paginii ───────────────────────── */ +/** + * Cele trei file. **Funcția Runge nu mai are filă**, la cerere: fenomenul e + * povestit de clipul de sus, iar aici ar fi fost a patra variantă a aceluiași + * desen. Funcția rămâne în listă, deci cine vrea oscilația o alege oricând. + */ const FILE = [ { id: "lagrange", eticheta: "Lagrange" }, { id: "neville", eticheta: "Neville" }, - { id: "runge", eticheta: "Funcția Runge" }, { id: "spline", eticheta: "Spline" }, ] as const; @@ -71,34 +75,20 @@ const NODURI_IMPLICIT = 5; const ESANTIOANE = 400; /** - * Funcția cu care pornesc filele care lasă alegerea la cititor. + * Funcția cu care pornește interfața. * - * Sinusoida, nu funcția Runge: pe ea polinomul de interpolare stă lipit de - * curbă, deci se vede lucrul pe care îl arată fila — multiplicatorul, nivelul - * schemei, bucata de spline —, nu oscilația de la capete. Oscilația are fila ei. + * Sinusoida: polinomul stă lipit de curbă, deci se vede lucrul pe care îl arată + * fila, nu oscilația de la capete. E și funcția pe care cele două condiții de + * capăt ale spline-ului se despart cel mai tare — panta ei la capete e `±π`, + * iar spline-ul natural o ignoră din construcție. */ const FUNCTIE_IMPLICITA = "sinusoida"; -/** Funcția pe care lucrează fila: cea aleasă, iar la Runge chiar funcția Runge. */ -function functiaFilei(fila: Fila, ales: string) { - return getFunctieInterpolata(fila === "runge" ? "runge" : ales); -} - /* ───────────────────────── cadrul de desen ───────────────────────── */ /** Marginea laterală, ca fracție din interval — cât să nu cadă nodul pe ramă. */ const MARGINE_X = 0.06; -/** - * Cât se lasă graficul să urce peste înălțimea funcției, în multipli ai ei. - * - * Fără plafon, la 13 noduri echidistante oscilația Runge ar întinde axa până - * unde funcția însăși devine o linie dreaptă lipită de zero — adică desenul ar - * arăta oscilația și ar ascunde tocmai lucrul cu care se compară. Cu plafon, - * vârful iese din cadru, iar cât de mare e chiar scrie alături, în cifre. - */ -const PLAFON_VERTICAL = 1.6; - function extremele(valori: readonly number[]): [number, number] { let min = Number.POSITIVE_INFINITY; let max = Number.NEGATIVE_INFINITY; @@ -131,11 +121,9 @@ export function InterfataInterpolare() { const [idFunctie, setIdFunctie] = useState(FUNCTIE_IMPLICITA); const [cate, setCate] = useState(NODURI_IMPLICIT); const [fila, setFila] = useState("lagrange"); - const [k, setK] = useState(1); - const [nivel, setNivel] = useState(1); const [tipSpline, setTipSpline] = useState("natural"); - const functie = functiaFilei(fila, idFunctie); + const functie = getFunctieInterpolata(idFunctie); const [a, b] = functie.interval; const [noduri, setNoduri] = useState(() => @@ -148,8 +136,12 @@ export function InterfataInterpolare() { const [xEval, setXEval] = useState(() => a + (b - a) * 0.62); const n = noduri.length - 1; - const kAles = Math.min(k, n); - const nivelAles = Math.min(nivel, n); + + // Nivelurile schemei Neville se derulează, nu se trag dintr-un cursor din + // panoul de parametri: sunt pași ai unui algoritm, iar pașii se iau cu + // săgețile, sub desen, ca peste tot pe site. + const derulareNivel = useDerulare(n + 1); + const nivelAles = Math.min(derulareNivel.pas, n); /** Nodurile se pot fi mutat de pe funcție; butonul de resetare le pune la loc. */ const asazaPeFunctie = (cateNoduri = cate, f = functie) => @@ -165,27 +157,9 @@ export function InterfataInterpolare() { asazaPe(getFunctieInterpolata(id)); }; - /** - * Fila cu funcția Runge își aduce funcția cu ea, iar la ieșire o lasă în urmă. - * - * Fenomenul Runge **e** o afirmație despre o funcție anume, deci acolo n-ai ce - * alege; iar celelalte trei file arătau, până acum, tocmai pe ea — și un - * polinom care oscilează cu ±1 face de nerecunoscut și multiplicatorul lui - * Lagrange, și nivelurile lui Neville. Nodurile se reașază la schimbare, - * fiindcă cele vechi stăteau pe cealaltă curbă. - */ - const schimbaFila = (noua: Fila) => { - const vecheaFunctie = functiaFilei(fila, idFunctie); - const nouaFunctie = functiaFilei(noua, idFunctie); - setFila(noua); - if (nouaFunctie.id !== vecheaFunctie.id) asazaPe(nouaFunctie); - }; - const schimbaNumarul = (cateNoduri: number) => { setCate(cateNoduri); asazaPeFunctie(cateNoduri); - setK(Math.min(k, cateNoduri - 1)); - setNivel(Math.min(nivel, cateNoduri - 1)); }; /* ── ce se calculează ─────────────────────────────────────────────── */ @@ -219,9 +193,31 @@ export function InterfataInterpolare() { [spline, noduri, n], ); - const curbaMultiplicatorului = useMemo( - () => esantioneaza((x) => multiplicatorLagrange(noduri, kAles, x), domeniuX, ESANTIOANE), - [noduri, kAles, domeniuX], + /* + * Spline-ul cu **cealaltă** condiție de capăt, desenat odată cu cel ales. + * + * Diferența dintre „natural" și „tensionat" stă numai în capete și se vede + * doar comparând: cu o singură curbă pe ecran, apăsarea butonului muta câțiva + * pixeli pe care nimeni n-avea cum să-i țină minte. Așa, cele două curbe stau + * una lângă alta, iar butonul spune doar care dintre ele e subiectul. + */ + const splineCealalta = useMemo( + () => + splineCubic(noduri, tipSpline === "natural" ? "tensionat" : "natural", { + la0: functie.fPrim(noduri[0]?.x ?? a), + laN: functie.fPrim(noduri[n]?.x ?? b), + }), + [noduri, tipSpline, functie, n, a, b], + ); + + const curbaSplineuluiCelalalt = useMemo( + () => + esantioneaza( + (x) => evalueazaSpline(splineCealalta, x), + [noduri[0]!.x, noduri[n]!.x], + ESANTIOANE, + ), + [splineCealalta, noduri, n], ); const schema = useMemo(() => schemaNeville(noduri, xEval), [noduri, xEval]); @@ -253,15 +249,8 @@ export function InterfataInterpolare() { const domeniuY = useMemo(() => { const [minF, maxF] = extremele(curbaFunctiei.map((p) => p.y)); const inaltime = Math.max(maxF - minF, 0.5); - // Fila cu funcția Runge cere loc: acolo oscilația **e** subiectul, iar un - // vârf tăiat de ramă ar lăsa afirmația paginii nedemonstrată. Pe celelalte - // file, aceeași margine ar împinge funcția într-o fâșie subțire din mijloc. - const generos = fila === "runge"; - return [ - minF - (generos ? PLAFON_VERTICAL * 0.55 : 0.45) * inaltime, - maxF + (generos ? PLAFON_VERTICAL : 0.55) * inaltime, - ]; - }, [curbaFunctiei, fila]); + return [minF - 0.45 * inaltime, maxF + 0.55 * inaltime]; + }, [curbaFunctiei]); /* ── cifrele care se citesc ───────────────────────────────────────── */ @@ -273,11 +262,6 @@ export function InterfataInterpolare() { () => abatereMaxima((x) => evalueazaSpline(spline, x), functie.f, [a, b], 2000), [spline, functie, a, b], ); - const amplitudine = useMemo( - () => extremele(curbaPolinomului.map((p) => Math.abs(p.y)))[1], - [curbaPolinomului], - ); - /* ── mutarea unui nod ─────────────────────────────────────────────── */ const mutaNodul = (i: number, punct: Punct) => { @@ -295,11 +279,8 @@ export function InterfataInterpolare() { return [Math.min(minim, maxim), Math.max(minim, maxim)]; }; - const noduriSeTrag = fila !== "runge"; - /* ── desenul ──────────────────────────────────────────────────────── */ - const punctulMultiplicatorului = { x: noduri[kAles]?.x ?? a, y: 1 }; const intrariNivel = schema.niveluri[nivelAles] ?? []; /* @@ -379,32 +360,26 @@ export function InterfataInterpolare() { /> ))} - {/* Polinomul de interpolare. **Nu** și pe fila spline: acolo - stătea doar ca termen de comparație, iar comparația aceea o - face deja fila Runge, cu tot cu cifre. O a patra curbă peste - celelalte trei nu mai spunea nimic în plus. */} - {(fila === "lagrange" || fila === "runge") && ( - - )} - - {/* Multiplicatorul nodului ales: 1 în nodul lui, 0 în celelalte. */} + {/* Polinomul de interpolare — **o singură linie**. Multiplicatorul + nu se mai desenează separat: polinomul e chiar suma + multiplicatorilor, iar cu amândouă pe ecran ochiul nu mai știa + care e rezultatul. Ce arată fila se vede trăgând de numărul de + noduri: linia trece prin toate, oricâte ar fi. */} {fila === "lagrange" && ( - - )} - - {(fila === "runge" || fila === "spline") && ( - + )} - {/* Verticala în care se evaluează schema Neville. */} - {fila === "neville" && ( - + {fila === "spline" && ( + <> + {/* Cealaltă condiție de capăt, ca termen de comparație. */} + + + )} {fila === "neville" && @@ -418,40 +393,20 @@ export function InterfataInterpolare() { /> ))} - {fila === "neville" && nivelAles === n && ( - - )} - - {/* Nodurile. Pe fila Runge stau pe loc — fenomenul e chiar despre - noduri echidistante. */} - {noduri.map((nod, i) => - noduriSeTrag ? ( - mutaNodul(i, p)} - limiteX={limiteleNodului(i)} - limiteY={[domeniuY[0], domeniuY[1]]} - descriere={`Nodul x${indice(i)}, la ${zecimale(nod.x, 2)}; ${zecimale(nod.y, 2)}`} - /> - ) : ( - - ), - )} - - {/* `lₖ(xₖ) = 1`: punctul care explică de ce formula arată așa. */} - {fila === "lagrange" && ( - ( + mutaNodul(i, p)} + limiteX={limiteleNodului(i)} + limiteY={[domeniuY[0], domeniuY[1]]} + descriere={`Nodul x${indice(i)}, la ${zecimale(nod.x, 2)}; ${zecimale(nod.y, 2)}`} /> - )} + ))} - - {fila === "neville" && ( - - )}
{ schimbaNumarul(NODURI_IMPLICIT); - setK(1); - setNivel(1); setTipSpline("natural"); setXEval(a + (b - a) * 0.62); + derulareNivel.setPas(0); }} incorporat className="border-bordura min-w-0 border-t lg:border-t-0 lg:border-l" @@ -484,10 +430,7 @@ export function InterfataInterpolare() { - {/* Pe fila Runge lista rămâne pe ecran, dar închisă: funcția e - chiar subiectul filei, iar scoasă de tot ar face panoul să se - scurteze la fiecare trecere prin tab. */} - - - Multiplicatorul nodului {`x${indice(kAles)}`} - -
- {noduri.map((_, i) => ( - - ))} -
+

{functie.ceArata}

, -
-
-
- - x, punctul evaluat - - - {zecimale(xEval, 2)} - -
- setXEval(v ?? a)} - /> -
-
-
- Nivelul schemei - - {nivelAles} din {n} - -
- setNivel(v ?? 0)} - /> +
+
+ + x, punctul evaluat + + + {zecimale(xEval, 2)} +
-
, - -
- Noduri echidistante -

{functie.ceArata}

+ setXEval(v ?? a)} + />
,
@@ -613,7 +517,6 @@ export function InterfataInterpolare() { {cifrele({ fila, n, - amplitudine, eroarePolinom, eroareSpline, schema, @@ -637,6 +540,29 @@ export function InterfataInterpolare() {
+ + {/* Nivelurile schemei, ca pași: bara de derulare stă sub desen, iar + triunghiul de valori sub ea. Așa graficul ia toată înălțimea ramei, + iar tabelul nu-l mai împinge în sus cu fiecare nivel calculat. */} + {fila === "neville" && ( + <> + + + + + )}
); } @@ -735,8 +661,8 @@ function etichetaFilei(fila: Fila): string { function formulaFilei(fila: Fila, tipSpline: TipSpline): string { if (fila === "lagrange") { return ( - `l_k(x) = \\prod_{\\substack{i=0 \\\\ i \\neq k}}^{n} \\frac{x - x_i}{x_k - x_i}` + - `\\qquad \\htmlId{unu}{l_k(x_k) = 1} \\qquad l_k(x_i) = 0` + "P_n(x) = \\sum_{k=0}^{n} f(x_k)\\, \\htmlId{multiplicator}{l_k(x)}, \\qquad " + + "l_k(x) = \\prod_{\\substack{i=0 \\\\ i \\neq k}}^{n} \\frac{x - x_i}{x_k - x_i}" ); } @@ -748,10 +674,6 @@ function formulaFilei(fila: Fila, tipSpline: TipSpline): string { ); } - if (fila === "runge") { - return "\\htmlId{eroare}{\\max_{x \\in [a,b]} |P_n(x) - f(x)|}"; - } - return ( "s_i(x) = a_i + b_i(x - x_i) + c_i(x - x_i)^2 + d_i(x - x_i)^3 \\qquad " + (tipSpline === "natural" @@ -761,18 +683,15 @@ function formulaFilei(fila: Fila, tipSpline: TipSpline): string { } function evidentiereaFilei(fila: Fila, nivel: number): string[] { - if (fila === "lagrange") return ["unu"]; + if (fila === "lagrange") return ["multiplicator"]; if (fila === "neville") return nivel === 0 ? [] : ["pondere-stanga", "pondere-dreapta"]; - if (fila === "runge") return ["eroare"]; return ["capete"]; } function cifrele({ fila, n, - amplitudine, eroarePolinom, - eroareSpline, schema, nivel, spline, @@ -780,7 +699,6 @@ function cifrele({ }: { fila: Fila; n: number; - amplitudine: number; eroarePolinom: number; eroareSpline: number; schema: ReturnType; @@ -798,19 +716,10 @@ function cifrele({ return [ ["nivelul", `${nivel} din ${n}`], ["polinoame", String(schema.niveluri[nivel]?.length ?? 0)], - ["P₀ₙ(x)", zecimale(schema.rezultat, 6)], - ]; - } - if (fila === "runge") { - return [ - ["max |Pₙ|", zecimale(amplitudine, 4)], - ["max |Pₙ − f|", zecimale(eroarePolinom, 4)], - ["max |s − f|", zecimale(eroareSpline, 4)], ]; } return [ ["bucăți", String(Math.max(0, noduri.length - 1))], - ["max |s − f|", zecimale(eroareSpline, 4)], ["s″(x₀)", zecimale(2 * (spline.c[0] ?? 0), 3)], ]; } diff --git a/src/components/content/InterfataNewtonCotes.tsx b/src/components/content/InterfataNewtonCotes.tsx index 3e415fa..523681f 100644 --- a/src/components/content/InterfataNewtonCotes.tsx +++ b/src/components/content/InterfataNewtonCotes.tsx @@ -357,19 +357,20 @@ export function InterfataNewtonCotes() { controlul principal al paginii, iar de el se trage privind figura, nu citind o etichetă. */}
+ {/* Cifra stă lipită de litera ei: „N = 4" se citește ca un lucru, + pe când `N` la stânga și `4` la dreapta, aruncate în capetele + rândului, se citeau ca două. `h` rămâne singur la dreapta, + fiindcă e cealaltă mărime. */}
- Subintervale - N - - - {n} - {rezultat && ( - - h = {zecimale(rezultat.h, 4)} - - )} + Subintervale{" "} + N = {n} + {rezultat && ( + + h = {zecimale(rezultat.h, 4)} + + )}
@@ -301,7 +305,8 @@ export function MatrixGrid({ data-stare={stare} style={{ gridColumn: colDate(j), gridRow: primaLinieDate + i }} className={cn( - "duration-mediu ease-standard min-w-11 rounded-md border-2 px-2 py-1.5 text-center transition-colors", + "duration-mediu ease-standard rounded-md border-2 text-center transition-colors", + corp === "foarte-mare" ? "min-w-14 px-3 py-2" : "min-w-11 px-2 py-1.5", // Linia/coloana activă stau dedesubt, ca fundal; starea // celulei se desenează peste ele, deci nu se pierde. // `/20` fiindcă `--viz-interval` e culoare plină: