import { ComposedChart, Bar, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, LabelList } from 'recharts'; export interface WaterfallDataPoint { label: string; value: number; cumulative: number; type: 'initial' | 'increase' | 'decrease' | 'total'; } export interface WaterfallChartProps { data: WaterfallDataPoint[]; title?: string; height?: number; formatValue?: (value: number) => string; } interface ProcessedDataPoint { label: string; value: number; cumulative: number; type: 'initial' | 'increase' | 'decrease' | 'total'; start: number; end: number; displayValue: number; } export function WaterfallChart({ data, title, height = 300, formatValue = (v) => `€${Math.abs(v).toLocaleString()}` }: WaterfallChartProps) { // Process data for waterfall visualization const processedData: ProcessedDataPoint[] = data.map((item) => { let start: number; let end: number; if (item.type === 'initial' || item.type === 'total') { start = 0; end = item.cumulative; } else if (item.type === 'decrease') { // Savings: bar goes down from previous cumulative start = item.cumulative; end = item.cumulative - item.value; } else { // Increase: bar goes up from previous cumulative start = item.cumulative - item.value; end = item.cumulative; } return { ...item, start: Math.min(start, end), end: Math.max(start, end), displayValue: Math.abs(item.value) }; }); const getBarColor = (type: string): string => { switch (type) { case 'initial': return '#64748B'; // slate-500 case 'decrease': return '#059669'; // emerald-600 (savings) case 'increase': return '#DC2626'; // red-600 (costs) case 'total': return '#6D84E3'; // primary blue default: return '#94A3B8'; } }; const CustomTooltip = ({ active, payload }: { active?: boolean; payload?: Array<{ payload: ProcessedDataPoint }> }) => { if (active && payload && payload.length) { const data = payload[0].payload; return (

{data.label}

{data.type === 'decrease' ? '-' : data.type === 'increase' ? '+' : ''} {formatValue(data.value)}

{data.type !== 'initial' && data.type !== 'total' && (

Acumulado: {formatValue(data.cumulative)}

)}
); } return null; }; // Find min/max for Y axis - always start from 0 const allValues = processedData.flatMap(d => [d.start, d.end]); const minValue = 0; // Always start from 0, not negative const maxValue = Math.max(...allValues); const padding = maxValue * 0.1; return (
{title && (

{title}

)} `€${(value / 1000).toFixed(0)}K`} /> } /> {/* Invisible bar for spacing (from 0 to start) */} {/* Visible bar (the actual segment) */} {processedData.map((entry, index) => ( ))} formatValue(value)} style={{ fontSize: 10, fill: '#475569' }} /> {/* Legend */}
Coste Base
Ahorro
Inversión
Total
); } export default WaterfallChart;