From 9879f5f927184c86d3f09a7a92d56ff7a738036c Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 4 Feb 2026 13:59:12 +0000 Subject: [PATCH] feat([2fd88f42]): implement zoom-adaptive color and legend scaling --- .../frontend/src/components/MapDisplay.tsx | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/heatmap-tool/frontend/src/components/MapDisplay.tsx b/heatmap-tool/frontend/src/components/MapDisplay.tsx index 39d71432..b7bda4f9 100644 --- a/heatmap-tool/frontend/src/components/MapDisplay.tsx +++ b/heatmap-tool/frontend/src/components/MapDisplay.tsx @@ -1,6 +1,6 @@ // src/components/MapDisplay.tsx -import React from 'react'; -import { MapContainer, TileLayer, CircleMarker, Tooltip } from 'react-leaflet'; +import React, { useState, useEffect } from 'react'; +import { MapContainer, TileLayer, CircleMarker, Tooltip, useMapEvents } from 'react-leaflet'; import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3'; import 'leaflet/dist/leaflet.css'; import 'leaflet.heat'; @@ -17,20 +17,50 @@ interface MapDisplayProps { const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, viewMode }) => { const germanyCenter: [number, number] = [51.1657, 10.4515]; - const maxCount = Math.max(...heatmapData.map(p => p.count), 1); + + // State for the data currently visible in the map viewport + const [visibleData, setVisibleData] = useState(heatmapData); + + // The dynamic max count based only on visible data + const dynamicMaxCount = Math.max(...visibleData.map(p => p.count), 1); const calculateRadius = (count: number) => { return 3 + Math.log(count + 1) * 5 * radiusMultiplier; }; + // getColor now uses the dynamicMaxCount const getColor = (count: number) => { - const ratio = count / maxCount; + const ratio = count / dynamicMaxCount; if (ratio > 0.8) return '#d73027'; // Red if (ratio > 0.5) return '#fdae61'; // Orange if (ratio > 0.2) return '#fee08b'; // Yellow return '#66bd63'; // Green }; + // This component listens to map events and updates the visible data + const DynamicLegendHandler = () => { + const map = useMapEvents({ + zoomend: () => updateVisibleData(), + moveend: () => updateVisibleData(), + }); + + const updateVisibleData = () => { + const bounds = map.getBounds(); + const visible = heatmapData.filter(p => + bounds.contains([p.lat, p.lon]) + ); + setVisibleData(visible.length > 0 ? visible : heatmapData); // Fallback to all data if none are visible + }; + + // Initial load + useEffect(() => { + updateVisibleData(); + }, [heatmapData]); + + + return null; + }; + const renderPoints = () => ( {heatmapData.map((point, idx) => ( @@ -66,7 +96,7 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, intensityExtractor={(p: HeatmapPoint) => p.count} radius={25} blur={20} - max={maxCount * 0.1} // Adjust max intensity for better visualization + max={dynamicMaxCount * 0.1} /> ); @@ -74,7 +104,6 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, return (

No data to display on the map.

-

Upload a file and apply filters to see the heatmap.

); } @@ -86,8 +115,9 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, attribution='© OpenStreetMap contributors' /> + {viewMode === 'points' ? renderPoints() : renderHeatmap()} - {viewMode === 'points' && } + {viewMode === 'points' && } ); };