159 lines
5.1 KiB
TypeScript
159 lines
5.1 KiB
TypeScript
import React, { useState } from "react";
|
|
|
|
const ExponentialExplorer: React.FC = () => {
|
|
const [a, setA] = useState(2); // Initial Value
|
|
const [b, setB] = useState(1.5); // Growth Factor
|
|
const [k, setK] = useState(0); // Horizontal Asymptote shift
|
|
|
|
const width = 300;
|
|
const range = 5; // x range -5 to 5
|
|
|
|
// Mapping
|
|
const toPx = (v: number, isY = false) => {
|
|
const scale = width / (range * 2);
|
|
const center = width / 2;
|
|
return isY ? center - v * scale : center + v * scale;
|
|
};
|
|
|
|
const generatePath = () => {
|
|
let d = "";
|
|
for (let x = -range; x <= range; x += 0.1) {
|
|
const y = a * Math.pow(b, x) + k;
|
|
if (y > range * 2 || y < -range * 2) continue; // Clip
|
|
const px = toPx(x);
|
|
const py = toPx(y, true);
|
|
d += d ? ` L ${px} ${py}` : `M ${px} ${py}`;
|
|
}
|
|
return d;
|
|
};
|
|
|
|
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-violet-50 rounded-xl border border-violet-100 text-center">
|
|
<div className="text-xs font-bold text-violet-400 uppercase mb-1">
|
|
Standard Form
|
|
</div>
|
|
<div className="text-xl font-mono font-bold text-violet-900">
|
|
y = <span className="text-indigo-600">{a}</span> ·{" "}
|
|
<span className="text-emerald-600">{b}</span>
|
|
<sup>x</sup> {k >= 0 ? "+" : ""}{" "}
|
|
<span className="text-rose-600">{k}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="text-xs font-bold text-indigo-600 uppercase flex justify-between">
|
|
Initial Value (a) <span>{a}</span>
|
|
</label>
|
|
<input
|
|
type="range"
|
|
min="0.5"
|
|
max="5"
|
|
step="0.5"
|
|
value={a}
|
|
onChange={(e) => setA(parseFloat(e.target.value))}
|
|
className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-bold text-emerald-600 uppercase flex justify-between">
|
|
Growth Factor (b) <span>{b}</span>
|
|
</label>
|
|
<input
|
|
type="range"
|
|
min="0.1"
|
|
max="3"
|
|
step="0.1"
|
|
value={b}
|
|
onChange={(e) => setB(parseFloat(e.target.value))}
|
|
className="w-full h-2 bg-emerald-100 rounded-lg appearance-none cursor-pointer accent-emerald-600"
|
|
/>
|
|
<p className="text-xs text-slate-400 mt-1">
|
|
{b > 1 ? "Growth (b > 1)" : "Decay (0 < b < 1)"}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-bold text-rose-600 uppercase flex justify-between">
|
|
Vertical Shift (k) <span>{k}</span>
|
|
</label>
|
|
<input
|
|
type="range"
|
|
min="-3"
|
|
max="3"
|
|
step="1"
|
|
value={k}
|
|
onChange={(e) => setK(parseFloat(e.target.value))}
|
|
className="w-full h-2 bg-rose-100 rounded-lg appearance-none cursor-pointer accent-rose-600"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 flex justify-center">
|
|
<div className="relative w-[300px] h-[300px] bg-white border border-slate-200 rounded-xl overflow-hidden">
|
|
<svg width="100%" height="100%" viewBox="0 0 300 300">
|
|
<line
|
|
x1="0"
|
|
y1="150"
|
|
x2="300"
|
|
y2="150"
|
|
stroke="#cbd5e1"
|
|
strokeWidth="2"
|
|
/>
|
|
<line
|
|
x1="150"
|
|
y1="0"
|
|
x2="150"
|
|
y2="300"
|
|
stroke="#cbd5e1"
|
|
strokeWidth="2"
|
|
/>
|
|
|
|
{/* Asymptote */}
|
|
<line
|
|
x1="0"
|
|
y1={toPx(k, true)}
|
|
x2="300"
|
|
y2={toPx(k, true)}
|
|
stroke="#e11d48"
|
|
strokeWidth="1"
|
|
strokeDasharray="4,4"
|
|
/>
|
|
<text
|
|
x="10"
|
|
y={toPx(k, true) - 5}
|
|
className="text-xs font-bold fill-rose-500"
|
|
>
|
|
y = {k}
|
|
</text>
|
|
|
|
{/* Function */}
|
|
<path
|
|
d={generatePath()}
|
|
fill="none"
|
|
stroke="#8b5cf6"
|
|
strokeWidth="3"
|
|
/>
|
|
|
|
{/* Intercept */}
|
|
<circle
|
|
cx={toPx(0)}
|
|
cy={toPx(a + k, true)}
|
|
r="4"
|
|
fill="#4f46e5"
|
|
stroke="white"
|
|
strokeWidth="2"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ExponentialExplorer;
|