From 9e947b6dfbf0705d73e8b144f4ebba70cd61c6aa Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 4 Feb 2026 14:09:50 +0000 Subject: [PATCH] revert([2fd88f42]): remove zoom-adaptive legend due to critical errors --- .../frontend/src/components/ErrorBoundary.tsx | 51 ++++++++++++++ .../frontend/src/components/Legend.tsx | 68 +++++++++++++++++++ .../src/components/MapBoundsManager.tsx | 26 ------- .../frontend/src/components/MapDisplay.tsx | 47 +++---------- 4 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 heatmap-tool/frontend/src/components/ErrorBoundary.tsx create mode 100644 heatmap-tool/frontend/src/components/Legend.tsx delete mode 100644 heatmap-tool/frontend/src/components/MapBoundsManager.tsx diff --git a/heatmap-tool/frontend/src/components/ErrorBoundary.tsx b/heatmap-tool/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..1f460039 --- /dev/null +++ b/heatmap-tool/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,51 @@ +// src/components/ErrorBoundary.tsx +import React, { Component, ErrorInfo, ReactNode } from 'react'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; + errorInfo: ErrorInfo | null; +} + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null, + errorInfo: null, + }; + + public static getDerivedStateFromError(_: Error): State { + // Update state so the next render will show the fallback UI. + return { hasError: true, error: _, errorInfo: null }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // You can also log the error to an error reporting service + console.error("Uncaught error:", error, errorInfo); + this.setState({ error: error, errorInfo: errorInfo }); + } + + public render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return ( +
+

Something went wrong.

+
+ {this.state.error && this.state.error.toString()} +
+ {this.state.errorInfo?.componentStack} +
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/heatmap-tool/frontend/src/components/Legend.tsx b/heatmap-tool/frontend/src/components/Legend.tsx new file mode 100644 index 00000000..3e7b182d --- /dev/null +++ b/heatmap-tool/frontend/src/components/Legend.tsx @@ -0,0 +1,68 @@ +// src/components/Legend.tsx +import React from 'react'; + +interface LegendProps { + getColor: (count: number) => string; + maxCount: number; +} + +const Legend: React.FC = ({ getColor, maxCount }) => { + // Dynamically generate grades based on the maxCount of the current dataset + const grades = [ + 1, + Math.round(maxCount / 5), + Math.round(maxCount / 2.5), + Math.round(maxCount / 1.5), + maxCount + ]; + + // Remove duplicates and filter out zero or invalid numbers, then sort + const uniqueGrades = [...new Set(grades)].filter(g => g > 0).sort((a,b) => a-b); + if (uniqueGrades.length > 1 && uniqueGrades[1] <= 1) { + uniqueGrades.shift(); // remove the '1' if the next step is too close + } + + return ( + <> + +
+

Count

+ {uniqueGrades.map((grade, index) => { + const from = grade; + const to = uniqueGrades[index + 1]; + return ( +
+ + {from}{to ? `–${to - 1}` : '+'} +
+ ); + })} +
+ + ); +}; + +export default Legend; diff --git a/heatmap-tool/frontend/src/components/MapBoundsManager.tsx b/heatmap-tool/frontend/src/components/MapBoundsManager.tsx deleted file mode 100644 index c42c159e..00000000 --- a/heatmap-tool/frontend/src/components/MapBoundsManager.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// src/components/MapBoundsManager.tsx -import React, { useEffect } from 'react'; -import { useMap } from 'react-leaflet'; -import L from 'leaflet'; -import type { HeatmapPoint } from '../App'; - -interface MapBoundsManagerProps { - data: HeatmapPoint[]; -} - -const MapBoundsManager: React.FC = ({ data }) => { - const map = useMap(); - - useEffect(() => { - if (data && data.length > 0) { - const bounds = L.latLngBounds(data.map(p => [p.lat, p.lon])); - if (bounds.isValid()) { - map.fitBounds(bounds, { padding: [50, 50] }); - } - } - }, [data, map]); - - return null; // This component does not render anything -}; - -export default MapBoundsManager; diff --git a/heatmap-tool/frontend/src/components/MapDisplay.tsx b/heatmap-tool/frontend/src/components/MapDisplay.tsx index 382183cc..b53793ef 100644 --- a/heatmap-tool/frontend/src/components/MapDisplay.tsx +++ b/heatmap-tool/frontend/src/components/MapDisplay.tsx @@ -1,15 +1,12 @@ // src/components/MapDisplay.tsx -import React, { useState, useEffect, useCallback } from 'react'; -import { MapContainer, TileLayer, CircleMarker, Tooltip, useMapEvents } from 'react-leaflet'; +import React from 'react'; +import { MapContainer, TileLayer, CircleMarker, Tooltip } from 'react-leaflet'; import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3'; import 'leaflet/dist/leaflet.css'; import 'leaflet.heat'; import type { HeatmapPoint, MapMode } from '../App'; import MarkerClusterGroup from 'react-leaflet-cluster'; import Legend from './Legend'; -import MapBoundsManager from './MapBoundsManager'; -import { LatLngBounds } from 'leaflet'; - interface MapDisplayProps { heatmapData: HeatmapPoint[]; @@ -17,48 +14,21 @@ interface MapDisplayProps { viewMode: MapMode; } -// This component listens to map events and calls a callback when the view changes -const DynamicLegendHandler = ({ onBoundsChange }: { onBoundsChange: (bounds: LatLngBounds) => void }) => { - const map = useMapEvents({ - zoomend: () => onBoundsChange(map.getBounds()), - moveend: () => onBoundsChange(map.getBounds()), - load: () => onBoundsChange(map.getBounds()), // Handle initial load - }); - - return null; -}; - - const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, viewMode }) => { const germanyCenter: [number, number] = [51.1657, 10.4515]; - - const [visibleData, setVisibleData] = useState(heatmapData); - const dynamicMaxCount = Math.max(...visibleData.map(p => p.count), 1); + const maxCount = Math.max(...heatmapData.map(p => p.count), 1); const calculateRadius = (count: number) => { return 3 + Math.log(count + 1) * 5 * radiusMultiplier; }; const getColor = (count: number) => { - const ratio = count / dynamicMaxCount; + const ratio = count / maxCount; if (ratio > 0.8) return '#d73027'; // Red if (ratio > 0.5) return '#fdae61'; // Orange if (ratio > 0.2) return '#fee08b'; // Yellow return '#66bd63'; // Green }; - - const updateVisibleData = useCallback((bounds: LatLngBounds) => { - 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 - }, [heatmapData]); - - // Reset visible data when heatmapData changes - useEffect(() => { - setVisibleData(heatmapData); - }, [heatmapData]); - const renderPoints = () => ( @@ -95,7 +65,7 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, intensityExtractor={(p: HeatmapPoint) => p.count} radius={25} blur={20} - max={dynamicMaxCount * 0.1} + max={maxCount * 0.1} // Adjust max intensity for better visualization /> ); @@ -103,6 +73,7 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, return (

No data to display on the map.

+

Upload a file and apply filters to see the heatmap.

); } @@ -113,12 +84,10 @@ const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution='© OpenStreetMap contributors' /> - - {viewMode === 'points' ? renderPoints() : renderHeatmap()} - {viewMode === 'points' && } + {viewMode === 'points' && } ); }; -export default MapDisplay; \ No newline at end of file +export default MapDisplay;