all is well
This commit is contained in:
parent
eac1bd0d13
commit
2f149503bb
53 changed files with 1543 additions and 354 deletions
|
|
@ -1,16 +1,34 @@
|
|||
import { ts } from '../../i18n/server';
|
||||
import { getEnumValueColor } from '../../lib/consts';
|
||||
|
||||
function shortenAxisLabel(label: string, total: number): string {
|
||||
if (label.length <= 3) return label;
|
||||
const parts = label.split(/[\s/&-]+/).filter(Boolean);
|
||||
if (parts.length > 1) {
|
||||
return parts
|
||||
.map((part) => Array.from(part)[0])
|
||||
.join('')
|
||||
.slice(0, 3);
|
||||
}
|
||||
return Array.from(label)
|
||||
.slice(0, total <= 5 ? 3 : 2)
|
||||
.join('');
|
||||
}
|
||||
|
||||
export default function EnumBarChart({
|
||||
counts,
|
||||
globalCounts,
|
||||
featureName,
|
||||
compact = false,
|
||||
}: {
|
||||
counts: Record<string, number>;
|
||||
globalCounts?: Record<string, number>;
|
||||
featureName: string;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const entries = Object.entries(counts).sort(([, countA], [, countB]) => countB - countA);
|
||||
if (entries.length === 0) return null;
|
||||
|
||||
const localTotal = entries.reduce((sum, [, c]) => sum + c, 0);
|
||||
|
||||
// When global counts are available, normalize both to percentages for comparison
|
||||
|
|
@ -28,6 +46,71 @@ export default function EnumBarChart({
|
|||
// Fallback to raw count scaling when no global data
|
||||
const maxCount = Math.max(...entries.map(([, count]) => count), 1);
|
||||
|
||||
if (compact) {
|
||||
const title = entries
|
||||
.map(([label, count]) => {
|
||||
const localPct = localTotal > 0 ? (count / localTotal) * 100 : 0;
|
||||
const globalPct =
|
||||
hasGlobal && globalTotal > 0 ? ((globalCounts[label] ?? 0) / globalTotal) * 100 : null;
|
||||
return `${ts(label)}: ${count.toLocaleString()} (${localPct.toFixed(1)}%)${
|
||||
globalPct != null ? ` / ${globalPct.toFixed(1)}%` : ''
|
||||
}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return (
|
||||
<div className="h-10" title={title}>
|
||||
<div className="flex h-7 items-end gap-[2px]">
|
||||
{entries.map(([label, count]) => {
|
||||
const localPct = localTotal > 0 ? count / localTotal : 0;
|
||||
const globalPct = hasGlobal ? (globalCounts[label] ?? 0) / globalTotal : 0;
|
||||
const localHeight = hasGlobal
|
||||
? maxPct > 0
|
||||
? (localPct / maxPct) * 100
|
||||
: 0
|
||||
: (count / maxCount) * 100;
|
||||
const globalHeight = hasGlobal && maxPct > 0 ? (globalPct / maxPct) * 100 : 0;
|
||||
const color = getEnumValueColor(featureName, label);
|
||||
|
||||
return (
|
||||
<div key={label} className="relative flex h-full min-w-[3px] flex-1 items-end">
|
||||
{hasGlobal && (
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 rounded-t-[2px] bg-warm-300/45 dark:bg-warm-600/50"
|
||||
style={{ height: `${Math.max(globalHeight, globalPct > 0 ? 8 : 0)}%` }}
|
||||
/>
|
||||
)}
|
||||
{count > 0 && (
|
||||
<div
|
||||
className="absolute bottom-0 left-[18%] right-[18%] rounded-t-[2px]"
|
||||
style={{
|
||||
height: `${Math.max(localHeight, 12)}%`,
|
||||
backgroundColor: `rgb(${color[0]},${color[1]},${color[2]})`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-0.5 flex gap-[2px]">
|
||||
{entries.map(([label]) => {
|
||||
const translated = ts(label);
|
||||
return (
|
||||
<span
|
||||
key={label}
|
||||
className="min-w-[3px] flex-1 truncate text-center text-[8px] font-medium leading-none text-warm-500 dark:text-warm-400"
|
||||
title={translated}
|
||||
>
|
||||
{shortenAxisLabel(translated, entries.length)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-1 mt-1">
|
||||
{entries.map(([label, count]) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue