Eigenes Meteogramm statt des eingebetteten von meteoblue

Das Widget von meteoblue war ein fremdes Dokument im iframe: es passte
farblich nicht, liess sich nicht aendern, wurde auf schmalen Schirmen als
Ganzes heruntergerechnet und konnte die eigenen Messwerte nicht zeigen.
Genau das kann das neue Modul - links vom Jetzt-Strich steht, was die
Wetterstation gemessen hat, rechts die Vorhersage.

Aufgebaut wie die uebrigen Anzeige-Module: meteogrammKatalog.js sagt, WAS
gezeigt wird, meteogramm.js nur, WIE. Der Renderer kennt kein Feld und keine
Spur beim Namen. Eine weitere Spur ist damit ein Objekt im Katalog und keine
Codeaenderung.

Ein einziges SVG ueber alle drei Felder, damit Zeitachse, Nachtstreifen,
Jetzt-Linie und Zeiger nur einmal existieren und ueberall gleich sitzen.

Zwei Entscheidungen, die man sonst sucht:

Das Wolkenband zeichnet je Schicht ein Rechteck mit waagerechtem
Farbverlauf. Ein Rechteck je Stunde war der erste Versuch und sah aus wie
Fernsehrauschen - zweihundert harte Kanten nebeneinander. Open-Meteo kennt
ohnehin nur drei Schichten statt eines Hoehenprofils.

Die Strahlung teilt sich das Feld mit der Temperatur, statt ein viertes zu
bekommen: gleicher Tagesrhythmus, gleiche Nacht-Schattierung, und ein
viertes Feld machte das Ganze am Handy hoeher als das externe Widget.

Die alte Einbindung bleibt vorerst und ist umschaltbar. mountMeteogram()
laeuft jetzt erst beim Umschalten - wer meteoblue nie ansieht, laedt es auch
nicht mehr.

