// components/emissionsGraph.tsx import React, { useMemo } from 'react'; import { LineChart, Line, AreaChart, Area, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { useBuilding, Building, ElectricityDataPoint, NaturalGasDataPoint, WasteDataPoint } from '@/lib/useBuildingData'; export type EmissionGraphFilters = { startDate?: Date | null; endDate?: Date | null; showWaste?: boolean; showElectricity?: boolean; showGas?: boolean; } interface EmissionsGraphProps { buildingid: string; filters: EmissionGraphFilters; graphType: 'line' | 'area' | 'pie'; } type ChartDataPoint = { date: string; electricity: number; gas: number; waste: number; }; const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8']; export default function EmissionsGraph({ buildingid, filters, graphType }: EmissionsGraphProps) { const { data: building, isLoading, error } = useBuilding(buildingid); const chartData = useMemo(() => { if (!building) return []; const dataMap = new Map>(); const addDataPoint = (date: Date, type: 'electricity' | 'gas' | 'waste', value: number) => { const dateString = date.toISOString().split('T')[0]; const existingData = dataMap.get(dateString) || { date: dateString }; existingData[type] = value; dataMap.set(dateString, existingData); }; // Collect all unique dates and data points const allDates = new Set(); const typedDataPoints: { [key: string]: { date: string, value: number }[] } = { electricity: [], gas: [], waste: [] }; if (filters.showElectricity) { building.electricityUsage.forEach((point: ElectricityDataPoint) => { const date = new Date(point.timestamp.seconds * 1000); const dateString = date.toISOString().split('T')[0]; allDates.add(dateString); typedDataPoints.electricity.push({ date: dateString, value: point.emissions }); }); } if (filters.showGas) { building.naturalGasUsage.forEach((point: NaturalGasDataPoint) => { const date = new Date(point.timestamp.seconds * 1000); const dateString = date.toISOString().split('T')[0]; allDates.add(dateString); typedDataPoints.gas.push({ date: dateString, value: point.emissions }); }); } if (filters.showWaste) { building.wasteGeneration.forEach((point: WasteDataPoint) => { const date = new Date(point.timestamp.seconds * 1000); const dateString = date.toISOString().split('T')[0]; allDates.add(dateString); typedDataPoints.waste.push({ date: dateString, value: point.emissions }); }); } // Sort dates and data points const sortedDates = Array.from(allDates).sort(); Object.values(typedDataPoints).forEach(points => points.sort((a, b) => a.date.localeCompare(b.date))); // Interpolate missing values const interpolateValue = (date: string, points: { date: string, value: number }[]) => { const index = points.findIndex(p => p.date >= date); if (index === -1) return points[points.length - 1]?.value || 0; if (index === 0) return points[0].value; const prev = points[index - 1]; const next = points[index]; const totalDays = (new Date(next.date).getTime() - new Date(prev.date).getTime()) / (1000 * 60 * 60 * 24); const daysSincePrev = (new Date(date).getTime() - new Date(prev.date).getTime()) / (1000 * 60 * 60 * 24); return Number((prev.value + (next.value - prev.value) * (daysSincePrev / totalDays)).toFixed(3)); }; // Fill in all data points sortedDates.forEach(date => { const point: Partial = { date }; if (filters.showElectricity) point.electricity = interpolateValue(date, typedDataPoints.electricity); if (filters.showGas) point.gas = interpolateValue(date, typedDataPoints.gas); if (filters.showWaste) point.waste = interpolateValue(date, typedDataPoints.waste); dataMap.set(date, point); }); // Modify the return statement to truncate values return Array.from(dataMap.values()) .filter(point => { const date = new Date(point.date || ''); return (!filters.startDate || date >= filters.startDate) && (!filters.endDate || date <= filters.endDate); }) .map(point => ({ ...point, electricity: point.electricity ? Number(point.electricity.toFixed(3)) : undefined, gas: point.gas ? Number(point.gas.toFixed(3)) : undefined, waste: point.waste ? Number(point.waste.toFixed(3)) : undefined, })); }, [building, filters]); const pieChartData = useMemo(() => { if (!building || !filters.showWaste) return []; const wasteTypes = new Map(); building.wasteGeneration.forEach((point: WasteDataPoint) => { const type = point.wasteCategory.toLowerCase(); wasteTypes.set(type, (wasteTypes.get(type) || 0) + point.emissions); }); return Array.from(wasteTypes, ([name, value]) => ({ name, value: Number(value.toFixed(3)) })); }, [building, filters.showWaste]); if (isLoading) { return (
{/* Skeleton content */}
Loading...
); } if (error) return
Error: {error.message}
; const renderLineChart = () => ( Number(value).toFixed(3)} /> {filters.showElectricity && building && building.electricityUsage.length > 0 && } {filters.showGas && building && building.naturalGasUsage.length > 0 && } {filters.showWaste && building && building.wasteGeneration.length > 0 && } ); const renderAreaChart = () => ( Number(value).toFixed(3)} /> {filters.showElectricity && building && building.electricityUsage.length > 0 && } {filters.showGas && building && building.naturalGasUsage.length > 0 && } {filters.showWaste && building && building.wasteGeneration.length > 0 && } ); const renderPieChart = () => ( `${name} ${(percent * 100).toFixed(0)}%`} > {pieChartData.map((entry, index) => ( ))} ); return (
{(() => { switch (graphType) { case 'line': return renderLineChart(); case 'area': return renderAreaChart(); case 'pie': return renderPieChart(); default: return <>; } })()}
); }