/** * * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2025 Florian Fischer * * * The JavaScript code in this page is free software: you can * redistribute it and/or modify it under the terms of the GNU * General Public License (GNU GPL) as published by the Free Software * Foundation, either version 3 of the License, or (at your option) * any later version. The code is distributed WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. * * As additional permission under GNU GPL version 3 section 7, you * may distribute non-source (e.g., minimized or compacted) forms of * that code without the copy of the GNU GPL normally required by * section 4, provided you include this license notice and a URL * through which recipients can access the Corresponding Source. * * @licend The above is the entire license notice * for the JavaScript code in this page. * */ // Observer der auf 'resize' events unseres Graphs reagiert und die // Streak Anzeige gegebenfalls anpasst. let graphResizeObserver; // Zeichne das Diagramm und erstelle eine Liste aller Events, // wenn die Seite komplett geladen wurde window.onload = function() { zeichne(); zeichneBilder(); zeichneStreak(); listeEvents(); } var diagramm; function zeichne() { // Wir haben keine Daten zum Zeichnen if (!daten || !daten.klassen) return; // Zeige den Indikator, dass das Diagramm gezeichnet wird let zeichnenIndikator = document.getElementById("zeichnen"); zeichnenIndikator.style.display = "block"; let zeichenort = document.getElementById("zeichenort"); zeichenort['aria-busy'] = true; const ctx = document.getElementById('zeichenflaeche'); // Verwerfe ein bereits gezeichnetes Diagramm if (diagramm) diagramm.destroy(); // Erzeuge ein neues Balkendiagramm mit unseren Daten diagramm = new Chart(ctx, { type: 'bar', options: { animation: false, // Deaktiviere die Animation beim Zeichnen der Balken plugins: { legend: { display: false // Wir benötigen keine Legende für den Punktestand }, tooltip: { enabled: false // Deaktiviere die Tooltips, die nur den Punktestand zeigen } } }, data: { // Die Klassenbezeichnungen sind unsere X-Achsen Bezeichner labels: daten.klassen.map(klasse => klasse.bezeichner), datasets: [ { label: 'Punkte', data: daten.klassen.map(klasse => klasse.punkte), backgroundColor: daten.klassen.map(klasse => klasse.farbe) } ] }, }); // Verstecke den Zeichenindikator zeichnenIndikator.style.display = "none"; zeichenort['aria-busy'] = false; } // Lade und zeichne die Bilder der Klassen an die Spitze ihrer jeweiligen Balken async function zeichneBilder() { // Das Objekt, das alle unsere Bilder Annotationen enthält let annotation = {annotations: {}}; for (const i in daten.klassen) { let klasse = daten.klassen[i]; // Die Url des Klassenbildes ist der Klassenbezeichner unter dem Bilderpfad let bildUrl = `${bilder_pfad}/${klasse.bezeichner}.png`; // Teste, ob das Bild existiert let ok = (await fetch(bildUrl, {method: 'HEAD'})).ok; // Das Bild existiert nicht if (!ok) { continue; } // Erzeuge ein neues Bild und verwende die Bild URL unter der ein Bild gefunden wurde let bild = new Image(); bild.src = bildUrl; // Zeichne das Bild knapp unter dem oberen Rand des Balkens let klassenAnnotation = { drawTime: 'afterDatasetsDraw', type: 'label', content: bild, width: 75, height: 75, xValue: klasse.bezeichner, yValue: klasse.punkte - 1 }; annotation.annotations[klasse.bezeichner] = klassenAnnotation; } // Ergänze die Diagramm-Optionen um unsere Annotationen diagramm.options.plugins.annotation = annotation; // Zeichne das Diagramm neu mit unseren Annotationen diagramm.update(); } // Zeichne den Streak für alle Klassen. // Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable // aller Ereignisse, die für den Streak relevant sind, enthalten. // Ereignisse ohne positives Delta beenden einen Streak. function zeichneStreak() { // Es gibt keine Streak-Ereignisse if (!daten.streakEreignisse) { return; } // Variable um den laufenden Streak per Klasse zu bestimmen let streaks = Object.fromEntries(daten.klassen.map(klasse => [klasse.bezeichner, 0])); for (let ereignis of daten.streakEreignisse) { const klasse = `${ereignis.Jahrgang}${ereignis.Bezeichner}`; // Das Streak Ereignis bringt keine Punkte und unterbricht damit den Streak. if (ereignis.Delta == 0) { streaks[klasse] = 0; // Erhöhe den Streak. } else { streaks[klasse] = streaks[klasse] + 1; } } let streakNodes = document.getElementById('streaks'); for (const klasse of Object.keys(streaks)) { const streak = streaks[klasse]; let streakNode = document.createElement("td"); streakNode.classList.add("streak"); streakNode.innerHTML = `${streak}/${streak_anzahl} ${streak_ziel}`; // Füge den Streak-Node der Klasse den Streak Nodes auf der Webseite hinzu. streakNodes.appendChild(streakNode); } const zeichenflaeche = document.getElementById('zeichenflaeche'); let streakTabelle = document.getElementById('streaks-tabelle'); // Setze die Breite passend zum Graphen streakTabelle.setAttribute("style", `width:${zeichenflaeche.style.width}px`); // Erzeuge ein Observer der auf Größenveränderungen des Graphen reagiert. graphResizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { // Passe die Breite unserer Streak-Tabelle an die des Graphen an. streakTabelle.setAttribute("style", `width:${entry.contentRect.width}px`); } }); graphResizeObserver.observe(zeichenflaeche); } // Erzeuge eine Liste aller Ereignisse. // Die globale Variable daten.ereignisse muss definiert sein und ein Iterable aller // Ereignisse, die angezeigt werden sollen, enthalten. function listeEvents() { let liste = document.getElementById("ereignisse"); for (let ereignis of daten.ereignisse) { // Erzeuge ein Listenelement, das alle Infos des Ereignisses enthält let ereignisNode = document.createElement("li"); ereignisNode.classList.add("ereignis"); // Span, das die Meta-Informationen (Klasse, Datum) eines Ereignisses enthält let ereignisUrsache = document.createElement("span"); ereignisUrsache.innerHTML = `${ereignis.Jahrgang}${ereignis.Bezeichner}` if (ereignis.Datum) { ereignisUrsache.innerHTML += ` ${ereignis.Datum}` } ereignisUrsache.classList.add("ereignis-klasse"); ereignisNode.appendChild(ereignisUrsache); // Die Veränderung des Punktestands durch das Ereignis let ereignisDelta = document.createElement("span"); ereignisDelta.innerHTML = `${ereignis.Delta}`; ereignisDelta.classList.add("ereignis-delta"); // Ergänze positive und negative CSS Klassen if (ereignis.Delta > 0) { ereignisDelta.classList.add("positiv"); } else { ereignisDelta.classList.add("negativ"); } ereignisNode.appendChild(ereignisDelta); // Die Beschreibung des Ereignisses let ereignisBeschreibung = document.createElement("span"); ereignisBeschreibung.innerHTML = `${ereignis.Beschreibung}`; ereignisBeschreibung.classList.add("ereignis-beschreibung"); ereignisNode.appendChild(ereignisBeschreibung); // Füge das neue Ereignis-Listenelement der Ereignis-Liste hinzu. // Jedes neue Element wird als erstes Kind eingefügt, damit // neuere Ereignisse weiter oben in der Liste stehen liste.insertBefore(ereignisNode, liste.firstChild); } }