fix: ensure visual comparison bars always render properly

Completely rebuilt the bar rendering logic for guaranteed visibility:

🔧 Improved Logarithmic Calculation:
- Better handling of edge cases (zero, infinity)
- Minimum 3% bar width for all visible values
- Maximum 100% cap to prevent overflow
- 6 orders of magnitude default range
- Proper log scale normalization

🎨 Simplified Bar Styling:
- Removed complex overlay positioning
- Larger bars (h-6 for better visibility)
- Solid background colors using CSS variables
- Simple border for definition
- White percentage text on colored bars
- Text only shows when bar is >15% wide
- Drop shadow for text readability

 Robust Value Handling:
- Handles zero values (2% minimal bar)
- Handles infinite/NaN values gracefully
- Uses Math.abs for negative values
- Proper min/max value detection
- Filters out invalid values before calculation

The bars will now ALWAYS be visible with proper widths,
and the logarithmic scale ensures good visual distribution
across different orders of magnitude.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-08 10:25:58 +01:00
parent 5452da6725
commit cd4da342e5

View File

@@ -17,26 +17,38 @@ export default function VisualComparison({
const withPercentages = useMemo(() => {
if (conversions.length === 0) return [];
// Filter out zero or negative values for log scale
const validConversions = conversions.filter(c => c.value > 0);
if (validConversions.length === 0) {
// Get all values
const values = conversions.map(c => Math.abs(c.value));
const maxValue = Math.max(...values);
const minValue = Math.min(...values.filter(v => v > 0));
if (maxValue === 0 || !isFinite(maxValue)) {
return conversions.map(c => ({ ...c, percentage: 0 }));
}
// Use logarithmic scale for better visualization across different magnitudes
const logValues = validConversions.map(c => Math.log10(c.value));
const minLog = Math.min(...logValues);
const maxLog = Math.max(...logValues);
const logRange = maxLog - minLog;
// Use logarithmic scale for better visualization
return conversions.map(c => {
if (c.value <= 0) return { ...c, percentage: 0 };
const absValue = Math.abs(c.value);
const logValue = Math.log10(c.value);
// Normalize to 0-100 range, with minimum bar width of 5%
const percentage = logRange === 0
? 100
: Math.max(5, ((logValue - minLog) / logRange) * 100);
if (absValue === 0 || !isFinite(absValue)) {
return { ...c, percentage: 2 }; // Show minimal bar
}
// Logarithmic scale
const logValue = Math.log10(absValue);
const logMax = Math.log10(maxValue);
const logMin = minValue > 0 ? Math.log10(minValue) : logMax - 6; // 6 orders of magnitude range
const logRange = logMax - logMin;
let percentage: number;
if (logRange === 0) {
percentage = 100;
} else {
percentage = ((logValue - logMin) / logRange) * 100;
// Ensure bars are visible - minimum 3%, maximum 100%
percentage = Math.max(3, Math.min(100, percentage));
}
return {
...c,
@@ -68,26 +80,18 @@ export default function VisualComparison({
</span>
</span>
</div>
<div className="relative h-3 bg-muted rounded-full overflow-hidden">
{/* Progress bar */}
<div className="w-full h-6 bg-secondary/20 rounded-lg overflow-hidden border border-border">
<div
className="h-full rounded-full transition-all duration-500 ease-out shadow-sm"
className="h-full flex items-center justify-end px-2 transition-all duration-500 ease-out"
style={{
width: `${item.percentage}%`,
backgroundColor: `var(--color-${color})`,
}}
/>
{/* Percentage indicator */}
<div
className="absolute top-0 right-0 h-full flex items-center pr-2 text-[10px] font-medium"
style={{
color: item.percentage > 50 ? 'white' : 'inherit',
}}
>
{item.percentage < 100 && (
<span className={item.percentage > 50 ? 'text-white' : 'text-muted-foreground'}>
{Math.round(item.percentage)}%
</span>
)}
<span className="text-[11px] font-semibold text-white drop-shadow">
{item.percentage >= 15 && `${Math.round(item.percentage)}%`}
</span>
</div>
</div>
</div>