JavaScript-Grundlagen III: Karten, GeoJSON & Dashboards

1. Karten im Web

Interaktive Karten gehören zu den wirkungsvollsten Visualisierungsformen in den Digital Humanities. Sie machen räumliche Zusammenhänge auf einen Blick sichtbar, die in Tabellen oder Listen verborgen bleiben. Ein Beispiel: Die Korrespondenz von Hugo Schuchardt umfasst Briefe aus über 50 Städten in Europa — auf einer Karte wird sein wissenschaftliches Netzwerk sofort greifbar.

Warum Karten für DH-Projekte?

  • Räumliche Muster: Wo konzentrieren sich Korrespondenzen, Fundorte, Ereignisse?
  • Netzwerke: Verbindungen zwischen Orten, Personen und Institutionen
  • Zeitliche Entwicklung: Wie verändert sich die räumliche Verteilung über die Zeit?
  • Exploration: Nutzende können selbst durch die Daten navigieren

Leaflet vs. OpenLayers

Die beiden bekanntesten Open-Source-Bibliotheken für Karten im Web sind Leaflet.js und OpenLayers. Für die meisten DH-Projekte ist Leaflet die bessere Wahl:

Kriterium Leaflet.js OpenLayers
Dateigröße ~40 KB (gzipped) ~180 KB (gzipped)
Lernkurve Flach, einfache API Steiler, umfangreicher
GeoJSON Nativ unterstützt Nativ unterstützt
Plugins Grosses Ökosystem Weniger Plugins
Ideal für Einfache bis mittlere Karten Komplexe GIS-Anwendungen

Tile-Server und Kartenkacheln

Weder Leaflet noch OpenLayers enthalten eigene Kartendaten. Sie laden Kacheln (Tiles) von einem Tile-Server. Das sind kleine Bildausschnitte (256×256 Pixel), die je nach Zoom-Stufe und Position zusammengesetzt werden. Die bekanntesten kostenlosen Tile-Provider sind:

  • OpenStreetMap: Detailliert, farbig, Community-gepflegt
  • CartoDB Positron: Hell, minimalistisch — ideal für Datenvisualisierung
  • CartoDB Dark Matter: Dunkel — gut für leuchtende Marker
  • Stamen Toner: Schwarzweiss — gut für Druckansichten

Tipp

Für DH-Projekte empfehlen wir CartoDB Positron als Basis-Karte. Die helle, dezente Gestaltung lenkt nicht von den eigentlichen Daten ab und eignet sich hervorragend für Marker, Polygone und Heatmaps.

2. Leaflet.js Grundlagen

Installation per CDN

Leaflet wird über ein CDN (Content Delivery Network) eingebunden. Zwei Zeilen im <head> Ihrer HTML-Datei genügen:

<!-- Leaflet CSS (muss vor dem JS geladen werden) -->
<link rel="stylesheet"
  href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
  integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
  crossorigin="" />

<!-- Leaflet JavaScript -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
  integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
  crossorigin=""></script>

Schlüsselkonzept: CDN-Einbindung

Ein CDN liefert Dateien von Servern weltweit aus, die dem Nutzenden geographisch nahe sind. Das integrity-Attribut (Subresource Integrity, SRI) stellt sicher, dass die geladene Datei nicht manipuliert wurde. Das crossorigin-Attribut erlaubt dem Browser, die Integritätsprüfung durchzuführen.

Eine Karte erstellen

Jede Leaflet-Karte braucht einen HTML-Container mit einer festen Höhe und eine JavaScript-Initialisierung:

<!-- HTML: Container mit fester Hoehe -->
<div id="map" style="height: 500px;"></div>

<script>
  // Karte initialisieren und auf Graz zentrieren
  const map = L.map('map').setView([47.07, 15.44], 13);

  // OpenStreetMap-Kacheln laden
  L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  }).addTo(map);
</script>

Die drei wichtigsten Methoden bei der Initialisierung:

  • L.map('map') — erstellt die Karte im Container mit der ID map
  • .setView([lat, lng], zoom) — setzt Mittelpunkt und Zoom-Stufe
  • L.tileLayer(url, options).addTo(map) — lädt die Kartenkacheln

Häufiger Fehler

