Files
edbridge-scholars/src/components/lessons/SlopeInterceptWidget.tsx
2026-03-01 20:24:14 +06:00

93 lines
4.3 KiB
TypeScript

import React, { useState } from 'react';
const SlopeInterceptWidget: React.FC = () => {
const [m, setM] = useState(2);
const [b, setB] = useState(1);
// Visualization config
const range = 10;
const scale = 25; // px per unit
const center = 150;
const toPx = (val: number, isY = false) => isY ? center - val * scale : center + val * scale;
// Points for triangle
const p1 = { x: 0, y: b };
const p2 = { x: 1, y: m * 1 + b };
// Triangle vertex (1, b)
const p3 = { x: 1, y: b };
return (
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
<div className="flex flex-col md:flex-row gap-8">
<div className="w-full md:w-1/3 space-y-6">
<div className="p-4 bg-slate-50 rounded-xl border border-slate-200 text-center">
<div className="text-sm text-slate-500 font-bold uppercase mb-1">Equation</div>
<div className="text-2xl font-mono font-bold text-slate-800">
y = <span className="text-blue-600">{m}</span>x + <span className="text-rose-600">{b}</span>
</div>
</div>
<div>
<label className="text-xs font-bold text-blue-600 uppercase">Slope (m) = {m}</label>
<input
type="range" min="-5" max="5" step="0.5"
value={m} onChange={e => setM(parseFloat(e.target.value))}
className="w-full h-2 bg-blue-100 rounded-lg appearance-none cursor-pointer accent-blue-600 mt-2"
/>
<p className="text-xs text-slate-400 mt-1">Rate of Change (Rise / Run)</p>
</div>
<div>
<label className="text-xs font-bold text-rose-600 uppercase">Y-Intercept (b) = {b}</label>
<input
type="range" min="-5" max="5" step="1"
value={b} onChange={e => setB(parseFloat(e.target.value))}
className="w-full h-2 bg-rose-100 rounded-lg appearance-none cursor-pointer accent-rose-600 mt-2"
/>
<p className="text-xs text-slate-400 mt-1">Starting Value (when x=0)</p>
</div>
</div>
<div className="w-full md:flex-1 h-[300px] bg-white border border-slate-200 rounded-xl relative overflow-hidden">
<svg width="100%" height="100%" viewBox="0 0 300 300" className="absolute top-0 left-0">
<defs>
<pattern id="si-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(#si-grid)" />
{/* Axes */}
<line x1="0" y1={center} x2="300" y2={center} stroke="#cbd5e1" strokeWidth="2" />
<line x1={center} y1="0" x2={center} y2="300" stroke="#cbd5e1" strokeWidth="2" />
{/* The Line */}
<line
x1={toPx(-range)} y1={toPx(m * -range + b, true)}
x2={toPx(range)} y2={toPx(m * range + b, true)}
stroke="#1e293b" strokeWidth="3"
/>
{/* Slope Triangle (between x=0 and x=1) */}
<path
d={`M ${toPx(p1.x)} ${toPx(p1.y, true)} L ${toPx(p3.x)} ${toPx(p3.y, true)} L ${toPx(p2.x)} ${toPx(p2.y, true)} Z`}
fill="rgba(37, 99, 235, 0.1)" stroke="#2563eb" strokeWidth="1" strokeDasharray="4,2"
/>
{/* Intercept Point */}
<circle cx={toPx(0)} cy={toPx(b, true)} r="5" fill="#e11d48" stroke="white" strokeWidth="2" />
<text x={toPx(0) + 10} y={toPx(b, true)} className="text-xs font-bold fill-rose-600">b={b}</text>
{/* Rise/Run Labels */}
<text x={toPx(0.5)} y={toPx(b, true) + (m>0 ? 15 : -10)} textAnchor="middle" className="text-[10px] font-bold fill-blue-400">Run: 1</text>
<text x={toPx(1) + 5} y={toPx(b + m/2, true)} className="text-[10px] font-bold fill-blue-600">Rise: {m}</text>
</svg>
</div>
</div>
</div>
);
};
export default SlopeInterceptWidget;