docs([2fd88f42]): finalize documentation and update tasks for heatmap tool

This commit is contained in:
2026-02-04 14:48:47 +00:00
parent 6101f8c2e5
commit 1862430fd8
3 changed files with 129 additions and 10 deletions

View File

@@ -1,16 +1,73 @@
# Heatmap Tool
# Heatmap Tool (Standalone)
This directory contains a standalone web application for visualizing data from XLSX files as a heatmap on a map of Germany.
Eine Webanwendung zur Visualisierung von Excel-Daten (XLSX) auf einer Deutschlandkarte. Das Tool aggregiert Daten basierend auf Postleitzahlen (PLZ) und stellt sie entweder als Punkte-Cluster oder als Heatmap dar.
## Description
## Features
The application allows users to upload an XLSX file containing German postal codes (PLZ). It then aggregates the data by postal code and displays it as a heatmap, where the color and size of the markers represent the density of records. The tool automatically detects other columns in the file and creates dynamic filters, allowing users to slice the data and visualize different segments.
* **Excel Upload:** Lädt beliebige `.xlsx` Dateien. Erkennt automatisch die PLZ-Spalte (oder fragt nach, wenn unklar).
* **Datenschutz:** Daten werden nur temporär im RAM des Containers verarbeitet. Keine Datenbank.
* **Visualisierung:**
* **Punkte-Karte:** Kreise pro PLZ, Radius = Anzahl der Einträge. Mit Marker-Clustering beim Herauszoomen.
* **Heatmap:** Klassische Dichte-Darstellung.
* **Interaktive Filter:** Alle anderen Spalten der Excel-Datei werden automatisch zu Filtern (Checkboxen).
* **Dynamische Tooltips:** Benutzer können per Drag-and-Drop konfigurieren, welche Daten im Tooltip eines Punktes angezeigt werden.
## Getting Started
## Installation & Start
To run the application, navigate to the `heatmap-tool` directory and follow the instructions in its `README.md` file.
Das Projekt ist vollständig dockerisiert.
```bash
cd heatmap-tool
```
Then follow the instructions in `heatmap-tool/README.md`.
1. In das Verzeichnis wechseln:
```bash
cd heatmap-tool
```
2. Container starten (im Hintergrund):
```bash
docker-compose up --build -d
```
3. Anwendung öffnen:
* Browser: `http://<DEINE-IP>:5173` (Frontend)
* Die API läuft intern auf Port 8000, ist aber von außen auf Port `8002` gemappt (wird aber durch den Vite-Proxy auf 5173 getunnelt).
4. Stoppen:
```bash
docker-compose down
```
## Architektur
* **Frontend:** React 19, Vite, Leaflet (`react-leaflet`, `react-leaflet-cluster`, `react-leaflet-heatmap-layer-v3`).
* **Backend:** Python FastAPI, Pandas (für Excel-Processing).
* **Kommunikation:** Das Frontend nutzt einen Proxy (`vite.config.ts`), um Anfragen an `/api` an das Backend weiterzuleiten.
## Lessons Learned & Known Issues (WICHTIG!)
### 1. Docker Networking & Vite Proxy
* **Problem:** Frontend-Container konnte Backend nicht unter `localhost` oder `127.0.0.1` erreichen.
* **Lösung:**
1. Beide Services müssen im selben Docker-Netzwerk sein (`networks: - heatmap-net` in `docker-compose.yml`).
2. Das Frontend darf **nicht** versuchen, die URL hartcodiert aufzurufen.
3. Stattdessen muss in `vite.config.ts` ein `server.proxy` eingerichtet werden, der `/api` auf `http://backend:8000` (Service-Name!) weiterleitet.
4. Im Frontend-Code (`App.tsx`, `axios`) werden dann nur relative Pfade genutzt (z.B. `/api/upload`).
### 2. React 19 vs. Leaflet Libraries
* **Problem:** Viele Leaflet-Plugins (wie `react-leaflet-heatmap-layer-v3`) haben veraltete Peer-Dependencies (z.B. React 17), was bei `npm install` zu Fehlern führt.
* **Lösung:**
* Lokal: Installation mit `--legacy-peer-deps`.
* **Docker Build:** Im `Dockerfile` des Frontends muss zwingend `RUN npm install --legacy-peer-deps` stehen, sonst schlägt der Build fehl.
### 3. Import/Export Syntax (TypeScript)
* **Problem:** `Uncaught SyntaxError: The requested module ... does not provide an export named ...`
* **Ursache:** Beim Importieren von TypeScript-Interfaces (z.B. `TooltipColumn`) in einer `.tsx` Datei wurde das Schlüsselwort `type` vergessen.
* **Korrekt:** `import type { TooltipColumn } from '../App';`
* **Falsch:** `import { TooltipColumn } from '../App';` (Führt zu Runtime-Fehlern im Browser, da Vite versucht, es als JS-Code zu kompilieren).
### 4. Endlosschleifen bei Karten-Events (Vorsicht!)
* **Problem:** Versuch, eine "zoom-adaptive Legende" zu bauen, die den `maxCount` basierend auf dem sichtbaren Ausschnitt neu berechnet.
* **Fehler:** Ein `useEffect` oder Event-Handler (`useMapEvents`), der den State (`visibleData`) aktualisiert, löst ein Re-Render der Karte aus. Das Re-Render löst erneut das Event aus -> **Endlosschleife / Stack Overflow**.
* **Status:** Feature wurde reverted. Wenn wir das wieder einbauen, muss der Handler vom Rendering entkoppelt sein (z.B. via `useCallback` oder komplett außerhalb der Render-Logik der Map-Komponente).
### 5. Daten-Normalisierung
* **Problem:** `KeyError: 'plz'`. Die Excel-Datei hatte "PLZ" (groß), das Backend erwartete "plz" (klein) oder umgekehrt.
* **Lösung:** Das Backend normalisiert jetzt die Spaltennamen intern, bevor die Daten an das Frontend gesendet werden. Das Frontend verlässt sich strikt auf klein geschrieben `plz`, `lat`, `lon`.

View File

@@ -0,0 +1,42 @@
// src/components/PlzSelector.tsx
import React, { useState } from 'react';
interface PlzSelectorProps {
columns: string[];
onSubmit: (selectedColumn: string) => void;
isLoading: boolean;
}
const PlzSelector: React.FC<PlzSelectorProps> = ({ columns, onSubmit, isLoading }) => {
const [selectedColumn, setSelectedColumn] = useState<string>(columns[0] || '');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (selectedColumn) {
onSubmit(selectedColumn);
}
};
return (
<div className="plz-selector" style={{ padding: '20px', border: '1px solid #555', borderRadius: '5px', marginTop: '20px' }}>
<h3>Postal Code Column Not Found</h3>
<p>Please select the column containing the postal codes (PLZ) from the list below.</p>
<form onSubmit={handleSubmit}>
<select
value={selectedColumn}
onChange={(e) => setSelectedColumn(e.target.value)}
style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
>
{columns.map(col => (
<option key={col} value={col}>{col}</option>
))}
</select>
<button type="submit" disabled={isLoading || !selectedColumn} style={{ width: '100%', padding: '10px' }}>
{isLoading ? 'Confirming...' : 'Confirm'}
</button>
</form>
</div>
);
};
export default PlzSelector;