Wenn die Karte nicht angezeigt wird, fehlt meist die Höhe des Containers. Ohne height hat der <div> eine Höhe von 0 Pixeln, und die Karte ist unsichtbar. Setzen Sie immer eine feste Höhe oder verwenden Sie CSS (height: 100vh; für Vollbild).

Marker

Marker kennzeichnen einzelne Punkte auf der Karte. Sie werden mit Koordinaten erstellt und zur Karte hinzugefügt:

// Einfacher Marker
L.marker([47.0787, 15.4485]).addTo(map);

// Marker mit Popup (oeffnet bei Klick)
L.marker([47.0787, 15.4485])
  .addTo(map)
  .bindPopup('<strong>Universität Graz</strong><br>Hauptgebäude, Universitätsplatz 3');

// Marker mit sofort offenem Popup
L.marker([47.0787, 15.4485])
  .addTo(map)
  .bindPopup('Uni Graz')
  .openPopup();

Popups und Tooltips

Popups öffnen sich bei Klick und können HTML enthalten. Tooltips erscheinen beim Hover und sind für kurze Beschriftungen gedacht:

// Popup mit HTML-Inhalt
marker.bindPopup(`
  <h3>Landesarchiv Steiermark</h3>
  <p>Karmeliterplatz 3, 8010 Graz</p>
  <p><em>Bestände: Urkunden ab dem 12. Jh.</em></p>
`);

// Tooltip (erscheint bei Hover)
marker.bindTooltip('Landesarchiv');

Kreise und Polygone

Neben Markern unterstützt Leaflet auch geometrische Formen:

// Kreis mit Radius in Metern
L.circle([47.07, 15.44], {
  radius: 500,
  color: '#1a5276',
  fillColor: '#2980b9',
  fillOpacity: 0.3
}).addTo(map);

// Polygon (Dreieck)
L.polygon([
  [47.08, 15.43],
  [47.06, 15.43],
  [47.07, 15.46]
], {
  color: '#C0392B',
  fillOpacity: 0.2
}).addTo(map);

Ausprobieren

Öffnen Sie die Beispieldatei examples/01-leaflet-karte.html im Browser und experimentieren Sie mit den Koordinaten, der Zoom-Stufe und den Marker-Texten. Ändern Sie den Tile-Provider zu CartoDB Positron und beobachten Sie den Unterschied.

3. GeoJSON verstehen

GeoJSON (RFC 7946) ist ein offenes Datenformat für geographische Strukturen, das auf JSON basiert. Es ist der Standard für räumliche Daten im Web und wird von praktisch allen Kartenbibliotheken und GIS-Tools unterstützt.

Grundstruktur

Die beiden wichtigsten Typen in GeoJSON sind Feature und FeatureCollection:

// FeatureCollection: Die haeufigste Struktur
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [15.4485, 47.0787]
      },
      "properties": {
        "name": "Graz",
        "land": "Österreich",
        "briefanzahl": 342
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [16.3738, 48.2082]
      },
      "properties": {
        "name": "Wien",
        "land": "Österreich",
        "briefanzahl": 218
      }
    }
  ]
}

Schlüsselkonzept: Koordinatenreihenfolge

GeoJSON verwendet die Reihenfolge [Längengrad, Breitengrad] (also [lng, lat]), während Leaflet [lat, lng] erwartet. Leaflet konvertiert GeoJSON-Koordinaten automatisch — aber wenn Sie manuell Koordinaten angeben, müssen Sie die richtige Reihenfolge beachten.

Geometrietypen

GeoJSON unterstützt verschiedene Geometrietypen:

  • Point: Ein einzelner Punkt — z.B. ein Ort, ein Fundort
  • LineString: Eine Linie aus mehreren Punkten — z.B. eine Reiseroute
  • Polygon: Eine geschlossene Fläche — z.B. eine Region, ein Gelände
  • MultiPoint: Mehrere Punkte als ein Feature
  • MultiLineString: Mehrere Linien als ein Feature
  • MultiPolygon: Mehrere Flächen als ein Feature
