feat([2fd88f42]): implement zoom-adaptive color and legend scaling

This commit is contained in:
2026-02-04 13:59:12 +00:00
parent 4876b0317f
commit a9e327fa26

View File

@@ -1,6 +1,6 @@
// src/components/MapDisplay.tsx // src/components/MapDisplay.tsx
import React from 'react'; import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, CircleMarker, Tooltip } from 'react-leaflet'; import { MapContainer, TileLayer, CircleMarker, Tooltip, useMapEvents } from 'react-leaflet';
import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3'; import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import 'leaflet.heat'; import 'leaflet.heat';
@@ -17,20 +17,50 @@ interface MapDisplayProps {
const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier, viewMode }) => { const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier, viewMode }) => {
const germanyCenter: [number, number] = [51.1657, 10.4515]; 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<HeatmapPoint[]>(heatmapData);
// The dynamic max count based only on visible data
const dynamicMaxCount = Math.max(...visibleData.map(p => p.count), 1);
const calculateRadius = (count: number) => { const calculateRadius = (count: number) => {
return 3 + Math.log(count + 1) * 5 * radiusMultiplier; return 3 + Math.log(count + 1) * 5 * radiusMultiplier;
}; };
// getColor now uses the dynamicMaxCount
const getColor = (count: number) => { const getColor = (count: number) => {
const ratio = count / maxCount; const ratio = count / dynamicMaxCount;
if (ratio > 0.8) return '#d73027'; // Red if (ratio > 0.8) return '#d73027'; // Red
if (ratio > 0.5) return '#fdae61'; // Orange if (ratio > 0.5) return '#fdae61'; // Orange
if (ratio > 0.2) return '#fee08b'; // Yellow if (ratio > 0.2) return '#fee08b'; // Yellow
return '#66bd63'; // Green 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 = () => ( const renderPoints = () => (
<MarkerClusterGroup> <MarkerClusterGroup>
{heatmapData.map((point, idx) => ( {heatmapData.map((point, idx) => (
@@ -66,7 +96,7 @@ const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier,
intensityExtractor={(p: HeatmapPoint) => p.count} intensityExtractor={(p: HeatmapPoint) => p.count}
radius={25} radius={25}
blur={20} blur={20}
max={maxCount * 0.1} // Adjust max intensity for better visualization max={dynamicMaxCount * 0.1}
/> />
); );
@@ -74,7 +104,6 @@ const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier,
return ( return (
<div style={{ textAlign: 'center', paddingTop: '50px' }}> <div style={{ textAlign: 'center', paddingTop: '50px' }}>
<p>No data to display on the map.</p> <p>No data to display on the map.</p>
<p>Upload a file and apply filters to see the heatmap.</p>
</div> </div>
); );
} }
@@ -86,8 +115,9 @@ const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier,
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/> />
<MapBoundsManager data={heatmapData} /> <MapBoundsManager data={heatmapData} />
<DynamicLegendHandler />
{viewMode === 'points' ? renderPoints() : renderHeatmap()} {viewMode === 'points' ? renderPoints() : renderHeatmap()}
{viewMode === 'points' && <Legend getColor={getColor} maxCount={maxCount} />} {viewMode === 'points' && <Legend getColor={getColor} maxCount={dynamicMaxCount} />}
</MapContainer> </MapContainer>
); );
}; };