feat: implement real-time observation patch normalization and live temperature threshold visualization logic

This commit is contained in:
2569718930@qq.com
2026-05-27 10:17:33 +08:00
parent e6a673e27d
commit 820dabfbf3
23 changed files with 709 additions and 29 deletions
@@ -15,7 +15,7 @@ import {
} from "recharts";
import type { ScanOpportunityRow } from "@/lib/dashboard-types";
import { TemperatureTooltipContent } from "@/components/dashboard/scan-terminal/TemperatureTooltipContent";
import type { EvidenceSeries } from "@/components/dashboard/scan-terminal/temperature-chart-logic";
import type { EvidenceSeries, ProbabilityOverlay } from "@/components/dashboard/scan-terminal/temperature-chart-logic";
type CityThreshold = {
threshold: number;
@@ -32,6 +32,7 @@ export function TemperatureChartCanvas({
cityThresholds,
chartSeries,
activeSeries,
probabilityOverlay,
zoomedData,
chartDomain,
intDegreeTicks,
@@ -55,6 +56,7 @@ export function TemperatureChartCanvas({
cityThresholds: CityThreshold[];
chartSeries: EvidenceSeries[];
activeSeries: EvidenceSeries[];
probabilityOverlay: ProbabilityOverlay | null;
zoomedData: Array<Record<string, any>>;
chartDomain: [number, number] | ["auto", "auto"];
intDegreeTicks: number[] | null;
@@ -160,6 +162,30 @@ export function TemperatureChartCanvas({
<span>{isEn ? "Show Runway Details" : "显示跑道明细"}</span>
</label>
)}
{probabilityOverlay && (
<span
className={clsx(
"inline-flex items-center gap-1.5 rounded border border-violet-200 bg-violet-50 px-1.5 py-0.5 text-[10px] font-bold text-violet-700",
canToggleRunwayDetails ? "" : "ml-auto",
)}
title={
probabilityOverlay.muLine
? probabilityOverlay.muLine.label
: isEn
? "Legacy Gaussian probability bands"
: "Legacy 高斯概率温度带"
}
>
<span className="h-2 w-2 rounded-full bg-violet-500/70" />
<span>{isEn ? "Gaussian" : "高斯概率"}</span>
{probabilityOverlay.muLine && (
<span className="font-mono text-violet-600">
μ {probabilityOverlay.muLine.value.toFixed(1)}{tempSymbol}
</span>
)}
</span>
)}
</div>
<div ref={chartHostRef} className="relative min-h-[220px] flex-1">
{canRenderChart && (
@@ -191,6 +217,16 @@ export function TemperatureChartCanvas({
domain={chartDomain}
ticks={intDegreeTicks ?? undefined}
/>
{timeframe === "1D" && probabilityOverlay?.bands.map((band) => (
<ReferenceArea
key={band.key}
y1={band.lower}
y2={band.upper}
strokeOpacity={0}
fill="#8b5cf6"
fillOpacity={band.opacity}
/>
))}
{timeframe === "1D" && cityThresholds.map((t, idx) => {
const isSelected = row && (Number(row.target_threshold ?? row.target_value) === t.threshold);
const labelText = isEn
@@ -213,6 +249,20 @@ export function TemperatureChartCanvas({
/>
);
})}
{timeframe === "1D" && probabilityOverlay?.muLine && (
<ReferenceLine
y={probabilityOverlay.muLine.value}
stroke="#7c3aed"
strokeDasharray="2 3"
strokeWidth={1.4}
label={{
value: compact ? undefined : probabilityOverlay.muLine.label,
fill: "#7c3aed",
fontSize: 9,
position: "insideTopLeft",
}}
/>
)}
<Tooltip
filterNull={false}
cursor={{ stroke: "#94a3b8", strokeWidth: 1 }}