diff --git a/src/components/FlowMap.tsx b/src/components/FlowMap.tsx index f43d6ab..013f498 100644 --- a/src/components/FlowMap.tsx +++ b/src/components/FlowMap.tsx @@ -39,8 +39,9 @@ interface FlowMapProps { export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) { const containerRef = useRef(null); const mapRef = useRef(null); - const [mapReady, setMapReady] = useState(false); - const [tick, setTick] = useState(0); // incrémenté sur moveend/zoomend → re-render + const [mapReady, setMapReady] = useState(false); + const [tick, setTick] = useState(0); + const [clustered, setClustered] = useState(true); // Initialisation Leaflet useEffect(() => { @@ -114,33 +115,35 @@ export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) { return { name, lat: d.lat, lng: d.lng, x: p.x, y: p.y, emitted: d.emitted, received: d.received, vol: d.emitted + d.received }; }).sort((a, b) => b.vol - a.vol); - // --- 2. Clustering glouton par distance pixel --- + // --- 2. Clustering glouton par distance pixel (ou 1 ville = 1 cluster) --- interface Cluster { - cx: number; cy: number; // centroïde pondéré (pixels) - lat: number; lng: number; // centroïde géo (pour debug éventuel) + cx: number; cy: number; + lat: number; lng: number; totalVol: number; emitted: number; received: number; cities: Set; } const clusters: Cluster[] = []; - const cityClusterIdx = new Map(); // nom ville → index cluster + const cityClusterIdx = new Map(); for (const city of cityList) { let bestIdx = -1; - let bestDist = Infinity; - for (let i = 0; i < clusters.length; i++) { - const cl = clusters[i]; - const dx = city.x - cl.cx; - const dy = city.y - cl.cy; - const d = Math.sqrt(dx * dx + dy * dy); - if (d < CLUSTER_RADIUS && d < bestDist) { - bestDist = d; - bestIdx = i; + + if (clustered) { + let bestDist = Infinity; + for (let i = 0; i < clusters.length; i++) { + const cl = clusters[i]; + const dx = city.x - cl.cx; + const dy = city.y - cl.cy; + const d = Math.sqrt(dx * dx + dy * dy); + if (d < CLUSTER_RADIUS && d < bestDist) { + bestDist = d; + bestIdx = i; + } } } if (bestIdx === -1) { - // Nouvelle graine clusters.push({ cx: city.x, cy: city.y, lat: city.lat, lng: city.lng, @@ -150,7 +153,6 @@ export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) { }); cityClusterIdx.set(city.name, clusters.length - 1); } else { - // Fusionner dans le cluster existant (centroïde pondéré) const cl = clusters[bestIdx]; const newVol = cl.totalVol + city.vol; cl.cx = (cl.cx * cl.totalVol + city.x * city.vol) / newVol; @@ -258,7 +260,7 @@ export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) { return { arcElems, nodeElems }; // tick en dep pour re-projeter sur pan/zoom // eslint-disable-next-line react-hooks/exhaustive-deps - }, [corridors, cityNodes, focusCity, tick, mapReady]); + }, [corridors, cityNodes, focusCity, tick, mapReady, clustered]); const [popupIdx, setPopupIdx] = useState(null); @@ -345,6 +347,18 @@ export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) { )} + {/* Bouton cluster / villes */} + + {/* Popup cluster */} {mapReady && svgElements && popupIdx !== null && (() => { const node = svgElements.nodeElems[popupIdx];