84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
|
|
const GrowthComparisonWidget: React.FC = () => {
|
|
const [linearRate, setLinearRate] = useState(10); // +10 per step
|
|
const [expRate, setExpRate] = useState(10); // +10% per step
|
|
const start = 100;
|
|
const steps = 10;
|
|
|
|
// Generate Data
|
|
const data = Array.from({ length: steps + 1 }, (_, i) => {
|
|
return {
|
|
x: i,
|
|
lin: start + linearRate * i,
|
|
exp: start * Math.pow(1 + expRate/100, i)
|
|
};
|
|
});
|
|
|
|
const maxY = Math.max(data[steps].lin, data[steps].exp);
|
|
|
|
// Scales
|
|
const width = 100;
|
|
const height = 60;
|
|
const getX = (i: number) => (i / steps) * width;
|
|
const getY = (val: number) => height - (val / maxY) * height; // Inverted Y
|
|
|
|
const linPath = `M ${data.map(d => `${getX(d.x)},${getY(d.lin)}`).join(' L ')}`;
|
|
const expPath = `M ${data.map(d => `${getX(d.x)},${getY(d.exp)}`).join(' L ')}`;
|
|
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
|
<div className="grid grid-cols-2 gap-8 mb-6">
|
|
<div>
|
|
<label className="text-xs font-bold text-indigo-600 uppercase">Linear Rate (+)</label>
|
|
<input
|
|
type="range" min="5" max="50" value={linearRate}
|
|
onChange={e => setLinearRate(Number(e.target.value))}
|
|
className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600 mt-2"
|
|
/>
|
|
<div className="text-right font-mono font-bold text-indigo-700">+{linearRate} / step</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-bold text-rose-600 uppercase">Exponential Rate (%)</label>
|
|
<input
|
|
type="range" min="2" max="30" value={expRate}
|
|
onChange={e => setExpRate(Number(e.target.value))}
|
|
className="w-full h-2 bg-rose-100 rounded-lg appearance-none cursor-pointer accent-rose-600 mt-2"
|
|
/>
|
|
<div className="text-right font-mono font-bold text-rose-700">+{expRate}% / step</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative h-64 w-full bg-slate-50 rounded-lg border border-slate-200 mb-6 overflow-hidden">
|
|
<svg viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none" className="w-full h-full p-4 overflow-visible">
|
|
{/* Grid */}
|
|
<line x1="0" y1={height} x2={width} y2={height} stroke="#cbd5e1" strokeWidth="0.5" />
|
|
<line x1="0" y1="0" x2="0" y2={height} stroke="#cbd5e1" strokeWidth="0.5" />
|
|
|
|
{/* Paths */}
|
|
<path d={linPath} fill="none" stroke="#4f46e5" strokeWidth="1" />
|
|
<path d={expPath} fill="none" stroke="#e11d48" strokeWidth="1" />
|
|
|
|
{/* Points */}
|
|
{data.map((d, i) => (
|
|
<g key={i}>
|
|
<circle cx={getX(d.x)} cy={getY(d.lin)} r="1" fill="#4f46e5" />
|
|
<circle cx={getX(d.x)} cy={getY(d.exp)} r="1" fill="#e11d48" />
|
|
</g>
|
|
))}
|
|
</svg>
|
|
{/* Labels */}
|
|
<div className="absolute top-2 right-2 text-xs font-bold bg-white/80 p-2 rounded shadow-sm">
|
|
<div className="text-indigo-600">Linear Final: {Math.round(data[steps].lin)}</div>
|
|
<div className="text-rose-600">Exp Final: {Math.round(data[steps].exp)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-sm text-slate-500">
|
|
Exponential growth eventually overtakes Linear growth, even if the linear rate seems larger at first!
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GrowthComparisonWidget; |