feat(lessons): add lessons from client db
This commit is contained in:
127
src/components/lessons/DataModifierWidget.tsx
Normal file
127
src/components/lessons/DataModifierWidget.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const DataModifierWidget: React.FC = () => {
|
||||
const initialData = [10, 12, 13, 15, 16, 18, 20];
|
||||
const [data, setData] = useState<number[]>(initialData);
|
||||
|
||||
const calculateStats = (arr: number[]) => {
|
||||
const sorted = [...arr].sort((a, b) => a - b);
|
||||
const sum = sorted.reduce((a, b) => a + b, 0);
|
||||
const mean = sum / sorted.length;
|
||||
const min = sorted[0];
|
||||
const max = sorted[sorted.length - 1];
|
||||
const range = max - min;
|
||||
|
||||
// Median
|
||||
const mid = Math.floor(sorted.length / 2);
|
||||
const median = sorted.length % 2 !== 0
|
||||
? sorted[mid]
|
||||
: (sorted[mid - 1] + sorted[mid]) / 2;
|
||||
|
||||
// SD (Population)
|
||||
const variance = sorted.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / sorted.length;
|
||||
const sd = Math.sqrt(variance);
|
||||
|
||||
return { mean, median, range, sd, sorted };
|
||||
};
|
||||
|
||||
const stats = calculateStats(data);
|
||||
|
||||
// Operations
|
||||
const reset = () => setData(initialData);
|
||||
|
||||
const addConstant = (k: number) => {
|
||||
setData(prev => prev.map(n => n + k));
|
||||
};
|
||||
|
||||
const multiplyConstant = (k: number) => {
|
||||
setData(prev => prev.map(n => n * k));
|
||||
};
|
||||
|
||||
const addOutlier = (val: number) => {
|
||||
setData(prev => [...prev, val]);
|
||||
};
|
||||
|
||||
// Visual scaling
|
||||
const minDisplay = Math.min(0, ...data) - 5;
|
||||
const maxDisplay = Math.max(Math.max(...data), 100) + 10;
|
||||
const getX = (val: number) => ((val - minDisplay) / (maxDisplay - minDisplay)) * 100;
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
{/* Controls */}
|
||||
<div className="w-full md:w-1/3 space-y-3">
|
||||
<h4 className="font-bold text-slate-700 mb-2">Apply Transformation</h4>
|
||||
<button onClick={() => addConstant(5)} className="w-full py-2 px-4 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg font-bold text-sm text-left transition-colors">
|
||||
+ Add 5 (Shift Right)
|
||||
</button>
|
||||
<button onClick={() => multiplyConstant(2)} className="w-full py-2 px-4 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg font-bold text-sm text-left transition-colors">
|
||||
× Multiply by 2 (Scale)
|
||||
</button>
|
||||
<button onClick={() => addOutlier(80)} className="w-full py-2 px-4 bg-rose-100 hover:bg-rose-200 text-rose-900 rounded-lg font-bold text-sm text-left transition-colors border border-rose-200">
|
||||
⚠ Add Outlier (80)
|
||||
</button>
|
||||
<button onClick={reset} className="w-full py-2 px-4 bg-slate-100 hover:bg-slate-200 text-slate-600 rounded-lg font-bold text-sm text-left transition-colors mt-4">
|
||||
↺ Reset Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Visualization */}
|
||||
<div className="flex-1">
|
||||
{/* Stats Panel */}
|
||||
<div className="grid grid-cols-4 gap-2 mb-6 text-center">
|
||||
<div className="p-2 bg-slate-50 border border-slate-200 rounded">
|
||||
<div className="text-xs uppercase font-bold text-slate-500">Mean</div>
|
||||
<div className="font-mono font-bold text-lg text-indigo-600">{stats.mean.toFixed(1)}</div>
|
||||
</div>
|
||||
<div className="p-2 bg-slate-50 border border-slate-200 rounded">
|
||||
<div className="text-xs uppercase font-bold text-slate-500">Median</div>
|
||||
<div className="font-mono font-bold text-lg text-emerald-600">{stats.median.toFixed(1)}</div>
|
||||
</div>
|
||||
<div className="p-2 bg-slate-50 border border-slate-200 rounded">
|
||||
<div className="text-xs uppercase font-bold text-slate-500">Range</div>
|
||||
<div className="font-mono font-bold text-lg text-slate-700">{stats.range.toFixed(0)}</div>
|
||||
</div>
|
||||
<div className="p-2 bg-slate-50 border border-slate-200 rounded">
|
||||
<div className="text-xs uppercase font-bold text-slate-500">SD</div>
|
||||
<div className="font-mono font-bold text-lg text-slate-700">{stats.sd.toFixed(1)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dot Plot */}
|
||||
<div className="h-32 relative border-b border-slate-300">
|
||||
{stats.sorted.map((val, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="absolute w-4 h-4 rounded-full bg-indigo-500 shadow-sm border border-white transform -translate-x-1/2"
|
||||
style={{ left: `${getX(val)}%`, bottom: '10px' }}
|
||||
title={`Value: ${val}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Mean Marker */}
|
||||
<div className="absolute top-0 bottom-0 w-0.5 bg-indigo-300 border-l border-dashed border-indigo-500 opacity-60" style={{ left: `${getX(stats.mean)}%` }}>
|
||||
<span className="absolute -top-6 left-1/2 -translate-x-1/2 text-xs font-bold text-indigo-600 bg-white px-1 rounded shadow-sm">x̄</span>
|
||||
</div>
|
||||
|
||||
{/* Median Marker */}
|
||||
<div className="absolute top-0 bottom-0 w-0.5 bg-emerald-300 border-l border-dashed border-emerald-500 opacity-60" style={{ left: `${getX(stats.median)}%` }}>
|
||||
<span className="absolute -bottom-6 left-1/2 -translate-x-1/2 text-xs font-bold text-emerald-600 bg-white px-1 rounded shadow-sm">M</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-sm text-slate-500 leading-relaxed bg-slate-50 p-3 rounded">
|
||||
{data.length > 7 ? (
|
||||
<span className="text-rose-600 font-bold">Notice how the Mean is pulled towards the outlier, while the Median barely moves!</span>
|
||||
) : (
|
||||
"Experiment with adding constants and multipliers to see which stats change."
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataModifierWidget;
|
||||
Reference in New Issue
Block a user