From 30057a07fb86e41594242c3a9be2fbf66d63bb21 Mon Sep 17 00:00:00 2001 From: syoul Date: Mon, 23 Mar 2026 21:47:10 +0100 Subject: [PATCH] feat: fondu entre les frames de l'animation heatmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fade out 250ms → mise à jour des données → fade in 250ms sur le canvas Leaflet.heat. Aucun état React supplémentaire — manipulation directe du canvas interne via _canvas. Co-Authored-By: Claude Sonnet 4.6 --- src/components/HeatMap.tsx | 41 ++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) 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 (