chore(build): refactor codebase for production
This commit is contained in:
@ -1,86 +1,103 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState } from "react";
|
||||
|
||||
const HistogramBuilderWidget: React.FC = () => {
|
||||
const [mode, setMode] = useState<'count' | 'percent'>('count');
|
||||
|
||||
const [mode, setMode] = useState<"count" | "percent">("count");
|
||||
|
||||
// Data: [60, 70), [70, 80), [80, 90), [90, 100)
|
||||
const data = [
|
||||
{ bin: '60-70', count: 4, label: '60s' },
|
||||
{ bin: '70-80', count: 9, label: '70s' },
|
||||
{ bin: '80-90', count: 6, label: '80s' },
|
||||
{ bin: '90-100', count: 1, label: '90s' },
|
||||
{ bin: "60-70", count: 4, label: "60s" },
|
||||
{ bin: "70-80", count: 9, label: "70s" },
|
||||
{ bin: "80-90", count: 6, label: "80s" },
|
||||
{ bin: "90-100", count: 1, label: "90s" },
|
||||
];
|
||||
|
||||
const total = data.reduce((acc, curr) => acc + curr.count, 0); // 20
|
||||
|
||||
const maxCount = Math.max(...data.map(d => d.count));
|
||||
|
||||
const maxCount = Math.max(...data.map((d) => d.count));
|
||||
const maxPercent = maxCount / total; // 0.45
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="font-bold text-slate-700">Test Scores Distribution</h3>
|
||||
<div className="flex bg-slate-100 p-1 rounded-lg">
|
||||
<button
|
||||
onClick={() => setMode('count')}
|
||||
className={`px-4 py-1.5 text-sm font-bold rounded-md transition-all ${mode === 'count' ? 'bg-white shadow-sm text-indigo-600' : 'text-slate-500 hover:text-slate-700'}`}
|
||||
>
|
||||
Frequency (Count)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode('percent')}
|
||||
className={`px-4 py-1.5 text-sm font-bold rounded-md transition-all ${mode === 'percent' ? 'bg-white shadow-sm text-rose-600' : 'text-slate-500 hover:text-slate-700'}`}
|
||||
>
|
||||
Relative Freq (%)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="font-bold text-slate-700">Test Scores Distribution</h3>
|
||||
<div className="flex bg-slate-100 p-1 rounded-lg">
|
||||
<button
|
||||
onClick={() => setMode("count")}
|
||||
className={`px-4 py-1.5 text-sm font-bold rounded-md transition-all ${mode === "count" ? "bg-white shadow-sm text-indigo-600" : "text-slate-500 hover:text-slate-700"}`}
|
||||
>
|
||||
Frequency (Count)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode("percent")}
|
||||
className={`px-4 py-1.5 text-sm font-bold rounded-md transition-all ${mode === "percent" ? "bg-white shadow-sm text-rose-600" : "text-slate-500 hover:text-slate-700"}`}
|
||||
>
|
||||
Relative Freq (%)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative h-64 border-b-2 border-slate-200 flex items-end px-8 gap-1">
|
||||
{/* Y Axis Labels */}
|
||||
<div className="absolute left-0 top-0 bottom-0 flex flex-col justify-between text-xs font-mono text-slate-400 py-2">
|
||||
<span>{mode === 'count' ? maxCount + 1 : ((maxPercent + 0.05)*100).toFixed(0) + '%'}</span>
|
||||
<span>{mode === 'count' ? Math.round((maxCount+1)/2) : (((maxPercent + 0.05)/2)*100).toFixed(0) + '%'}</span>
|
||||
<span>0</span>
|
||||
</div>
|
||||
<div className="relative h-64 border-b-2 border-slate-200 flex items-end px-8 gap-1">
|
||||
{/* Y Axis Labels */}
|
||||
<div className="absolute left-0 top-0 bottom-0 flex flex-col justify-between text-xs font-mono text-slate-400 py-2">
|
||||
<span>
|
||||
{mode === "count"
|
||||
? maxCount + 1
|
||||
: ((maxPercent + 0.05) * 100).toFixed(0) + "%"}
|
||||
</span>
|
||||
<span>
|
||||
{mode === "count"
|
||||
? Math.round((maxCount + 1) / 2)
|
||||
: (((maxPercent + 0.05) / 2) * 100).toFixed(0) + "%"}
|
||||
</span>
|
||||
<span>0</span>
|
||||
</div>
|
||||
|
||||
{data.map((d, i) => {
|
||||
const heightRatio = d.count / maxCount; // Normalize to max height of graph area roughly
|
||||
// Actually map 0 to maxScale
|
||||
const maxScale = mode === 'count' ? maxCount + 1 : (maxPercent + 0.05);
|
||||
const val = mode === 'count' ? d.count : d.count / total;
|
||||
const hPercent = (val / maxScale) * 100;
|
||||
{data.map((d, i) => {
|
||||
// Normalize to max height of graph area roughly
|
||||
// Actually map 0 to maxScale
|
||||
const maxScale = mode === "count" ? maxCount + 1 : maxPercent + 0.05;
|
||||
const val = mode === "count" ? d.count : d.count / total;
|
||||
const hPercent = (val / maxScale) * 100;
|
||||
|
||||
return (
|
||||
<div key={i} className="flex-1 flex flex-col justify-end group relative h-full">
|
||||
{/* Tooltip */}
|
||||
<div className="opacity-0 group-hover:opacity-100 absolute -top-10 left-1/2 -translate-x-1/2 bg-slate-800 text-white text-xs py-1 px-2 rounded pointer-events-none transition-opacity z-10 whitespace-nowrap">
|
||||
{d.bin}: {mode === 'count' ? d.count : `${(d.count/total*100).toFixed(0)}%`}
|
||||
</div>
|
||||
|
||||
{/* Bar */}
|
||||
<div
|
||||
className={`w-full transition-all duration-500 rounded-t ${mode === 'count' ? 'bg-indigo-500 group-hover:bg-indigo-600' : 'bg-rose-500 group-hover:bg-rose-600'}`}
|
||||
style={{ height: `${hPercent}%` }}
|
||||
></div>
|
||||
|
||||
{/* Bin Label */}
|
||||
<div className="absolute -bottom-6 w-full text-center text-xs font-bold text-slate-500">
|
||||
{d.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 p-4 bg-slate-50 rounded-xl border border-slate-200">
|
||||
<p className="text-sm text-slate-600">
|
||||
<strong>Key Takeaway:</strong> Notice that the <span className="font-bold text-slate-800">shape</span> of the distribution stays exactly the same.
|
||||
Only the <span className="font-bold text-slate-800">Y-axis scale</span> changes.
|
||||
</p>
|
||||
</div>
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-1 flex flex-col justify-end group relative h-full"
|
||||
>
|
||||
{/* Tooltip */}
|
||||
<div className="opacity-0 group-hover:opacity-100 absolute -top-10 left-1/2 -translate-x-1/2 bg-slate-800 text-white text-xs py-1 px-2 rounded pointer-events-none transition-opacity z-10 whitespace-nowrap">
|
||||
{d.bin}:{" "}
|
||||
{mode === "count"
|
||||
? d.count
|
||||
: `${((d.count / total) * 100).toFixed(0)}%`}
|
||||
</div>
|
||||
|
||||
{/* Bar */}
|
||||
<div
|
||||
className={`w-full transition-all duration-500 rounded-t ${mode === "count" ? "bg-indigo-500 group-hover:bg-indigo-600" : "bg-rose-500 group-hover:bg-rose-600"}`}
|
||||
style={{ height: `${hPercent}%` }}
|
||||
></div>
|
||||
|
||||
{/* Bin Label */}
|
||||
<div className="absolute -bottom-6 w-full text-center text-xs font-bold text-slate-500">
|
||||
{d.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 p-4 bg-slate-50 rounded-xl border border-slate-200">
|
||||
<p className="text-sm text-slate-600">
|
||||
<strong>Key Takeaway:</strong> Notice that the{" "}
|
||||
<span className="font-bold text-slate-800">shape</span> of the
|
||||
distribution stays exactly the same. Only the{" "}
|
||||
<span className="font-bold text-slate-800">Y-axis scale</span>{" "}
|
||||
changes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HistogramBuilderWidget;
|
||||
export default HistogramBuilderWidget;
|
||||
|
||||
Reference in New Issue
Block a user