🧱 Einbindung in MODX 3
Einbindung in MODX 3 mit Bootstrap 5 – dwdWetter
Snippet: dwdWetterNowModx (Übersicht + Wetter jetzt)
<?php
/**
* dwdWetterNowModx
* V 2025-12-01
*
* Liest eine dwdWetter-JSON-Datei und gibt:
* - Kopfbereich / Übersicht
* - aktuellen Eintrag (erste forecast-Zeile) für "Wetter jetzt"
*
* Parameter:
* - stationID : DWD-Station (z.B. K428)
* - jsonDir : Dateisystempfad zum wetterdata-Ordner (optional)
*/
$stationID = $modx->getOption('stationID', $scriptProperties, 'K428');
$defaultJsonDir = MODX_BASE_PATH . 'wetterdata/';
$jsonDir = $modx->getOption('jsonDir', $scriptProperties, $defaultJsonDir);
// Dateipfad zur JSON-Datei
$file = rtrim($jsonDir, '/\\') . DIRECTORY_SEPARATOR . 'wetter_' . $stationID . '.json';
// Optionales Logging (kannst du auch entfernen, wenn es stört)
$modx->log(modX::LOG_LEVEL_INFO, '[dwdWetterNowModx] JSON-Pfad: ' . $file);
if (!file_exists($file)) {
return '<div class="alert alert-warning">Keine Wetterdaten für Station ' . htmlentities($stationID) . ' gefunden.</div>';
}
$json = file_get_contents($file);
$data = json_decode($json, true);
if (!is_array($data) || empty($data['forecast'])) {
return '<div class="alert alert-warning">Wetterdaten für Station ' . htmlentities($stationID) . ' sind ungültig.</div>';
}
// Basis-Platzhalter (Station + Sonne)
$base = [
'stationID' => $data['stationID'] ?? '',
'stationName' => $data['stationName'] ?? '',
'pubDate' => $data['pubDate'] ?? '',
'sunrise' => $data['sunrise'] ?? '',
'sunset' => $data['sunset'] ?? '',
];
// Tageslänge aus sunrise/sunset berechnen (nur auf Uhrzeitbasis)
$lenHours = '';
if (!empty($data['sunrise']) && !empty($data['sunset'])) {
$sr = DateTime::createFromFormat('H:i', $data['sunrise']);
$ss = DateTime::createFromFormat('H:i', $data['sunset']);
if ($sr && $ss) {
$diff = $sr->diff($ss);
$len = $diff->h + $diff->i / 60;
$lenHours = number_format($len, 2, '.', '');
}
}
$base['dayLength'] = $lenHours;
// Fertiger Text für "Tageslicht ..."
if (!empty($data['sunrise']) && !empty($data['sunset']) && $lenHours !== '') {
$base['dayInfo'] = sprintf(
'Tageslicht %s bis %s (Tageslänge %s h)',
$data['sunrise'],
$data['sunset'],
$lenHours
);
} else {
$base['dayInfo'] = '';
}
// Luftdrucktendenz aus den ersten beiden Forecast-Einträgen ableiten
$pressureTrend = '';
if (
isset($data['forecast'][0]['pressure'], $data['forecast'][1]['pressure'])
) {
// Druckwerte in hPa (dwdWetter.php liefert sie bereits in hPa)
$p1 = (int)$data['forecast'][0]['pressure'];
$p2 = (int)$data['forecast'][1]['pressure'];
$delta = $p2 - $p1; // Differenz in hPa
$deltaAbs = abs($delta);
// Zeitabstand in Stunden für Anzeige (z.B. +3 hPa/3h)
$hoursDiff = 1;
if (
isset($data['forecast'][0]['date'], $data['forecast'][0]['time'],
$data['forecast'][1]['date'], $data['forecast'][1]['time'])
) {
$t1 = strtotime($data['forecast'][0]['date'].' '.$data['forecast'][0]['time']);
$t2 = strtotime($data['forecast'][1]['date'].' '.$data['forecast'][1]['time']);
if ($t1 && $t2 && $t2 !== $t1) {
$hoursDiff = max(1, (int)round(($t2 - $t1) / 3600));
}
}
// stabil, wenn |Δp| <= 4 hPa
$threshold = 4;
if ($deltaAbs > $threshold) {
$trendWord = ($p2 > $p1) ? 'steigend' : 'fallend';
$deltaSigned = sprintf('%+d', $delta); // Vorzeichen +/-
$pressureTrend = sprintf(
'Luftdrucktendenz %s (%s hPa/%dh)',
$trendWord,
$deltaSigned,
$hoursDiff
);
} else {
$pressureTrend = 'Luftdrucktendenz ist stabil';
}
}
$base['pressureTrend'] = $pressureTrend;
// "Jetzt" = erster forecast-Eintrag
$now = $data['forecast'][0];
if (!is_array($now)) {
return '<div class="alert alert-warning">Fehler beim Lesen der aktuellen Wetterdaten.</div>';
}
$pl = array_merge($base, $now);
// Chunk fürs Layout
return $modx->getChunk('dwdWetterNowTpl', $pl);
Info
Die Luftdrucktendenz wird anhand der ersten beiden Forecast-Druckwerte berechnet und gilt als stabil, wenn die Änderung höchstens 4 hPa beträgt. Übersteigt die Differenz diesen Wert, wird die Richtung als steigend oder fallend angegeben, inklusive des hPa-Delta pro Stunde.
Snippet: dwdWetterConditionDe (ww-Code - Hashs mit deutschen Konditionen)
<?php
/**
* dwdWetterConditionDe
*
* Gibt für einen DWD-ww-Code (0–100) die deutsche Beschreibung zurück.
*
* Aufruf:
* [[!dwdWetterConditionDe? &code=`60`]]
* oder
* [[!dwdWetterConditionDe? &code=`[[+sigWeatherCode]]`]]
* oder
* [[!dwdWetterConditionDe? &code=`[[+mainSigWeatherCode]]`]]
*
* Parameter:
* - code : ww-Code (Zahl oder String)
* - default : (optional) Fallback-Text, falls Code unbekannt ist
*/
$code = $modx->getOption('code', $scriptProperties, '');
$default = $modx->getOption('default', $scriptProperties, '');
// ww-Code in String-Schlüssel normieren (z.B. 60 → "60")
$code = trim((string)(is_numeric($code) ? (int)$code : $code));
if ($code === '') {
return $default;
}
# ww-Code - Hashs mit deutschen Konditionen (Code/Description), Quellen:
# https://wetterkanal.kachelmannwetter.com/was-ist-der-ww-code-in-der-meteorologie/
# https://www.dwd.de/DE/leistungen/opendata/help/schluessel_datenformate/kml/mosmix_element_weather_xls.html
# es werden nicht alle benötigt, aber wer weiss... ;-)
$strConditions_de = array(
// Bewölkung
'0' => 'Effektive Wolkendecke weniger als 2/8',
'1' => 'Effektive Wolkendecke zwischen 2/8 und 5/8',
'2' => 'Effektive Wolkendecke zwischen 5/8 und 6/8',
'3' => 'Effektive Wolkendecke mindestens 6/8',
// Dunst, Rauch, Staub oder Sand
'4' => 'Sicht durch Rauch oder Asche vermindert',
'5' => 'trockener Dunst (relative Feuchte < 80 %)',
'6' => 'verbreiteter Schwebstaub, nicht vom Wind herangeführt',
'7' => 'Staub oder Sand bzw. Gischt, vom Wind herangeführt',
'8' => 'gut entwickelte Staub- oder Sandwirbel',
'9' => 'Staub- oder Sandsturm im Gesichtskreis, aber nicht an der Station',
// Trockenereignisse
'10' => 'feuchter Dunst (relative Feuchte > 80 %)',
'11' => 'Schwaden von Bodennebel',
'12' => 'durchgehender Bodennebel',
'13' => 'Wetterleuchten sichtbar, kein Donner gehört',
'14' => 'Niederschlag im Gesichtskreis, nicht den Boden erreichend',
'15' => 'Niederschlag in der Ferne (> 5 km), aber nicht an der Station',
'16' => 'Niederschlag in der Nähe (< 5 km), aber nicht an der Station',
'17' => 'Gewitter (Donner hörbar), aber kein Niederschlag an der Station',
'18' => 'Markante Böen im Gesichtskreis, aber kein Niederschlag an der Station',
'19' => 'Tromben (trichterförmige Wolkenschläuche) im Gesichtskreis',
// Ereignisse der letzten Stunde, aber nicht zur Beobachtungszeit
'20' => 'nach Sprühregen oder Schneegriesel',
'21' => 'nach Regen',
'22' => 'nach Schneefall',
'23' => 'nach Schneeregen oder Eiskörnern',
'24' => 'nach gefrierendem Regen',
'25' => 'nach Regenschauer',
'26' => 'nach Schneeschauer',
'27' => 'nach Graupel- oder Hagelschauer',
'28' => 'nach Nebel',
'29' => 'nach Gewitter',
// Staubsturm, Sandsturm, Schneefegen oder -treiben
'30' => 'leichter oder mäßiger Sandsturm, an Intensität abnehmend',
'31' => 'leichter oder mäßiger Sandsturm, unveränderte Intensität',
'32' => 'leichter oder mäßiger Sandsturm, an Intensität zunehmend',
'33' => 'schwerer Sandsturm, an Intensität abnehmen',
'34' => 'schwerer Sandsturm, unveränderte Intensität',
'35' => 'schwerer Sandsturm, an Intensität zunehmend',
'36' => 'leichtes oder mäßiges Schneefegen, unter Augenhöhe',
'37' => 'starkes Schneefegen, unter Augenhöhe',
'38' => 'leichtes oder mäßiges Schneetreiben, über Augenhöhe',
'39' => 'starkes Schneetreiben, über Augenhöhe',
// Nebel oder Eisnebel
'40' => 'Nebel in einiger Entfernung',
'41' => 'Nebel in Schwaden oder Bänken',
'42' => 'Nebel, Himmel erkennbar, dünner werdend',
'43' => 'Nebel, Himmel nicht erkennbar, dünner werdend',
'44' => 'Nebel, Himmel erkennbar, unverändert',
'45' => 'Nebel, Himmel nicht erkennbar, unverändert',
'46' => 'Nebel, Himmel erkennbar, dichter werdend',
'47' => 'Nebel, Himmel nicht erkennbar, dichter werdend',
'48' => 'Nebel mit Reifansatz, Himmel erkennbar',
'49' => 'Nebel mit Reifansatz, Himmel nicht erkennbar',
// Sprühregen
'50' => 'unterbrochener leichter Sprühregen',
'51' => 'durchgehend leichter Sprühregen',
'52' => 'unterbrochener mäßiger Sprühregen',
'53' => 'durchgehend mäßiger Sprühregen',
'54' => 'unterbrochener starker Sprühregen',
'55' => 'durchgehend starker Sprühregen',
'56' => 'leichter gefrierender Sprühregen',
'57' => 'mäßiger oder starker gefrierender Sprühregen',
'58' => 'leichter Sprühregen mit Regen',
'59' => 'mäßiger oder starker Sprühregen mit Regen',
// Regen
'60' => 'unterbrochener leichter Regen oder einzelne Regentropfen',
'61' => 'durchgehend leichter Regen',
'62' => 'unterbrochener mäßiger Regen',
'63' => 'durchgehend mäßiger Regen',
'64' => 'unterbrochener starker Regen',
'65' => 'durchgehend starker Regen',
'66' => 'leichter gefrierender Regen',
'67' => 'mäßiger oder starker gefrierender Regen',
'68' => 'leichter Schneeregen',
'69' => 'mäßiger oder starker Schneeregen',
// Schnee
'70' => 'unterbrochener leichter Schneefall oder einzelne Schneeflocken',
'71' => 'durchgehend leichter Schneefall',
'72' => 'unterbrochener mäßiger Schneefall',
'73' => 'durchgehend mäßiger Schneefall',
'74' => 'unterbrochener starker Schneefall',
'75' => 'durchgehend starker Schneefall',
'76' => 'Eisnadeln (Polarschnee)',
'77' => 'Schneegriesel',
'78' => 'Schneekristalle',
'79' => 'Eiskörner (gefrorene Regentropfen)',
// Schauer
'80' => 'leichter Regenschauer',
'81' => 'mäßiger oder starker Regenschauer',
'82' => 'äußerst heftiger Regenschauer',
'83' => 'leichter Schneeregenschauer',
'84' => 'mäßiger oder starker Schneeregenschauer',
'85' => 'leichter Schneeschauer',
'86' => 'mäßiger oder starker Schneeschauer',
'87' => 'leichter Graupelschauer',
'88' => 'mäßiger oder starker Graupelschauer',
'89' => 'leichter Hagelschauer',
'90' => 'mäßiger oder starker Hagelschauer',
// Gewitter
'91' => 'Gewitter in der letzten Stunde, zurzeit leichter Regen',
'92' => 'Gewitter in der letzten Stunde, zurzeit mäßiger oder starker Regen',
'93' => 'Gewitter in der letzten Stunde, zurzeit leichter Schneefall/Schneeregen/Graupel/Hagel',
'94' => 'Gewitter in der letzten Stunde, zurzeit mäßiger oder starker Schneefall/Schneeregen/Graupel/Hagel',
'95' => 'leichtes oder mäßiges Gewitter mit Regen oder Schnee',
'96' => 'leichtes oder mäßiges Gewitter mit Graupel oder Hagel',
'97' => 'starkes Gewitter mit Regen oder Schnee',
'98' => 'starkes Gewitter mit Sandsturm',
'99' => 'starkes Gewitter mit Graupel oder Hagel',
'100' => 'not available',
);
// Ergebnis zurückgeben
if (array_key_exists($code, $strConditions_de)) {
return $strConditions_de[$code];
}
return $default;
Dieses Snippet wandelt den DWD-ww-Code (z.B. 61) in einen lesbaren deutschen Text um.
Eingaben:
- Parameter
&code=→ numerischer oder stringbasierter ww-Code - Parameter
&default=→ Fallback-Text, falls Code unbekannt
Interne Logik (Beschreibung):
-
codewird als String normalisiert (z.B.61) -
eine interne Map ordnet jedem Code eine Beschreibung zu, z.B.:
60→ „unterbrochener leichter Regen …“61→ „durchgehend leichter Regen“63→ „durchgehend mäßiger Regen“80→ „leichter Regenschauer“95→ „leichtes oder mäßiges Gewitter mit Regen oder Schnee“- usw. (0–100)
-
wenn der Code in der Map existiert → Text zurückgeben
-
sonst →
defaultzurückgeben
Chunk: dwdWetterNowTpl (Übersicht + „Wetter jetzt“ Karte, BS5)
<div class="col-12 col-md-6 col-lg-4 mb-4">
<div class="card h-100 text-center shadow-sm">
<div class="card-body">
<!-- Datum / Uhrzeit / Text-->
<div class="small text-muted mb-2">
<strong>Stand: [[+weekday]] [[+date]] [[+time]]</strong>
</div>
<div class="small text-muted mb-2">
[[!dwdWetterConditionDe? &code=`[[+sigWeatherCode]]`]]
</div>
<!-- Icon -->
<div class="my-2">
<img src="/assets/dwdWetter/dwd_img/[[+icon]]"
alt="Wettericon"
class="img-fluid"
style="max-width:80px;">
</div>
<!-- Temperatur -->
<div class="display-6 mb-3">
[[+temp2m]] °C
</div>
<!-- Zusatzinfos -->
<div class="small text-start mx-auto" style="max-width:220px;">
<div><strong>Wind:</strong> [[+windSpeedKmh]] km/h ([[+windDirection]])</div>
<div><strong>Regen (6h):</strong> [[+rain6h]] mm</div>
<div><strong>Wolken:</strong> [[+cloud]] %</div>
<div><strong>Sicht:</strong> [[+visibilityKm]] km</div>
</div>
</div>
<div class="card-footer small text-muted">
<!-- Tageslicht -->
<div class="mb-1">
[[+dayInfo]]
</div>
<!-- Luftdruck -->
<div class="mb-1">
Luftdruck: [[+pressure]] hPa
</div>
<!-- Luftdrucktendenz -->
<div class="mb-2">
[[+pressureTrend]]
</div>
<div>
<strong>[[+stationName]] ([[+stationID]])</strong><br>
Stand: [[+pubDate]]
</div>
</div>
</div>
</div>
Snippet:dwdWetterDailyModx (10-Tages-Übersicht aus daily)
<?php
/**
* dwdWetterDailyModx
*
* Gibt Tageskarten aus dem daily-Array der dwdWetter-JSON aus.
*
* Parameter:
* - stationID : DWD-Station (z.B. K428)
* - jsonDir : Dateisystempfad zum wetterdata-Ordner (optional)
* - days : Anzahl Tage (Standard 10)
*/
$stationID = $modx->getOption('stationID', $scriptProperties, 'K428');
$days = (int)$modx->getOption('days', $scriptProperties, 10);
$defaultJsonDir = MODX_BASE_PATH . 'wetterdata/';
$jsonDir = $modx->getOption('jsonDir', $scriptProperties, $defaultJsonDir);
$file = rtrim($jsonDir, '/\\') . DIRECTORY_SEPARATOR . 'wetter_' . $stationID . '.json';
if (!file_exists($file)) {
return '<div class="alert alert-warning">Keine Tagesdaten für Station ' . htmlentities($stationID) . ' gefunden.</div>';
}
$json = file_get_contents($file);
$data = json_decode($json, true);
if (!is_array($data) || empty($data['daily'])) {
return '<div class="alert alert-warning">Tagesdaten für Station ' . htmlentities($stationID) . ' sind ungültig.</div>';
}
$daily = array_slice($data['daily'], 0, $days);
// Basis-Platzhalter
$base = [
'stationID' => $data['stationID'] ?? '',
'stationName' => $data['stationName'] ?? '',
];
$out = '';
foreach ($daily as $row) {
if (!is_array($row)) {
continue;
}
// mainIcon kommt jetzt direkt aus der JSON (vom PHP-Hauptscript erzeugt).
// Fallback, falls es aus irgendeinem Grund fehlt oder leer ist.
if (empty($row['mainIcon'])) {
$row['mainIcon'] = 'unknown.png';
}
$pl = array_merge($base, $row);
$out .= $modx->getChunk('dwdWetterDailyCardTpl', $pl);
}
return $out;
Chunk: dwdWetterDailyCardTpl (10-Tage-Vorschau, BS5)
<div class="col-6 col-md-3 col-lg-2 mb-3">
<div class="card h-100 text-center">
<div class="card-body p-2">
<div class="small text-muted mb-1">
[[+weekday]] [[+date]]
</div>
<div class="small text-muted mb-1">
[[!dwdWetterConditionDe? &code=`[[+mainSigWeatherCode]]`]]
</div>
<div class="my-1">
<img src="/assets/dwdWetter/dwd_img/[[+mainIcon]]"
alt="Tagesicon"
style="max-width:48px; height:auto;">
</div>
<div class="fw-bold mb-1">
[[+tempMin]] – [[+tempMax]] °C
</div>
<div class="small">
mittlere Temp.: [[+tempMean]] °C<br>
Niederschlag gesamt: [[+rain6hSum]] mm<br>
max. Böe: [[+windGustMax]] km/h
</div>
</div>
</div>
</div>
Einbindung in eine MODX-Seite (Beispiel)
[[$fcCalendar]]
<div class="mt-4 ms-3">
<p class="mb-1"><em>Farblegende:</em></p>
<p class="small">
<span class="badge bg-primary me-1"> </span>
Termine aus dem Dorfkalender (TV-Einträge)<br>
<span class="badge bg-success me-1"> </span>
Abfuhrtermine A.R.T.<br>
<small class="ms-4 d-block">
Quelle:
<a href="https://www.art-trier.de/abfuhrtermin" target="_blank" rel="noopener noreferrer">
www.art-trier.de/abfuhrtermin
</a>
</small>
<span class="badge me-1" style="background-color: #7e2555;"> </span>
Schulferien Rheinland-Pfalz<br>
<span class="badge me-1" style="background-color: #7e6025;"> </span>
Feiertage und besondere kirchliche Tage<br>
<span class="badge me-1" style="background-color: #6b7d3f;"> </span>
Berichte und Ereignisse (teilweise mit Link)<br>
<span class="badge me-1" style="background-color: #138496;"> </span>
Zeitumstellung Sommer-/Winterzeit
</p>
<p class="small text-muted mb-0">
<em>Alle Angaben ohne Gewähr. Keine Garantie für Korrektheit der Daten.</em>
</p>
</div>
Einbindung Wetter Chart-Snippet dwdWetterDailyChartModx
Benötigt:
chart.jsvon https://cdn.jsdelivr.net/npm/chart.js und wird nur geladen, wenn das Snippet verwendet wird, und dank Placeholder nur einmal pro Seite.
<?php
/**
* dwdWetterDailyChartModx
*
* Benötigt: chart.js von https://cdn.jsdelivr.net/npm/chart.js
*
* 10-Tage-Chart (Temperatur min/max + Niederschlag) aus dem daily-Array.
*
* Parameter:
* - stationID : DWD-Station (z.B. K428)
* - jsonDir : Dateisystempfad zum JSON-Ordner (optional)
* - days : Anzahl Tage (Standard 10)
*/
$stationID = $modx->getOption('stationID', $scriptProperties, 'K428');
$days = (int)$modx->getOption('days', $scriptProperties, 10);
$defaultJsonDir = MODX_BASE_PATH . 'wetterdata/';
$jsonDir = $modx->getOption('jsonDir', $scriptProperties, $defaultJsonDir);
// Chart.js nur einmal einbinden
if (!$modx->getPlaceholder('dwdwetter_chartjs_loaded')) {
// assets_url aus MODX-Config holen
$assetsUrl = $modx->getOption('assets_url', null, '/assets/');
// Script registrieren (relative URL wird automatisch korrekt ausgegeben)
// Lokation prüfen und anpassen!
$modx->regClientScript($assetsUrl . 'bs5/chart.js');
// Merker setzen, damit Chart.js nur einmal geladen wird
$modx->setPlaceholder('dwdwetter_chartjs_loaded', 1);
}
$file = rtrim($jsonDir, '/\\') . DIRECTORY_SEPARATOR . 'wetter_' . $stationID . '.json';
if (!file_exists($file)) {
return '<div class="alert alert-warning">Keine Tagesdaten für Station ' .
htmlentities($stationID) . ' gefunden.</div>';
}
$json = file_get_contents($file);
$data = json_decode($json, true);
if (!is_array($data) || empty($data['daily'])) {
return '<div class="alert alert-warning">Tagesdaten für Station ' .
htmlentities($stationID) . ' sind ungültig.</div>';
}
// Tagesdaten begrenzen
$daily = array_slice($data['daily'], 0, $days);
// Datenarrays für Chart.js
$labels = [];
$tempMinArr = [];
$tempMaxArr = [];
$rainArr = [];
foreach ($daily as $row) {
if (!is_array($row)) {
continue;
}
// Label im Format "Mo. 2025-11-24"
$labels[] = ($row['weekday'] ?? '') . ' ' . ($row['date'] ?? '');
$tempMinArr[] = isset($row['tempMin']) ? (float)$row['tempMin'] : null;
$tempMaxArr[] = isset($row['tempMax']) ? (float)$row['tempMax'] : null;
$rainArr[] = isset($row['rain6hSum']) ? (float)$row['rain6hSum'] : 0.0;
}
if (empty($labels)) {
return '<div class="alert alert-warning">Keine anzeigbaren Tagesdaten für Station ' .
htmlentities($stationID) . ' gefunden.</div>';
}
// Einzigartige Canvas-ID
$canvasId = 'wxDailyChart_' .
preg_replace('/[^A-Za-z0-9_]/', '', $stationID) . '_' .
substr(uniqid('', true), -6);
// Platzhalter für den Chunk
$placeholders = [
'canvasId' => $canvasId,
'stationID' => $data['stationID'] ?? $stationID,
'stationName' => $data['stationName'] ?? $stationID,
'pubDate' => $data['pubDate'] ?? '',
'labelsJson' => json_encode($labels, JSON_UNESCAPED_UNICODE),
'tempMinJson' => json_encode($tempMinArr),
'tempMaxJson' => json_encode($tempMaxArr),
'rainJson' => json_encode($rainArr),
];
// Design + JS über Chunk
return $modx->getChunk('dwdWetterDailyChartTpl', $placeholders);
Einbindung Chart-Chunk dwdWetterDailyChartTpl
Das JS erkennt jeden Wechsel des data-bs-theme-Attributs (Hell/Dunkel/Auto) und aktualisiert sofort alle Chart.js-Farben wie Achsen, Grid, Legende und Beschriftungen, sodass der Wetter-Chart immer automatisch zum aktuellen Farbmodus passt.
Der gewählte Modus des Benutzers wird zusätzlich dauerhaft in localStorage gespeichert (modx-color-mode) und beim nächsten Besuch automatisch wiederhergestellt.
<div class="row justify-content-center my-4">
<div class="col-12 col-md-10 col-lg-8">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title mb-2">
10-Tage-Trend – [[+stationName]]
</h5>
<div class="small text-muted mb-3">
Datenquelle: Deutscher Wetterdienst (DWD)<br>
Stand: [[+pubDate]]
</div>
<div class="chart-container" style="position:relative; height:180px;">
<canvas id="[[+canvasId]]"></canvas>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var ctx = document.getElementById('[[+canvasId]]');
if (!ctx || typeof Chart === 'undefined') {
console.warn('Chart.js ist nicht geladen oder Canvas fehlt ([[+canvasId]]).');
return;
}
// Farben je nach Bootstrap-Theme (light / dark)
function getChartColors() {
var theme = document.documentElement.getAttribute('data-bs-theme') === 'dark'
? 'dark'
: 'light';
if (theme === 'dark') {
return {
axis: '#f8f9fa', // Achsentext, Legende
grid: 'rgba(255,255,255,0.15)', // Grid im Chartbereich
gridSecondary: 'rgba(255,255,255,0.10)' // z.B. rechte Y-Achse
};
} else {
return {
axis: '#212529',
grid: 'rgba(0,0,0,0.08)',
gridSecondary: 'rgba(0,0,0,0.04)'
};
}
}
var labels = [[+labelsJson]];
var tempMinData = [[+tempMinJson]];
var tempMaxData = [[+tempMaxJson]];
var rainData = [[+rainJson]];
var colors = getChartColors();
var wxChart = new Chart(ctx, {
data: {
labels: labels,
datasets: [
{
type: 'line',
label: 'Temperatur min.',
data: tempMinData,
yAxisID: 'yTemp',
borderWidth: 2,
tension: 0.4,
pointRadius: 3,
borderColor: '#003f8c', // dunkelblau
backgroundColor: '#003f8c'
},
{
type: 'line',
label: 'Temperatur max.',
data: tempMaxData,
yAxisID: 'yTemp',
borderWidth: 2,
tension: 0.4,
pointRadius: 3,
borderColor: '#8c0000', // dunkelrot
backgroundColor: '#8c0000'
},
{
type: 'bar',
label: 'Niederschlag (Tagessumme)',
data: rainData,
yAxisID: 'yRain',
borderWidth: 1,
backgroundColor: 'rgba(120, 160, 200, 0.45)', // kühles Blau-Grau
borderColor: 'rgba(80, 120, 160, 0.9)' // etwas dunklerer Rand
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
ticks: {
color: colors.axis,
maxRotation: 0,
autoSkip: false,
callback: function(value) {
var label = this.getLabelForValue(value);
if (!label) {
return '';
}
var parts = label.split(' ');
if (parts.length < 2) {
return label;
}
var date = parts[1];
var d = date.split('-');
if (d.length < 3) {
return label;
}
return d[2] + '.' + d[1];
}
},
grid: {
color: colors.grid
}
},
yTemp: {
position: 'left',
title: {
display: true,
text: 'Temperatur (°C)',
color: colors.axis
},
ticks: {
color: colors.axis
},
grid: {
color: colors.grid
}
},
yRain: {
position: 'right',
title: {
display: true,
text: 'Niederschlag (mm)',
color: colors.axis
},
ticks: {
color: colors.axis
},
grid: {
drawOnChartArea: false,
color: colors.gridSecondary
}
}
},
plugins: {
legend: {
position: 'top',
labels: {
color: colors.axis
}
},
tooltip: {
callbacks: {
label: function(context) {
var label = context.dataset.label || '';
if (label) label += ': ';
if (context.parsed.y != null) {
if (context.dataset.yAxisID === 'yTemp') {
label += context.parsed.y.toFixed(1) + ' °C';
} else {
label += context.parsed.y.toFixed(1) + ' mm';
}
}
return label;
}
}
}
}
}
});
// Auf Theme-Wechsel reagieren (Hell/Dunkel/Auto)
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.type === 'attributes' && m.attributeName === 'data-bs-theme') {
var c = getChartColors();
wxChart.options.scales.x.ticks.color = c.axis;
wxChart.options.scales.x.grid.color = c.grid;
wxChart.options.scales.yTemp.title.color = c.axis;
wxChart.options.scales.yTemp.ticks.color = c.axis;
wxChart.options.scales.yTemp.grid.color = c.grid;
wxChart.options.scales.yRain.title.color = c.axis;
wxChart.options.scales.yRain.ticks.color = c.axis;
wxChart.options.scales.yRain.grid.color = c.gridSecondary;
wxChart.options.plugins.legend.labels.color = c.axis;
wxChart.update();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-bs-theme']
});
});
</script>
Chart Einbindung in eine MODX-Seite (Beispiel)
[[!dwdWetterDailyChartModx?
&stationID=`K428`
&days=`10`
&jsonDir=`[[++base_path]]assets/dwdWetter/json/`
]]
DWD Wetter Mini Icon für die Menüleiste
1) Snippet dwdWetterNowMini
<?php
$stationID = $modx->getOption('stationID',$scriptProperties,'K428');
$jsonDir = $modx->getOption('jsonDir',$scriptProperties, MODX_BASE_PATH.'assets/dwdWetter/json/');
$file = rtrim($jsonDir,'/').'/wetter_'.$stationID.'.json';
if (!file_exists($file)) return '';
$data = json_decode(file_get_contents($file), true);
if (!is_array($data) || empty($data['forecast'][0])) return '';
$now = $data['forecast'][0];
$pl = $now;
$pl['sigWeatherText'] = 'Wettercode '.$now['sigWeatherCode'];
return $modx->getChunk($scriptProperties['tpl'],$pl);
2) Chunk weatherMiniTpl
<div class="d-flex align-items-center ms-3"
style="cursor:pointer;"
onclick="location.href='[[~42]]'"
title="[[+weekday]] [[+time]] • [[+temp2m]] °C • [[!dwdWetterConditionDe? &code=`[[+sigWeatherCode]]`]]">
<img src="/assets/dwdWetter/dwd_img/[[+icon]]"
alt="Wetter"
class="me-2"
style="width:32px; height:auto;">
<span class="fw-semibold">[[+temp2m]] °C</span>
</div>
3) Setze dieses Snippet an eine geeignete Stelle in deinem Template
[[!dwdWetterNowMini?
&stationID=`K428`
&jsonDir=`[[++base_path]]assets/dwdWetter/json/`
&tpl=`weatherMiniTpl`
]]

