From ee1859a1183e616d63561cf2112f815b5e90ab90 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 19:25:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20LiveTemperatureThresholdCh?= =?UTF-8?q?art=20=E7=BC=BA=E5=A4=B1=E5=B8=B8=E9=87=8F+Legend=20=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LiveTemperatureThresholdChart.tsx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx index 3860e20c..90822a47 100644 --- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx +++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx @@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { ExternalLink } from "lucide-react"; import { + Brush, CartesianGrid, Legend, Line, @@ -20,6 +21,10 @@ import { buildDebBaselinePath } from "@/lib/temperature-chart-paths"; import { Panel } from "@/components/dashboard/scan-terminal/Panel"; import { rowName, temp } from "@/components/dashboard/scan-terminal/utils"; +const ROLLING_WINDOW_BEFORE_MS = 12 * 60 * 60 * 1000; +const ROLLING_WINDOW_AFTER_LIVE_MS = 2 * 60 * 60 * 1000; +const ROLLING_WINDOW_AFTER_FORECAST_MS = 8 * 60 * 60 * 1000; + const SETTLEMENT_RUNWAY_PAIRS: Record> = { shanghai: [["17L", "35R"]], beijing: [["01", "19"]], @@ -151,12 +156,11 @@ type RunwayHistorySeries = { points: Array<{ ts: number; value: number }>; }; -// Sliding window: keep at most this many observation points (24h at 1-min ≈ 1440) const MAX_OBS_POINTS = 1440; const HOURLY_CACHE_TTL_MS = 30 * 60 * 1000; -const ROLLING_WINDOW_BEFORE_MS = 6 * 60 * 60 * 1000; -const ROLLING_WINDOW_AFTER_LIVE_MS = 45 * 60 * 1000; -const ROLLING_WINDOW_AFTER_FORECAST_MS = 6 * 60 * 60 * 1000; +const FULL_DAY_SLOT_MINUTES = 30; +const FULL_DAY_SLOTS = 48; +const SLOT_INTERVAL_MS = FULL_DAY_SLOT_MINUTES * 60 * 1000; const _hourlyCache = new Map(); const RUNWAY_LINE_COLORS = ["#00897b", "#d97706", "#7c3aed", "#0891b2", "#ea580c", "#64748b"]; @@ -717,6 +721,37 @@ function buildChartDomain( return [Number((min - pad).toFixed(1)), Number((max + pad).toFixed(1))]; } +function generateFullDaySlots(localDateStr: string): number[] { + const parts = localDateStr.split("-"); + if (parts.length !== 3) return []; + const year = parseInt(parts[0], 10); + const month = parseInt(parts[1], 10) - 1; + const day = parseInt(parts[2], 10); + const slots: number[] = []; + for (let h = 0; h < 24; h++) { + for (let m = 0; m < 60; m += FULL_DAY_SLOT_MINUTES) { + slots.push(Date.UTC(year, month, day, h, m)); + } + } + return slots; +} + +function binObservationsToSlots( + slots: number[], + obs: Array<{ ts: number; value: number }>, +): Array { + const result: Array = new Array(slots.length).fill(null); + for (const point of obs) { + for (let i = slots.length - 1; i >= 0; i--) { + if (point.ts >= slots[i]) { + result[i] = point.value; + break; + } + } + } + return result; +} + // ── Main component ───────────────────────────────────────────────────── export function LiveTemperatureThresholdChart({