Files
geograsim/App/assets/js/glossar-experiments-monsun.js
T
Adminator fecd8bb9c9 Textsystem: Bis-Strich mit Leerzeichen (Zahl – Zahl) per Agent-Sweep
562 Ersetzungen in 50 Dateien: Halbgeviertstrich zwischen zwei Ziffern (=
"bis"-Bereich) bekommt Leerzeichen davor/danach (3–4 -> 3 – 4). Regex
/([0-9])\s*–\s*(?=[0-9])/ auf Rohtext (format-erhaltend, CRLF/Einrueckung
unveraendert), Bindestriche in Woertern und Minuszeichen unangetastet, alle
JSON weiter gueltig. Energiemanager-Zeitbloecke sind Display-Labels (kein
Logik-Key) -> safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 12:38:19 +02:00

195 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Glossar-Experimente — Gruppe „monsun".
*
* Registrierung via window.GlossarExperiments[key_slug] = fn(root).
* Eigener svgEl-Helper, nur bestehende .ge-*-CSS-Klassen (aus glossar.php).
* Keine Libraries. Mobile-/Tap-freundlich. rAF-Loops stoppen bei geschlossenem
* Modal über den root.isConnected-Guard.
*/
(function () {
'use strict';
const NS = 'http://www.w3.org/2000/svg';
const svgEl = (tag, attrs, parent) => {
const el = document.createElementNS(NS, tag);
if (attrs) for (const k in attrs) el.setAttribute(k, attrs[k]);
if (parent) parent.appendChild(el);
return el;
};
const deAT = (n) => Number(n).toLocaleString('de-AT');
// =====================================================================
// Experiment: Monsun-Regen — Regen pro Monat in Mumbai (Balkendiagramm)
// =====================================================================
function monsunRain(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🌧 Wann fällt der Regen in Indien?</h3>
<p>Jeder Balken ist ein Monat in <strong>Mumbai</strong> (Indien). Schau, wie riesig die Balken von <strong>Juni bis September</strong> werden. In dieser kurzen Zeit fällt fast der ganze Jahresregen — davon hängt die Reisernte ab.</p>
</div>
<svg class="ge-mon-svg" viewBox="0 0 600 340" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Balkendiagramm Regen pro Monat in Mumbai"></svg>
<div class="ge-controls">
<button class="ge-btn ge-btn-primary" data-scen="normal">☔ Normales Jahr</button>
<button class="ge-btn" data-scen="weak">🌵 Schwacher Monsun</button>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill" id="ge-mon-share"></div>
<div class="ge-gh-pill" id="ge-mon-total"></div>
</div>
<p class="ge-note" id="ge-mon-note"></p>
</div>
`;
const svg = root.querySelector('.ge-mon-svg');
const sharePill = root.querySelector('#ge-mon-share');
const totalPill = root.querySelector('#ge-mon-total');
const noteEl = root.querySelector('#ge-mon-note');
if (!svg || !sharePill || !totalPill || !noteEl) return;
const MONTHS = ['Jän', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
// Monsun-Monate (0-basiert): JuniSeptember
const MONSOON = [5, 6, 7, 8];
// Regen pro Monat in mm.
// Normal: Mumbai (Colaba) 1981 2010, India Meteorological Department.
const NORMAL = [1, 0, 1, 0, 16, 506, 769, 472, 356, 82, 9, 3];
// Schwacher Monsun (Dürre-Jahr): Spitzen-Monate stark reduziert.
const WEAK = [1, 0, 1, 0, 10, 280, 420, 240, 180, 60, 6, 2];
// Vergleich: Wien fällt Monat für Monat ziemlich gleichmäßig (~60 mm).
const WIEN_REF = 60;
const NOTES = {
normal: 'Regen pro Monat in Mumbai, Normalwerte 1981 2010. Quelle: India Meteorological Department (über „Climate of Mumbai"). Ganzes Jahr rund 2.215 mm. Die dünne Linie zeigt Wien: dort fallen jeden Monat etwa 60 mm — ziemlich gleichmäßig.',
weak: 'Schwacher Monsun (Dürre-Jahr): geschätzte Werte, in denen die Monsun-Monate viel weniger Regen bringen. So etwas macht Bäuerinnen und Bauern Sorge — der Klimawandel lässt den Monsun unberechenbarer werden. Quelle Normalwerte: India Meteorological Department.'
};
// Layout
const X0 = 58, X1 = 588; // Plot links/rechts
const BASE = 278, TOP = 34; // Baseline / oberer Rand
const PLOT_H = BASE - TOP; // Zeichenhöhe
const MAX_MM = 800; // feste Skala (Balken schrumpfen sichtbar)
const PX_PER_MM = PLOT_H / MAX_MM;
const slot = (X1 - X0) / MONTHS.length;
const BAR_W = slot * 0.66;
const yFor = (mm) => BASE - Math.min(MAX_MM, mm) * PX_PER_MM;
// --- Statische Achsen / Gitter ---
// Y-Gitterlinien + Beschriftung
[0, 200, 400, 600, 800].forEach(mm => {
const y = yFor(mm);
svgEl('line', { x1: X0, y1: y, x2: X1, y2: y, stroke: '#dae8ec', 'stroke-width': mm === 0 ? '1.6' : '1' }, svg);
const t = svgEl('text', { x: X0 - 8, y: y + 4, 'text-anchor': 'end', 'font-size': '11', fill: '#4a4a4a', 'font-weight': '600' }, svg);
t.textContent = mm;
});
svgEl('text', { x: X0 - 8, y: TOP - 14, 'text-anchor': 'end', 'font-size': '11', fill: '#4a4a4a', 'font-weight': '700' }, svg).textContent = 'mm';
// Monat-Beschriftung + farbige Monsun-Markierung am Boden
const barNodes = [];
const valNodes = [];
MONTHS.forEach((m, i) => {
const cx = X0 + slot * i + slot / 2;
const isMon = MONSOON.includes(i);
// Balken (Höhe wird animiert gesetzt)
const bar = svgEl('rect', {
x: cx - BAR_W / 2, y: BASE, width: BAR_W, height: 0, rx: 3,
fill: isMon ? '#2f7d8a' : '#a9c7cd'
}, svg);
barNodes.push(bar);
// Wert-Label über dem Balken (nur für die großen Monsun-Balken sichtbar)
const val = svgEl('text', {
x: cx, y: BASE, 'text-anchor': 'middle', 'font-size': '10',
'font-weight': '800', fill: '#1f4e5a', opacity: '0'
}, svg);
valNodes.push(val);
// Monatsname
const lbl = svgEl('text', {
x: cx, y: BASE + 18, 'text-anchor': 'middle', 'font-size': '11',
'font-weight': isMon ? '800' : '600', fill: isMon ? '#1f4e5a' : '#6a6a6a'
}, svg);
lbl.textContent = m;
});
// „Regenzeit"-Klammer über JunSep
const bx0 = X0 + slot * MONSOON[0] + slot * 0.10;
const bx1 = X0 + slot * (MONSOON[MONSOON.length - 1] + 1) - slot * 0.10;
svgEl('line', { x1: bx0, y1: TOP - 6, x2: bx1, y2: TOP - 6, stroke: '#e8833a', 'stroke-width': '2', 'stroke-linecap': 'round' }, svg);
svgEl('text', { x: (bx0 + bx1) / 2, y: TOP - 12, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#e8833a' }, svg).textContent = 'Regenzeit';
// Wien-Vergleichslinie (dünn, gestrichelt) + Label
const wienY = yFor(WIEN_REF);
svgEl('line', { x1: X0, y1: wienY, x2: X1, y2: wienY, stroke: '#c85c4a', 'stroke-width': '2', 'stroke-dasharray': '6 4' }, svg);
svgEl('text', { x: X1, y: wienY - 6, 'text-anchor': 'end', 'font-size': '10.5', 'font-weight': '800', fill: '#c85c4a' }, svg)
.textContent = 'Wien: ~60 mm pro Monat';
// --- Zustand + Animation ---
let animId = null;
let current = NORMAL;
function paint(fromData, toData, scen) {
cancelAnimationFrame(animId);
const dur = 650;
const start = performance.now();
function tick(now) {
if (!root.isConnected) { cancelAnimationFrame(animId); return; }
const t = Math.min(1, (now - start) / dur);
const e = 1 - Math.pow(1 - t, 3);
for (let i = 0; i < barNodes.length; i++) {
const mm = fromData[i] + (toData[i] - fromData[i]) * e;
const y = yFor(mm);
barNodes[i].setAttribute('y', y);
barNodes[i].setAttribute('height', Math.max(0, BASE - y));
// Wert nur bei den großen Balken (> 100 mm) zeigen
if (toData[i] > 100) {
valNodes[i].setAttribute('y', y - 5);
valNodes[i].textContent = Math.round(mm);
valNodes[i].setAttribute('opacity', (t).toFixed(2));
} else {
valNodes[i].setAttribute('opacity', '0');
}
}
if (t < 1) animId = requestAnimationFrame(tick);
}
animId = requestAnimationFrame(tick);
}
function setScenario(scen) {
const data = scen === 'weak' ? WEAK : NORMAL;
const from = current;
current = data;
const total = data.reduce((a, b) => a + b, 0);
const monsoonSum = MONSOON.reduce((a, i) => a + data[i], 0);
const share = Math.round((monsoonSum / total) * 100);
sharePill.innerHTML = `Juni September: <strong>${share} %</strong> vom Jahresregen`;
sharePill.dataset.state = 'warn';
totalPill.innerHTML = `Ganzes Jahr: <strong>${deAT(total)} mm</strong>`;
totalPill.dataset.state = scen === 'weak' ? 'bad' : 'good';
noteEl.textContent = NOTES[scen];
root.querySelectorAll('[data-scen]').forEach(b => {
b.classList.toggle('ge-btn-primary', b.dataset.scen === scen);
});
paint(from, data, scen);
}
root.querySelectorAll('[data-scen]').forEach(b => {
b.addEventListener('click', () => setScenario(b.dataset.scen));
});
// Startanimation von 0 hoch
current = NORMAL.map(() => 0);
setScenario('normal');
}
// Registry — Object.assign, damit Ladereihenfolge egal ist.
window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
'monsun': monsunRain
});
})();