// LineString: Reiseroute Graz - Wien - Berlin
{
  "type": "Feature",
  "geometry": {
    "type": "LineString",
    "coordinates": [
      [15.44, 47.07],
      [16.37, 48.21],
      [13.41, 52.52]
    ]
  },
  "properties": {
    "name": "Reiseroute Schuchardt 1887",
    "zweck": "Vortragsreise"
  }
}

// Polygon: Steiermark (vereinfacht)
{
  "type": "Feature",
  "geometry": {
    "type": "Polygon",
    "coordinates": [[
      [14.3, 46.6], [16.2, 46.6],
      [16.2, 47.8], [14.3, 47.8],
      [14.3, 46.6]
    ]]
  },
  "properties": {
    "name": "Steiermark",
    "typ": "Bundesland"
  }
}

Properties

Das properties-Objekt eines Features kann beliebige Metadaten enthalten. Es gibt keine vorgegebene Struktur — Sie können jede Information speichern, die für Ihr Projekt relevant ist:

"properties": {
  "name": "Graz",
  "institution": "Universität Graz",
  "briefanzahl": 342,
  "zeitraum": "1876-1927",
  "partner": ["Ascoli", "Meyer", "Baudouin"],
  "url": "https://schuchardt.uni-graz.at"
}

Hinweis

GeoJSON-Dateien haben die Endung .geojson oder .json. Sie können GeoJSON in Online-Tools wie geojson.io erstellen und validieren.

4. GeoJSON mit Leaflet

Leaflet hat erstklassige Unterstützung für GeoJSON. Die Grundfunktion ist denkbar einfach:

// GeoJSON-Daten zur Karte hinzufuegen
L.geoJSON(geojsonData).addTo(map);

Leaflet erkennt automatisch den Geometrietyp und erstellt die passenden Layer: Marker für Points, Polylines für LineStrings, Polygone für Polygons.

Styling von GeoJSON

Mit der style-Option können Sie das Aussehen von Linien und Flächen anpassen:

L.geoJSON(geojsonData, {
  style: (feature) => {
    return {
      color: '#1a5276',
      weight: 2,
      fillColor: '#2980b9',
      fillOpacity: 0.3
    };
  }
}).addTo(map);

Die style-Funktion erhält das Feature als Parameter, sodass Sie das Styling von den Properties abhängig machen können:

// Farbe abhaengig von der Briefanzahl
L.geoJSON(geojsonData, {
  style: (feature) => {
    const briefe = feature.properties.briefanzahl;
    let farbe = '#aaa';
    if (briefe > 200) farbe = '#C0392B';
    else if (briefe > 100) farbe = '#D4AC0D';
    else if (briefe > 50) farbe = '#27AE60';
    return { color: farbe, weight: 2 };
  }
}).addTo(map);

onEachFeature: Popups und Events

Die onEachFeature-Callback-Funktion wird für jedes Feature einmal aufgerufen. Sie erhält das Feature (mit seinen Properties) und den Layer (das Leaflet-Objekt):

const onEachFeature = (feature, layer) => {
  if (feature.properties && feature.properties.name) {
    layer.bindPopup(`
      <strong>${feature.properties.name}</strong><br>
      Briefe: ${feature.properties.briefanzahl || 0}<br>
      Zeitraum: ${feature.properties.zeitraum || 'unbekannt'}
    `);
  }
};

L.geoJSON(geojsonData, {
  onEachFeature: onEachFeature
}).addTo(map);

pointToLayer: Marker anpassen

Standardmäßig erstellt Leaflet für Point-Features blaue Standard-Marker. Mit pointToLayer können Sie stattdessen Circle-Marker verwenden, deren Größe von den Daten abhängt:

L.geoJSON(geojsonData, {
  pointToLayer: (feature, latlng) => {
    const briefe = feature.properties.briefanzahl || 1;
    const radius = Math.sqrt(briefe) * 2;  // Groesse proportional
    return L.circleMarker(latlng, {
      radius: radius,
      fillColor: '#1a5276',
      color: '#fff',
      weight: 1,
      fillOpacity: 0.7
    });
  },
  onEachFeature: onEachFeature
}).addTo(map);

Schlüsselkonzept: L.geoJSON Optionen

Die drei wichtigsten Optionen für L.geoJSON() sind:

  • style — Aussehen von Linien und Flächen
  • onEachFeature — Popups, Tooltips und Events für jedes Feature
  • pointToLayer — Marker-Typ und -Größe für Point-Features

