75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { ts } from '../../i18n/server';
|
|
import { getEnumValueColor } from '../../lib/consts';
|
|
|
|
export default function EnumBarChart({
|
|
counts,
|
|
globalCounts,
|
|
featureName,
|
|
}: {
|
|
counts: Record<string, number>;
|
|
globalCounts?: Record<string, number>;
|
|
featureName: string;
|
|
}) {
|
|
const entries = Object.entries(counts).sort(([, countA], [, countB]) => countB - countA);
|
|
const localTotal = entries.reduce((sum, [, c]) => sum + c, 0);
|
|
|
|
// When global counts are available, normalize both to percentages for comparison
|
|
const globalTotal = globalCounts ? Object.values(globalCounts).reduce((sum, c) => sum + c, 0) : 0;
|
|
|
|
const hasGlobal = globalCounts && globalTotal > 0;
|
|
|
|
// Compute max percentage across both datasets for consistent bar scaling
|
|
const maxPct = entries.reduce((max, [label, count]) => {
|
|
const localPct = localTotal > 0 ? count / localTotal : 0;
|
|
const globalPct = hasGlobal ? (globalCounts[label] ?? 0) / globalTotal : 0;
|
|
return Math.max(max, localPct, globalPct);
|
|
}, 0);
|
|
|
|
// Fallback to raw count scaling when no global data
|
|
const maxCount = Math.max(...entries.map(([, count]) => count), 1);
|
|
|
|
return (
|
|
<div className="space-y-1 mt-1">
|
|
{entries.map(([label, count]) => {
|
|
const localPct = localTotal > 0 ? count / localTotal : 0;
|
|
const globalPct = hasGlobal ? (globalCounts[label] ?? 0) / globalTotal : 0;
|
|
|
|
const localWidth = hasGlobal
|
|
? maxPct > 0
|
|
? (localPct / maxPct) * 100
|
|
: 0
|
|
: (count / maxCount) * 100;
|
|
const globalWidth = hasGlobal && maxPct > 0 ? (globalPct / maxPct) * 100 : 0;
|
|
|
|
const color = getEnumValueColor(featureName, label);
|
|
const barStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
|
|
|
|
return (
|
|
<div key={label} className="flex items-center gap-2 text-xs">
|
|
<span className="w-16 truncate text-warm-500 dark:text-warm-400 text-right shrink-0">
|
|
{ts(label)}
|
|
</span>
|
|
<div className="flex-1 h-3 bg-warm-100 dark:bg-navy-700 rounded overflow-hidden relative">
|
|
{hasGlobal && (
|
|
<div
|
|
className="absolute inset-y-0 left-0 bg-warm-300/60 dark:bg-warm-600/60 rounded"
|
|
style={{ width: `${globalWidth}%` }}
|
|
/>
|
|
)}
|
|
<div
|
|
className="h-full rounded relative"
|
|
style={{
|
|
width: `${localWidth}%`,
|
|
backgroundColor: barStyle,
|
|
}}
|
|
/>
|
|
</div>
|
|
<span className="w-8 text-warm-500 dark:text-warm-400 text-right shrink-0">
|
|
{count}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|