'use client' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, Legend, Area, ComposedChart, } from 'recharts' import { format, parseISO } from 'date-fns' import type { WeatherPoint } from '@/lib/types' import { useThresholdStore } from '@/store/threshold-store' interface WindDirectionChartProps { data: WeatherPoint[] yesterdayData?: WeatherPoint[] className?: string } interface ChartDataPoint { timestamp: string time: string offset?: number yesterdayOffset?: number direction?: number isInRange: boolean } // Transform direction to offset from West (270°) // offset = ((direction - 270 + 180) % 360) - 180 // This maps: 180° (S) = -90°, 270° (W) = 0°, 360° (N) = 90° function calculateOffset(direction: number): number { return ((direction - 270 + 180) % 360) - 180 } export function WindDirectionChart({ data, yesterdayData, className }: WindDirectionChartProps) { const { dirCenter, dirRange } = useThresholdStore() // Calculate acceptable bounds const centerOffset = calculateOffset(dirCenter) const minOffset = centerOffset - dirRange const maxOffset = centerOffset + dirRange // Filter data for TODAY's 8am-10pm window only const filterTimeWindow = (points: WeatherPoint[]) => { const now = new Date() const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0) const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 22, 0, 0) return points.filter((point) => { const timestamp = parseISO(point.timestamp) return timestamp >= todayStart && timestamp < todayEnd }) } // Generate static time slots for 8am-10pm (14 hours) const generateTimeSlots = (): { hour: number; label: string }[] => { const slots = [] for (let hour = 8; hour < 22; hour++) { slots.push({ hour, label: format(new Date().setHours(hour, 0, 0, 0), 'ha') }) } return slots } const filteredData = filterTimeWindow(data) // Don't filter yesterday's data - show all available historical data const filteredYesterday = yesterdayData || [] // Helper to clamp values to chart display range const clampToChartRange = (value: number | undefined): number | undefined => { if (value === undefined) return undefined return Math.max(-60, Math.min(60, value)) } // Generate static time slots and map data to them const timeSlots = generateTimeSlots() const chartData: ChartDataPoint[] = timeSlots.map(slot => { // Find forecast data for this hour const forecastPoint = filteredData.find(point => parseISO(point.timestamp).getHours() === slot.hour ) // Find yesterday's data for this hour const yesterdayPoint = filteredYesterday.find(yp => parseISO(yp.timestamp).getHours() === slot.hour ) const rawOffset = forecastPoint ? calculateOffset(forecastPoint.wind_direction) : undefined const offset = clampToChartRange(rawOffset) const isInRange = rawOffset !== undefined ? (rawOffset >= minOffset && rawOffset <= maxOffset) : false return { timestamp: forecastPoint?.timestamp || '', time: slot.label, offset, yesterdayOffset: clampToChartRange(yesterdayPoint ? calculateOffset(yesterdayPoint.wind_direction) : undefined), direction: forecastPoint?.wind_direction, isInRange, } }) // Helper to convert offset back to compass direction for display const offsetToCompass = (offset: number): string => { const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'] const deg = (((offset + 270) % 360) + 360) % 360 const index = Math.round(deg / 45) % 8 return directions[index] } // Custom tooltip const CustomTooltip = ({ active, payload }: any) => { if (!active || !payload || !payload.length) return null const data = payload[0].payload // Don't show tooltip if there's no forecast data for this time slot if (data.offset === undefined || data.direction === undefined) return null return (
{format(parseISO(data.timestamp), 'EEE ha')}
Direction: {data.direction.toFixed(0)}° ({offsetToCompass(data.offset)})
Offset from West: {data.offset.toFixed(1)}°
{data.yesterdayOffset !== undefined && (Yesterday: {data.yesterdayOffset.toFixed(1)}°
)}Range: {minOffset.toFixed(0)}° to {maxOffset.toFixed(0)}°