Ausprobieren

Öffnen Sie examples/02-geojson-karte.html und ändern Sie die Properties eines Features. Beobachten Sie, wie sich Popup-Inhalt und Marker-Größe ändern.

5. Interaktive Karten

Click-Events auf Features

Leaflet-Layer unterstützen verschiedene Events. Das wichtigste ist click:

L.geoJSON(geojsonData, {
  onEachFeature: (feature, layer) => {
    layer.on('click', (e) => {
      console.log('Geklickt auf:', feature.properties.name);
      console.log('Koordinaten:', e.latlng);
    });
  }
}).addTo(map);

Weitere nützliche Events:

  • mouseover / mouseout — Hover-Effekte
  • dblclick — Doppelklick
  • contextmenu — Rechtsklick

Sidebar/Panel aktualisieren

Das typische Muster für interaktive DH-Anwendungen: Klick auf der Karte aktualisiert einen Inhaltsbereich neben der Karte.

const detailPanel = document.querySelector('#detail-panel');

L.geoJSON(geojsonData, {
  onEachFeature: (feature, layer) => {
    layer.bindPopup(feature.properties.name);

    layer.on('click', () => {
      const p = feature.properties;
      detailPanel.innerHTML = `
        <h3>${p.name}</h3>
        <table>
          <tr><th>Briefe</th><td>${p.briefanzahl}</td></tr>
          <tr><th>Zeitraum</th><td>${p.zeitraum}</td></tr>
          <tr><th>Partner</th><td>${(p.partner || []).join(', ')}</td></tr>
        </table>
      `;
    });
  }
}).addTo(map);

Marker filtern

Um Marker dynamisch ein- und auszublenden, speichern Sie die GeoJSON-Layer-Gruppe und erstellen sie bei Bedarf neu:

let geojsonLayer = null;

const renderMarker = (daten) => {
  // Alte Marker entfernen
  if (geojsonLayer) {
    map.removeLayer(geojsonLayer);
  }

  // Neue Marker erstellen
  geojsonLayer = L.geoJSON(daten, {
    onEachFeature: onEachFeature
  }).addTo(map);

  // Kartenausschnitt anpassen
  if (geojsonLayer.getBounds().isValid()) {
    map.fitBounds(geojsonLayer.getBounds());
  }
};

// Filter-Input
const suchfeld = document.querySelector('#suche');
suchfeld.addEventListener('input', (e) => {
  const suchbegriff = e.target.value.toLowerCase();
  const gefiltert = {
    type: 'FeatureCollection',
    features: geojsonData.features.filter(f =>
      f.properties.name.toLowerCase().includes(suchbegriff)
    )
  };
  renderMarker(gefiltert);
});

Tipp

map.fitBounds(layer.getBounds()) passt den Kartenausschnitt automatisch so an, dass alle Marker sichtbar sind. Das ist besonders nützlich nach dem Filtern, wenn nur noch wenige Marker übrig sind.

6. Einfache Datenvisualisierung ohne Bibliothek

Nicht jede Visualisierung braucht eine mächtige Bibliothek wie D3.js oder Chart.js. Für einfache Balkendiagramme und Zeitleisten reichen CSS und SVG vollkommen aus.

CSS Bar Chart

Die Idee: Ein Flex-Container mit align-items: flex-end richtet Kind-Elemente am unteren Rand aus. Die Höhe jedes Balkens wird als Prozent des Maximalwerts berechnet.

// Daten
const daten = [
  { label: '1870er', wert: 45 },
  { label: '1880er', wert: 187 },
  { label: '1890er', wert: 234 },
  { label: '1900er', wert: 156 }
];

// Maximalwert fuer proportionale Berechnung
const maxWert = Math.max(...daten.map(d => d.wert));

// Balken generieren
const chart = document.querySelector('#chart');
daten.forEach(d => {
  const prozent = (d.wert / maxWert) * 100;
  chart.innerHTML += `
    <div class="bar" style="height: ${prozent}%">
      <span class="bar__wert">${d.wert}</span>
      <span class="bar__label">${d.label}</span>
    </div>
  `;
});
/* CSS fuer den Bar Chart */
#chart {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  height: 250px;
  border-bottom: 2px solid #333;
  border-left: 2px solid #333;
  padding: 0 1rem 0 0.5rem;
}

