feat: fondu entre les frames de l'animation heatmap
ci/woodpecker/push/woodpecker Pipeline was successful

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 <noreply@anthropic.com>
This commit is contained in:
syoul
2026-03-23 21:47:10 +01:00
parent 40c09e2e4b
commit 30057a07fb
+28 -13
View File
@@ -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(() => { useEffect(() => {
if (!heatRef.current || !mapRef.current) return; if (!heatRef.current || !mapRef.current) return;
// Normalize amounts for intensity (log scale feels better visually) const canvas = (heatRef.current as unknown as { _canvas?: HTMLCanvasElement })._canvas;
const maxAmount = Math.max(...transactions.map((t) => t.amount), 1);
const points: L.HeatLatLngTuple[] = transactions.map((tx) => [ const update = () => {
tx.lat, const maxAmount = Math.max(...transactions.map((t) => t.amount), 1);
tx.lng, const points: L.HeatLatLngTuple[] = transactions.map((tx) => [
Math.min(Math.log1p(tx.amount) / Math.log1p(maxAmount), 1), 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 if (!canvas) {
try { update();
heatRef.current.setLatLngs(points); return;
} catch {
// map was torn down (React StrictMode double-invoke), ignore
} }
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]); }, [transactions]);
return ( return (