Stacked bar component

This commit is contained in:
Enrico Ros
2025-05-20 17:04:16 -07:00
parent 0d28934f37
commit eac6228dde
@@ -0,0 +1,85 @@
import * as React from 'react';
import { Box, Typography } from '@mui/joy';
/** Generic segment interface for stacked bar charts */
export interface StackedBarSegment {
key: string;
label: string;
value: number;
color: string;
}
export interface StackedBarBreakdownProps {
/** Array of segments to display */
segments: StackedBarSegment[];
/** Optional title for the chart */
title?: string;
/** Whether to show absolute values in the legend (otherwise just percentages) */
showValues?: boolean;
/** Function to format values for display and tooltips */
valueFormatter?: (value: number) => string;
/** Optional description text below the chart */
description?: string;
}
export function StackedBarBreakdown({ segments, title, showValues = false, valueFormatter = (v) => v.toString(), description }: StackedBarBreakdownProps) {
// Calculate total for percentages
const total = segments.reduce((sum, seg) => sum + seg.value, 0) || 1;
// Filter out zero-value segments
const nonZeroSegments = segments.filter(seg => seg.value > 0);
return (
<Box>
{title && <Typography level='title-md' mb={1}>{title}</Typography>}
{/* The stacked bar */}
<Box sx={{
display: 'flex',
height: 8,
borderRadius: 8,
overflow: 'hidden',
my: 1,
}}>
{nonZeroSegments.map(({ key, value, color }) => {
const percentage = (value / total) * 100;
return (
<Box
key={key}
title={`${valueFormatter(value)} (${percentage.toFixed(0)}%)`}
sx={{
width: `${percentage}%`,
backgroundColor: color,
transition: 'width 0.3s ease-in-out',
}}
/>
);
})}
</Box>
{/* Legend with colored squares */}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{nonZeroSegments.map(({ key, label, value, color }) => {
const percentage = (value / total) * 100;
return (
<Box key={key} sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ width: 10, height: 10, backgroundColor: color, borderRadius: 2 }} />
<Typography level='body-xs'>
{label} {showValues
? `${valueFormatter(value)} (${percentage.toFixed(0)}%)`
: `(${percentage.toFixed(0)}%)`}
</Typography>
</Box>
);
})}
</Box>
{description && (
<Typography level='body-sm' mt={1}>{description}</Typography>
)}
</Box>
);
}