.bar {
  flex: 1;
  background: linear-gradient(to top, #1a5276, #2980b9);
  border-radius: 4px 4px 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: 0.5rem;
  position: relative;
  min-width: 40px;
  transition: height 0.5s ease;
}

.bar__wert {
  color: white;
  font-size: 0.85rem;
  font-weight: bold;
}

.bar__label {
  position: absolute;
  bottom: -1.8rem;
  font-size: 0.75rem;
  color: #666;
  white-space: nowrap;
}

SVG-Grundlagen für Visualisierung

SVG (Scalable Vector Graphics) bietet mehr Kontrolle als CSS. Die wichtigsten Elemente für Datenvisualisierung:

  • <rect> — Rechtecke für Balkendiagramme
  • <circle> — Kreise für Datenpunkte
  • <line> — Linien für Achsen und Verbindungen
  • <text> — Beschriftungen
  • <g> — Gruppen für zusammengehörige Elemente
// SVG Bar Chart generieren
const svgBreite = 500, svgHoehe = 250;
const maxWert = Math.max(...daten.map(d => d.wert));
const barBreite = (svgBreite / daten.length) - 15;
const padding = 30;

let bars = '';
daten.forEach((d, i) => {
  const barHoehe = (d.wert / maxWert) * (svgHoehe - padding * 2);
  const x = i * (barBreite + 15) + padding;
  const y = svgHoehe - barHoehe - padding;

  bars += `
    <rect x="${x}" y="${y}" width="${barBreite}" height="${barHoehe}"
          fill="#1a5276" rx="3" />
    <text x="${x + barBreite / 2}" y="${y - 8}"
          text-anchor="middle" font-size="13" fill="#333">${d.wert}</text>
    <text x="${x + barBreite / 2}" y="${svgHoehe - 8}"
          text-anchor="middle" font-size="12" fill="#666">${d.label}</text>
  `;
});

document.querySelector('#svg-chart').innerHTML = `
  <svg width="${svgBreite}" height="${svgHoehe}" viewBox="0 0 ${svgBreite} ${svgHoehe}">
    <line x1="${padding}" y1="${svgHoehe - padding}"
          x2="${svgBreite}" y2="${svgHoehe - padding}"
          stroke="#333" stroke-width="2" />
    ${bars}
  </svg>
`;

Canvas (Ausblick)

Für sehr grosse Datenmengen (tausende Datenpunkte) bietet HTML5 Canvas eine performantere Alternative zu SVG. Canvas zeichnet Pixel statt DOM-Elemente und ist daher schneller bei vielen Objekten. Für die meisten DH-Projekte reicht SVG aber völlig aus.

Ausprobieren

Öffnen Sie examples/03-daten-visualisierung.html und experimentieren Sie mit den Datenwerten. Ändern Sie Zahlen, fügen Sie neue Jahrzehnte hinzu und beobachten Sie, wie sich das Diagramm anpasst.

7. Dashboard-Aufbau

Ein Dashboard kombiniert mehrere Darstellungsformen zu einer integrierten Ansicht. In DH-Projekten ist ein typisches Dashboard: Karte + Tabelle + Filter.

CSS Grid für Multi-Panel-Layouts

CSS Grid eignet sich hervorragend für Dashboard-Layouts, weil man Bereiche benennen und visuell anordnen kann:

/* Dashboard-Layout mit benannten Bereichen */
.dashboard {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "filter  filter"
    "karte   tabelle";
  gap: 1rem;
  height: calc(100vh - 80px);
  padding: 1rem;
}

.dashboard__filter  { grid-area: filter; }
.dashboard__karte   { grid-area: karte; }
.dashboard__tabelle {
  grid-area: tabelle;
  overflow-y: auto;
}

Die grid-template-areas-Eigenschaft macht das Layout im Code lesbar: Man sieht sofort, dass der Filter oben über die gesamte Breite geht, und Karte und Tabelle nebeneinander stehen.

Responsive Dashboard

Auf kleinen Bildschirmen sollten die Panels untereinander stehen:

@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "filter"
      "karte"
      "tabelle";
    height: auto;
  }

  .dashboard__karte {
    min-height: 300px;
  }
}

