diff --git a/src/components/HeatMap.tsx b/src/components/HeatMap.tsx index 06070ae..f37a9c3 100644 --- a/src/components/HeatMap.tsx +++ b/src/components/HeatMap.tsx @@ -64,25 +64,40 @@ export function HeatMap({ transactions }: HeatMapProps) { }; }, []); - // Update heatmap data when transactions change + // Update heatmap data with fade transition when transactions change useEffect(() => { if (!heatRef.current || !mapRef.current) return; - // Normalize amounts for intensity (log scale feels better visually) - const maxAmount = Math.max(...transactions.map((t) => t.amount), 1); + const canvas = (heatRef.current as unknown as { _canvas?: HTMLCanvasElement })._canvas; - const points: L.HeatLatLngTuple[] = transactions.map((tx) => [ - tx.lat, - tx.lng, - Math.min(Math.log1p(tx.amount) / Math.log1p(maxAmount), 1), - ]); + const update = () => { + const maxAmount = Math.max(...transactions.map((t) => t.amount), 1); + const points: L.HeatLatLngTuple[] = transactions.map((tx) => [ + tx.lat, + tx.lng, + Math.min(Math.log1p(tx.amount) / Math.log1p(maxAmount), 1), + ]); + try { + heatRef.current?.setLatLngs(points); + } catch { + // map was torn down (React StrictMode double-invoke), ignore + } + }; - // Guard: only update if the heat layer is still attached to the map - try { - heatRef.current.setLatLngs(points); - } catch { - // map was torn down (React StrictMode double-invoke), ignore + if (!canvas) { + update(); + return; } + + canvas.style.transition = 'opacity 0.25s ease-in-out'; + canvas.style.opacity = '0'; + + const t = setTimeout(() => { + update(); + canvas.style.opacity = '1'; + }, 250); + + return () => clearTimeout(t); }, [transactions]); return (