121 lines
6.1 KiB
TypeScript
121 lines
6.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
|
|
const StandardFormWidget: React.FC = () => {
|
|
const [A, setA] = useState(2);
|
|
const [B, setB] = useState(3);
|
|
const [C, setC] = useState(12);
|
|
|
|
// Intercepts
|
|
const xInt = A !== 0 ? C / A : null;
|
|
const yInt = B !== 0 ? C / B : null;
|
|
|
|
// Vis
|
|
const range = 15;
|
|
const scale = 15;
|
|
const center = 150;
|
|
const toPx = (val: number, isY = false) => isY ? center - val * scale : center + val * scale;
|
|
|
|
// Line points
|
|
// If B!=0, y = (C - Ax)/B. If A!=0, x = (C - By)/A.
|
|
let p1, p2;
|
|
if (B !== 0) {
|
|
p1 = { x: -range, y: (C - A * -range) / B };
|
|
p2 = { x: range, y: (C - A * range) / B };
|
|
} else {
|
|
// Vertical line x = C/A
|
|
p1 = { x: C/A, y: -range };
|
|
p2 = { x: C/A, y: range };
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex justify-center mb-4">
|
|
<div className="px-6 py-3 bg-slate-800 text-white rounded-xl shadow-md text-2xl font-mono font-bold tracking-wider">
|
|
{A}x + {B}y = {C}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
<div className="bg-indigo-50 p-3 rounded-lg border border-indigo-100">
|
|
<label className="text-xs font-bold text-indigo-800 uppercase block mb-1">A (x-coeff)</label>
|
|
<input type="number" value={A} onChange={e => setA(Number(e.target.value))} className="w-full p-1 border rounded text-center font-bold"/>
|
|
</div>
|
|
<div className="bg-emerald-50 p-3 rounded-lg border border-emerald-100">
|
|
<label className="text-xs font-bold text-emerald-800 uppercase block mb-1">B (y-coeff)</label>
|
|
<input type="number" value={B} onChange={e => setB(Number(e.target.value))} className="w-full p-1 border rounded text-center font-bold"/>
|
|
</div>
|
|
<div className="bg-amber-50 p-3 rounded-lg border border-amber-100">
|
|
<label className="text-xs font-bold text-amber-800 uppercase block mb-1">C (constant)</label>
|
|
<input type="number" value={C} onChange={e => setC(Number(e.target.value))} className="w-full p-1 border rounded text-center font-bold"/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col md:flex-row gap-8">
|
|
<div className="w-full md:w-1/3 space-y-4">
|
|
<div className="p-4 bg-slate-50 rounded-lg border border-slate-200">
|
|
<h4 className="font-bold text-slate-700 mb-2 border-b pb-1">Cover-Up Method</h4>
|
|
|
|
<div className="mb-4">
|
|
<p className="text-xs text-slate-500 uppercase font-bold mb-1">Find X-Intercept (Set y=0)</p>
|
|
<div className="font-mono text-sm bg-white p-2 rounded border border-slate-200 text-slate-600">
|
|
{A}x = {C} <br/>
|
|
x = {C} / {A} <br/>
|
|
<span className="text-indigo-600 font-bold">x = {xInt !== null ? xInt.toFixed(2) : 'Undefined'}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-xs text-slate-500 uppercase font-bold mb-1">Find Y-Intercept (Set x=0)</p>
|
|
<div className="font-mono text-sm bg-white p-2 rounded border border-slate-200 text-slate-600">
|
|
{B}y = {C} <br/>
|
|
y = {C} / {B} <br/>
|
|
<span className="text-emerald-600 font-bold">y = {yInt !== null ? yInt.toFixed(2) : 'Undefined'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-full md:flex-1 h-[300px] border border-slate-200 rounded-lg relative bg-white overflow-hidden">
|
|
<svg width="100%" height="100%" viewBox="0 0 300 300" className="absolute">
|
|
{/* Grid */}
|
|
<defs>
|
|
<pattern id="sf-grid" width={scale} height={scale} patternUnits="userSpaceOnUse">
|
|
<path d={`M ${scale} 0 L 0 0 0 ${scale}`} fill="none" stroke="#f1f5f9" strokeWidth="1"/>
|
|
</pattern>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill="url(#sf-grid)" />
|
|
|
|
{/* Axes */}
|
|
<line x1="0" y1={center} x2="300" y2={center} stroke="#94a3b8" strokeWidth="2" />
|
|
<line x1={center} y1="0" x2={center} y2="300" stroke="#94a3b8" strokeWidth="2" />
|
|
|
|
{/* Line */}
|
|
<line
|
|
x1={toPx(p1.x)} y1={toPx(p1.y, true)}
|
|
x2={toPx(p2.x)} y2={toPx(p2.y, true)}
|
|
stroke="#0f172a" strokeWidth="3"
|
|
/>
|
|
|
|
{/* Intercepts */}
|
|
{xInt !== null && Math.abs(xInt) <= range && (
|
|
<g>
|
|
<circle cx={toPx(xInt)} cy={center} r="5" fill="#4f46e5" stroke="white" strokeWidth="2"/>
|
|
<text x={toPx(xInt)} y={center + 20} textAnchor="middle" className="text-xs font-bold fill-indigo-700">{xInt.toFixed(1)}</text>
|
|
</g>
|
|
)}
|
|
{yInt !== null && Math.abs(yInt) <= range && (
|
|
<g>
|
|
<circle cx={center} cy={toPx(yInt, true)} r="5" fill="#10b981" stroke="white" strokeWidth="2"/>
|
|
<text x={center + 10} y={toPx(yInt, true)} dominantBaseline="middle" className="text-xs font-bold fill-emerald-700">{yInt.toFixed(1)}</text>
|
|
</g>
|
|
)}
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StandardFormWidget; |