Komponenten verbinden

Das zentrale Prinzip eines Dashboards: eine Datenquelle, mehrere Darstellungen. Wenn der Filter geändert wird, aktualisieren sich alle Komponenten:

// Zentrale Render-Funktion
const renderAll = (daten) => {
  renderKarte(daten);
  renderTabelle(daten);
};

// Filter-Event
document.querySelector('#suche').addEventListener('input', (e) => {
  const term = e.target.value.toLowerCase();
  const gefiltert = alleDaten.filter(d =>
    d.name.toLowerCase().includes(term) ||
    d.partner.toLowerCase().includes(term)
  );
  renderAll(gefiltert);
});

// Karte rendern
const renderKarte = (daten) => {
  if (markerLayer) map.removeLayer(markerLayer);
  const geojson = {
    type: 'FeatureCollection',
    features: daten.map(d => ({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [d.lng, d.lat] },
      properties: d
    }))
  };
  markerLayer = L.geoJSON(geojson, { onEachFeature }).addTo(map);
};

// Tabelle rendern
const renderTabelle = (daten) => {
  const tbody = document.querySelector('#tabelle tbody');
  tbody.innerHTML = daten.map(d => `
    <tr>
      <td>${d.name}</td>
      <td>${d.partner}</td>
      <td>${d.briefe}</td>
    </tr>
  `).join('');
};

Schlüsselkonzept: Single Source of Truth

In einem Dashboard gibt es genau eine Datenquelle (das Array mit allen Einträgen) und mehrere Views (Karte, Tabelle, Chart). Filter ändern nicht die Views direkt, sondern filtern die Daten, und alle Views werden mit den gefilterten Daten neu gerendert.

Performance-Hinweis

Bei grossen Datenmengen (mehr als 500 Marker) kann das komplette Neurendern bei jedem Tastendruck langsam werden. Lösungen: Debouncing (nur nach einer kurzen Pause rendern) oder Marker-Clustering (viele nahe Marker zu einem Cluster zusammenfassen).

8. CLAUDE.md und Projektstruktur

Wenn Sie mit einem LLM wie Claude an einem Projekt arbeiten, ist es entscheidend, dass das LLM den Kontext Ihres Projekts versteht. Die CLAUDE.md-Datei ist dafür das zentrale Werkzeug: Sie wird im Stammverzeichnis Ihres Projekts abgelegt und von Claude Code automatisch gelesen.

Was gehört in eine CLAUDE.md?

# CLAUDE.md

## Projektbeschreibung
Kurze Beschreibung: Was ist das Projekt? Fuer wen?

## Technische Regeln
- Welche Technologien? (z.B. Vanilla HTML/CSS/JS, kein Framework)
- Externe Abhaengigkeiten? (z.B. Leaflet.js per CDN)
- Welche Browser? (z.B. aktuelle Chrome/Firefox/Safari)
- Pfad-Konventionen? (z.B. alle Pfade relativ)

## Dateistruktur
- Welche Ordner gibt es?
- Wo liegen HTML, CSS, JS, Daten?
- Wie heissen die wichtigsten Dateien?

## Konventionen
- Sprache der Inhalte (z.B. Deutsch)
- Variablen-Stil (z.B. const/let, kein var)
- CSS-Klassen-Konvention (z.B. BEM)
- HTML-Stil (z.B. semantische Elemente)

## Datenformate
- Beispiel-Strukturen fuer JSON, GeoJSON etc.
- Welche Felder haben die Datenobjekte?

Dateien und Ordner sinnvoll strukturieren

Eine klare Projektstruktur hilft nicht nur dem LLM, sondern auch Ihnen selbst:

mein-dh-portfolio/
  index.html              # Startseite
  karte.html              # Korrespondenz-Karte
  tabelle.html            # Briefe-Tabelle
  dashboard.html          # Kombinierte Ansicht
  css/
    style.css             # Alle Styles
  js/
    app.js                # Hauptlogik
    karte.js              # Karten-spezifischer Code
  data/
    briefe.json           # Brief-Metadaten
    orte.geojson          # Geographische Daten
  img/
    logo.svg              # Projektlogo
  CLAUDE.md               # Projektregeln fuer LLM