Die Karte heisst "Wetter" statt "Forecast": darunter steht bereits eine
zweite Karte dieses Namens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 13:54:55 +02:00
co-authored by Claude Opus 5
parent a4b25ba279
commit 2c48d78880
8 changed files with 1496 additions and 5 deletions
+271
View File
@@ -0,0 +1,271 @@
/* ==========================================================================
Was das Meteogramm zeigt.
Hier steht alles, was man ändern will, und nichts, was man zum Zeichnen
wissen muss. js/solar/meteogramm.js kennt kein Feld, keine Spur und keine
Farbe beim Namen - es liest nur, was hier deklariert ist. Dasselbe Muster
wie anlageKatalog.js und energieflussKatalog.js.
---------------------------------------------------------------------------
So kommt eine Spur dazu
1. Spalte in ajax/meteogramm.php hinten anhängen
2. Schlüssel in SPALTEN hinten anhängen
3. Eintrag in "spuren" des passenden Feldes
Sonst nichts. Hinten anhängen ist wichtig: die Stellen der bestehenden
Spuren bleiben dadurch gültig.
---------------------------------------------------------------------------
Das Vokabular
Ein FELD ist ein waagerechter Streifen mit eigener senkrechter Achse. Alle
Felder teilen sich die Zeitachse.
id Kennung
name Überschrift links oben im Feld
hoehe Schlüssel in HOEHEN (breit/schmal)
links { einheit, min?, max?, minSpanne?, gitter? } - linke Achse
rechts dasselbe für die rechte Achse, oder fehlt
nacht true = Tag/Nacht-Schattierung in diesem Feld zeichnen
symbole { alle, mindestAbstand } für die Wettersymbolleiste, oder fehlt
pfeile { spalte, alle, mindestAbstand } für die Windpfeile, oder fehlt
baender Liste von BÄNDERN, vor den Spuren gezeichnet
spuren Liste von SPUREN, in Zeichenreihenfolge (erste zuunterst)
Eine SPUR ist eine Reihe von Werten über die Zeit.
id, name Kennung; der Name steht in Legende und Tooltip
spalte Schlüssel in SPALTEN
achse "links" oder "rechts"
art "linie" | "flaeche" | "balken"
farbe Datenfarben gehören hierher, nicht ins Stylesheet
breit Strichstärke bei "linie"
strich z.B. "4 3" für gestrichelt
nurGemessen true = nur Stunden mit gemessen = 1
nurVorhersage true = nur Stunden ohne
beschriftung "minmax" = Tagesminimum und -maximum an die Kurve schreiben
format Wert -> Text für den Tooltip
legende true = in der Legendenzeile zeigen
Ein BAND füllt eine Fläche mit veränderlicher Deckkraft - so wird das
Höhenprofil der Bewölkung nachgebaut, das Open-Meteo nicht liefert.
schichten je { spalte, von, bis, name } in Einheiten der rechten Achse
maxDeckung Deckkraft bei 100 %
========================================================================== */
const METEOGRAMM_KATALOG = (function () {
"use strict";
const komma = (z, n = 1) => Number(z).toFixed(n).replace(".", ",");
const ganz = z => String(Math.round(z));
/* Die Spaltenlage einer Stundenzeile aus ajax/meteogramm.php. Steht dort
im Kopfkommentar noch einmal - beide Listen müssen zueinander passen. */
const SPALTEN = {
t: 0, gemessen: 1, temp: 2, regen: 3, schnee: 4, wind: 5, boe: 6,
richtung: 7, strahlung: 8, wtief: 9, wmittel: 10, whoch: 11,
code: 12, feuchte: 13, druck: 14, regenWkt: 15,
};
/* Eine Tageszeile. */
const TAGSPALTEN = { t: 0, tmin: 1, tmax: 2, code: 3, auf: 4, unter: 5 };
/* Feldhöhen in Nutzereinheiten (= CSS-Pixel). Unter UMBRUCH gilt "schmal".
Zusammen mit Achsenstreifen und Rändern ergibt "schmal" rund 345 px -
das eingebettete meteoblue-Bild brauchte 500. */
const HOEHEN = {
breit: { temp: 150, regen: 130, wind: 115 },
schmal: { temp: 120, regen: 100, wind: 92 },
};
const UMBRUCH = 520;
/* --------------------------------------------------------- Wettersymbole
WMO-Code -> Glyphe der Bootstrap-Icons, wie das ZEICHEN-Register in
energiefluss.js. Die achtundzwanzig Codes fallen auf zwölf Symbole
zusammen; wo es eine Nachtvariante gibt, steht sie daneben.
Die Codepunkte stammen aus css/bootstrap-icons.min.css. Der Kommentar
dahinter nennt das Icon - ohne ihn wäre die Zeile nicht zu lesen. */
const WETTER = {
0: { glyph: "", nacht: "", name: "klar" }, // bi-sun-fill / bi-moon-stars-fill
1: { glyph: "", nacht: "", name: "überwiegend klar" }, // dito
2: { glyph: "", nacht: "", name: "teils bewölkt" }, // bi-cloud-sun-fill / bi-cloud-moon-fill
3: { glyph: "", name: "bedeckt" }, // bi-clouds-fill
45: { glyph: "", name: "Nebel" }, // bi-cloud-fog2-fill
48: { glyph: "", name: "Reifnebel" }, // bi-cloud-fog2-fill
51: { glyph: "", name: "leichter Nieselregen" }, // bi-cloud-drizzle-fill
53: { glyph: "", name: "Nieselregen" }, // bi-cloud-drizzle-fill
55: { glyph: "", name: "starker Nieselregen" }, // bi-cloud-drizzle-fill
56: { glyph: "", name: "gefrierender Nieselregen" }, // bi-cloud-sleet-fill
57: { glyph: "", name: "starker gefrierender Nieselregen" }, // bi-cloud-sleet-fill
61: { glyph: "", name: "leichter Regen" }, // bi-cloud-rain-fill
63: { glyph: "", name: "Regen" }, // bi-cloud-rain-fill
65: { glyph: "", name: "starker Regen" }, // bi-cloud-rain-heavy-fill
66: { glyph: "", name: "gefrierender Regen" }, // bi-cloud-sleet-fill
67: { glyph: "", name: "starker gefrierender Regen" }, // bi-cloud-sleet-fill
71: { glyph: "", name: "leichter Schneefall" }, // bi-cloud-snow-fill
73: { glyph: "", name: "Schneefall" }, // bi-cloud-snow-fill
75: { glyph: "", name: "starker Schneefall" }, // bi-cloud-snow-fill
77: { glyph: "", name: "Schneegriesel" }, // bi-snow3
80: { glyph: "", name: "leichte Regenschauer" }, // bi-cloud-rain-fill
81: { glyph: "", name: "Regenschauer" }, // bi-cloud-rain-fill
82: { glyph: "", name: "heftige Regenschauer" }, // bi-cloud-rain-heavy-fill
85: { glyph: "", name: "leichte Schneeschauer" }, // bi-cloud-snow-fill
86: { glyph: "", name: "Schneeschauer" }, // bi-cloud-snow-fill
95: { glyph: "", name: "Gewitter" }, // bi-cloud-lightning-fill
96: { glyph: "", name: "Gewitter mit Hagel" }, // bi-cloud-lightning-rain-fill
99: { glyph: "", name: "schweres Gewitter mit Hagel" }, // bi-cloud-lightning-rain-fill
};
/* Die Farben der Anzeige. Strukturfarben stehen im Stylesheet, Datenfarben
hier - so wie bei energieflussKatalog.js. */
const FARBE = {
temp: "#e5624f",
tempMess: "#f0907f",
strahlung: "#f0b429",
regen: "#4f8fb8",
schnee: "#a8cfe8",
wind: "#7fb3d5",
boe: "#e8a33a",
wolken: "#c9d0d6",
};
return {
spalten: SPALTEN,
tagSpalten: TAGSPALTEN,
hoehen: HOEHEN,
umbruch: UMBRUCH,
wetter: WETTER,
farbe: FARBE,
/* Voreinstellung ist fünf Tage wie beim Vorbild; am Handy zwei, weil dort
fünf Tage drei Pixel je Stunde bedeuten. */
zeitraeume: [
{ tage: 2, name: "2 Tage" },
{ tage: 5, name: "5 Tage", vorgabe: true },
{ tage: 7, name: "7 Tage" },
],
zeitraumSchmal: 2,
/* Wie weit der Rückblick reicht, in Stunden. Die eigene Station misst
alle fünf Minuten; der Endpunkt mittelt auf Stunden. */
rueckblick: 24,
/* Der Windpfeil zeigt, wohin der Wind weht. Die meteorologische Richtung
sagt, woher er kommt - deshalb dreht der Renderer um 180 Grad, genau
wie updateWeatherCards() in common.js. */
windPfeil: {
laenge: 9,
// Unter dieser Stärke verblasst der Pfeil: bei Windstille zeigt eine
// Richtung ohnehin nichts an.
schwach: 5,
},
felder: [
{
id: "temp",
name: "Temperatur",
hoehe: "temp",
nacht: true,
links: { einheit: "°C", minSpanne: 8, gitter: 4 },
// Die Strahlung teilt sich das Feld mit der Temperatur statt ein
// eigenes zu bekommen: beide folgen demselben Tagesrhythmus und
// derselben Tag/Nacht-Schattierung, und ein viertes Feld machte das
// Meteogramm am Handy höher als das externe - also genau das
// Gegenteil dessen, was es soll. Die Achse ist fest 0..1000, damit
// ein trüber Tag flach bleibt und nicht auf volle Höhe gestreckt wird.
rechts: { einheit: "W/m²", min: 0, max: 1000, gitter: 0 },
symbole: { alle: 3, mindestAbstand: 26 },
spuren: [
{
id: "strahlung", name: "Strahlung", spalte: "strahlung",
achse: "rechts", art: "flaeche", farbe: FARBE.strahlung,
deckkraft: 0.16, legende: true,
format: w => ganz(w) + " W/m²",
},
{
id: "temp", name: "Temperatur", spalte: "temp",
achse: "links", art: "linie", farbe: FARBE.temp, breit: 2.4,
beschriftung: "minmax", legende: true, nurVorhersage: true,
format: w => komma(w, 1) + " °C",
},
{
// Dieselbe Spalte, aber nur die gemessenen Stunden: links vom
// Jetzt-Strich steht, was die eigene Station wirklich gesehen
// hat. Zwei Spuren statt einer, damit die Farbe wechseln kann,
// ohne dass der Renderer von "gemessen" wissen muss.
id: "tempMess", name: "gemessen", spalte: "temp",
achse: "links", art: "linie", farbe: FARBE.tempMess, breit: 2.4,
nurGemessen: true, legende: true,
format: w => komma(w, 1) + " °C",
},
],
},
{
id: "regen",
name: "Niederschlag und Bewölkung",
hoehe: "regen",
nacht: true,
// minSpanne 2 mm: ohne sie füllte ein Nieselregen von 0,2 mm das
// ganze Feld und sähe aus wie ein Wolkenbruch.
links: { einheit: "mm/h", min: 0, minSpanne: 2, gitter: 3 },
// Die rechte Achse ist die Höhe in Kilometern - sie gehört zum
// Wolkenband, nicht zu einer Spur.
rechts: { einheit: "km", min: 0, max: 14, ticks: [0, 2, 7, 14] },
baender: [
{
id: "wolken", name: "Bewölkung", achse: "rechts",
farbe: FARBE.wolken, maxDeckung: 0.42,
// Open-Meteo kennt drei Schichten statt eines Profils. Die
// Grenzen sind die üblichen meteorologischen: tiefe Wolken bis
// 2 km, mittelhohe bis 7 km, hohe darüber.
schichten: [
{ spalte: "wtief", von: 0, bis: 2, name: "tief" },
{ spalte: "wmittel", von: 2, bis: 7, name: "mittel" },
{ spalte: "whoch", von: 7, bis: 14, name: "hoch" },
],
},
],
spuren: [
{
id: "regen", name: "Niederschlag", spalte: "regen",
achse: "links", art: "balken", farbe: FARBE.regen, legende: true,
format: w => komma(w, 1) + " mm/h",
},
{
id: "schnee", name: "Schnee", spalte: "schnee",
achse: "links", art: "balken", farbe: FARBE.schnee, legende: true,
// Schnee kommt in cm, die Achse rechnet in mm Wasser. Die
// Faustregel ist ein Zentimeter Neuschnee je Millimeter Wasser.
skala: w => w * 1,
format: w => komma(w, 1) + " cm",
},
],
},
{
id: "wind",
name: "Wind",
hoehe: "wind",
links: { einheit: "km/h", min: 0, minSpanne: 20, gitter: 3 },
pfeile: { spalte: "richtung", staerke: "wind", alle: 3, mindestAbstand: 16 },
spuren: [
{
id: "boe", name: "Böen", spalte: "boe",
achse: "links", art: "linie", farbe: FARBE.boe, breit: 1.6,
strich: "4 3", legende: true,
format: w => komma(w, 1) + " km/h",
},
{
id: "wind", name: "Wind", spalte: "wind",
achse: "links", art: "linie", farbe: FARBE.wind, breit: 2.2,
legende: true,
format: w => komma(w, 1) + " km/h",
},
],
},
],
};
})();