Namenskonventionen

  • Dateinamen: Kleinbuchstaben, Bindestriche statt Leerzeichen (mein-projekt.html)
  • CSS-Klassen: BEM-artig (.dashboard__filter, .bar-chart__label)
  • JavaScript-Variablen: camelCase (geojsonDaten, renderTabelle)
  • Daten-Dateien: Beschreibend (briefe.json, nicht data.json)

Beispiel-Prompt mit CLAUDE.md-Bezug

„Lies die CLAUDE.md und erstelle eine neue Seite dashboard.html, die eine Leaflet-Karte links und eine Tabelle rechts zeigt. Verwende die Daten aus data/briefe.json. Halte dich an die CSS-Konventionen aus css/style.css.“

9. Context Engineering für komplexe Projekte

Context Engineering (CE) ist die Kunst, einem LLM genau den Kontext zu geben, den es braucht, um konsistenten und korrekten Code zu generieren. Bei komplexen Projekten mit mehreren Dateien ist das besonders wichtig.

Was ist Kontext?

Kontext umfasst alles, was das LLM wissen muss, um guten Code zu generieren:

  • Projektstruktur: Welche Dateien gibt es? Wie hängen sie zusammen?
  • Bestehender Code: Wie sieht das vorhandene CSS aus? Welche JavaScript-Funktionen gibt es schon?
  • Datenbeispiele: Wie sehen die JSON- oder GeoJSON-Daten aus?
  • Design-Tokens: Welche Farben, Schriften und Abstände werden verwendet?
  • Konventionen: Welcher Coding-Stil wird verwendet?

Kontext-Strategien

CLAUDE.md als Basis

Die CLAUDE.md wird automatisch gelesen und gibt dem LLM die Grundregeln. Sie müssen sie nicht in jedem Prompt wiederholen.

Relevante Dateien referenzieren

Sagen Sie dem LLM, welche bestehenden Dateien es berücksichtigen soll: „Orientiere dich am Stil von karte.html und verwende die Klassen aus css/style.css.“

Datenbeispiele mitliefern

Zeigen Sie dem LLM, wie die Daten aussehen. Ein Beispiel-JSON mit 2–3 Einträgen reicht, damit das LLM die Struktur versteht.

Anforderungen präzisieren (RE)

Beschreiben Sie genau, was die Komponente tun soll, für wen sie ist und welche Interaktionen möglich sein sollen.

Ergebnis prüfen (RV)

Testen Sie den generierten Code im Browser. Prüfen Sie: Funktioniert alles? Stimmen die Daten? Ist der Code lesbar und konsistent?

Beispiel: Guter Prompt mit Kontext

„Ich arbeite an einem DH-Portfolio (siehe CLAUDE.md). Erstelle eine JavaScript-Funktion renderKarte(daten), die ein Array von Objekten auf einer Leaflet-Karte als Marker darstellt. Jedes Objekt hat die Felder: name (String), lat (Number), lng (Number), briefe (Number). Die Funktion soll die bestehende Karte (Variable map) verwenden und alte Marker entfernen, bevor neue gesetzt werden. Verwende L.circleMarker mit einer Groesse proportional zu briefe. Popup: Name und Briefanzahl.“

Beispiel: Schlechter Prompt ohne Kontext

„Mach mir eine Karte mit Markern.“

Agentic Coding: CE + RE + RV im Zusammenspiel

Context Engineering (CE): Gib dem LLM Projektstruktur, bestehenden Code und Datenbeispiele.
Requirement Engineering (RE): Beschreibe präzise, was die Komponente tun soll.
Code Review (RV): Prüfe den generierten Code systematisch auf Funktion, Konsistenz und Fehler.

Diese drei Kompetenzen bilden zusammen den Kern des Agentic-Coding-Workflows für komplexe Projekte.

Ausprobieren

Erstellen Sie eine CLAUDE.md für Ihr eigenes DH-Projekt. Beschreiben Sie die Projektstruktur, die verwendeten Technologien und die wichtigsten Konventionen. Testen Sie dann, ob ein LLM mit dieser CLAUDE.md konsistenteren Code generiert.