Das Projekt RisikoAtlas hat die Förderung der Risikokompetenz zum Ziel. Zu diesem Zweck wurden am Harding-Zentrum für Risikokompetenz am Max-Planck-Institut für Bildungsforschung digitale Werkzeuge entwickelt, die auf wissenschaftlichen Erkenntnissen beruhen. Neben interaktiven Visualisierungen evidenzbasierter Risikokommunikation, einer App zur Entscheidungsunterstützung und einer Browser-Erweiterung als Leseassistenz sind Lernvisualisierungen zur Verbesserung der Risikokompetenz Teil des Projektes. Das vorliegende Modul ist eines dieser sechs Lernmodule.
Die Lernmodule wurden ursprünglich entwickelt von kf interactive.
Die folgende Liste gibt einen Überblick über die Ziele der Module:
Alle mit einem * versehenen Module greifen über eine API auf eine Datenbank zu. Auf diese Weise rufen sie die benötigten Daten ab, und speichern andererseits Benutzerantworten, um Nutzern den Vergleich zu Anderen zu ermöglichen. Für jedes dieser Module existiert auch eine offline-Version, die ausschließlich auf lokale Daten zugreift.
Im Wurzelverzeichnis liegen die für den Build Prozess notwendigen Konfigurationsdateien. Das Verzeichnis doc/ enthält detailliertere Dokumentationen zu einzelnen Aspekten des Projekts. Alle für die WebApp benötigten Dateien werden in public/ erstellt bzw. dorthin kopiert. Im src/ Verzeichnis befinden sich alle Quelldateien, Bilder und Fonts. Der tasks/ Ordner enthält Javascript-Dateien, die die Teilschritte des Build-Prozesses definieren.
Die grobe Struktur sieht folgendermaßen aus:
├── .editorconfig // Konfiguration für Texteditoren
├── .babelrc // Konfiguration von babel
├── .eslintrc.yml // Konfiguration des Javascript Linters
├── .htmlhintrc // Konfiguration des HTML Linters
├── .sass-lint.yml // Konfiguration des Sass Linters
├── config.js // Konfiguration des Build-Systems
├── gulpfile.babel.js // gulp Datei, verwendet Definitionen unter `tasks/`
├── package.json // npm Abhängigkeiten, Shortcuts für gulp tasks
├── doc/ // Dokumentation in markdown
├── public/ // Zielverzeichnis für den Build-Prozess
├── src/ // Quellverzeichnis
│ ├── fonts // Font Dateien
│ ├── html // HTML 'Templates'
│ ├── img // Bilder und Sprites
│ ├── js // Javascript Quelldateien
│ └── scss // Sass stylesheets
└── tasks/ // Definitionen für den gulp Build-Prozess
Die Code Style Konventionen wurden von den ursprünglichen Entwicklern übernommen und nur an wenigen Stellen angepasst. Der Javascript Code ist in ES6 (bzw. ES2015) verfasst und als CSS-Preprocessor wird Sass mit der scss Syntax verwendet.
Das Projekt verwendet editorconfig für die Integration dieser Konventionen in Editoren, die entsprechende Datei heißt .editorconfig.
Für die statische Überprüfung des Quellcodes werden folgende Linter verwendet:
Die zugehörigen Konfigurationsdateien befinden sich im Root-Verzeichnis, wie oben in der Auflistung angegeben.
Dieses Modul stellt ein interaktives Werkzeug als Hilfe zur Verbesserung des Verständnisses von Diagrammen bereit.
Dazu werden den Nutzern Multiple-Choice-Fragen zu Charakteristiken des gezeigten Graphen präsentiert. Die Lösung und gegebenenfalls die falsch gewählte Antwort werden grafisch hervorgehoben. Ein zweiter Typ von Test ermöglicht es den Nutzern, Teile des Diagramms auszuwählen und zu skalieren, um einen in der Frage vorgegebenen Effekt zu erzielen. Dies soll zeigen, wie einfache Manipulationen die Interpretation von Graphen beeinflussen können. Erst wenn eine korrekte Lösung gefunden wurde, kann zur folgenden Aufgabe weitergegangen werden.
Für einen besseren Überblick sind die Quell-Dateien mit kurzen Beschreibungen aufgelistet:
├── main.jsx // Einstiegspunkt für App *mit* Verwendung der API ("online mode")
├── main-offline.jsx // Einstiegspunkt für App *ohne* Verwendung der API ("offline mode")
├── config.js
├── components // (p)react Komponenten
│ ├── Index.jsx // Web App Haupt-Komponente
│ └── partials
│ ├── GraphItem.jsx // Komponente für d3 Visualisierungen
│ ├── IntroItem.jsx // Komponente für die Einführung zu den Fragen
│ └── QuestionItem.jsx // Komponente für Benutzereingaben für Fragen
├── content
│ ├── Gruppe-x_item-y.json // Definitionen der Fragen
│ ├── module.json // Definition der (meisten) Labels und Texte des User Interfaces
│ └── offline.js // Definition der Reihenfolge der Fragen
├── modules // d3 Module
│ ├── configuration.js // Konfiguration für d3-Module
│ ├── axes.js // Achsendefinitionen
│ ├── main.js // d3 Haupt-Modul
│ ├── defs.js // Defintionen für SVG-Element <defs> (clip-path, gradient)
│ ├── gradient.js // Darstellung der Steigung innerhalb eines Intervalls (Fragetyp 3)
│ ├── grid.js // Skalierendes Hintergrundraster
│ ├── handleConnector.js // Definition der Verbindungslinien zwischen Anfassern (Fragetyp 3)
│ ├── handleSymbols.js // Definition der Geometrie der Anfasser als SVG symbols (Fragetyp 3)
│ ├── handle.js // Konstruktor und Definition des Verhaltens der Anfasser (Fragetyp 3)
│ ├── legend.js // Legende der Graphen
│ ├── bar.js // Balkendiagramm als Feedback für Benutzer
│ ├── lineGraphs.js // Darstellung der Graphen
│ ├── points.js // Hervorhebung der Lösung / falschen Antwort (Fragetyp 1)
│ ├── scales.js // d3 Skalen für x- und y-Achsen
│ ├── range.js // Definition eines Auswahlbereichs (Fragetyp 3)
│ └── rangeController.js // Controller für Auswahlbereich; Kommunikation zwischen Visualisierungs-Modulen (untereinander) und Anwendung
└── utilities
├── api.js // API für Lesen aus / schreiben in Datenbank
├── touch.js
├── fonts.js
├── formatter.js
└── math.js
├── randomizer.js
└── validator.js // Funktionen zur Validierung der Benutzerantworten
In diesem Abschnitt werden die technischen Voraussetzungen für die Erstellung von im Browser lauffähigem Code und und die Installation und Verwendung der Entwicklungsumgebung erläutert.
Dieses Projekt wurde entwickelt auf Basis von nodejs unter Verwendung von npm als Paket-Manager. Mit den folgenden Versionen wurde zuletzt getestet:
nodejs: v14.4.0
npm: 6.14.4
Alle Abhängigkeiten sind definiert in der npm Konfigurations-Datei package.json. Wie üblich werden diese installiert mit dem Befehl npm install. gulp wird dabei(??? ist das zutreffend?) als Task-Manager dieses Projekts global installiert.
TODO: Testen mit node LTS 12..
Die Build Konfiguration ist in config.js im Wurzelverzeichnis definiert. Außerdem sind in der Datei package.json die zu unterstützenden Browser-Versionen für autoprefixer angegeben.
Konfigurationen für Babel, Editoren und Linter sind ebenfalls im Wurzelverzeichnis zu finden:
babel: .babelrc
editorconfig: .editorconfig
html: .htmlhintrc
javascript: .eslintrc.yml
sass: .sass-lint.yml
Alle Schritte zum Erstellen von Builds sind in den Javascript-Dateien unter tasks/ definiert und werden von der gulp Konfigurationsdatei gulpfile.babel.js importiert. Dort sind die Teilschritte in Tasks zusammengefasst, die man am häufigsten benötigt.
$ gulp # Default task, Kurzform für 'gulp watch'
$ gulp build # Erstellt einen Development Build
$ gulp watch # Erstellt einen Development Build und startet den Entwicklungsserver
Die Unterscheidung zwischen Development und Production Build wird anhand der nodejs Umgebungsvariable NODE_ENV vorgenommen. Ohne diese Angabe wird immer ein Development Build erstellt (siehe config.js). Für das Development Target werden Javascript und CSS zusätzlich mit Sourcemaps versehen, für die Produktiv-Version dagegen werden die Dateien von unnötigem Ballast befreit (terser für Javascript, cssnano für CSS).
# Erstellen eines Production Build
$ NODE_ENV=production gulp build
Zusätzlich gibt es für dieses Projekt die Unterscheidung zwischen ‘online’ und ‘offline’ Versionen. Im Fall der ‘online’ Version wird auf eine API zugegriffen, um die benötigten Inhalte zu laden, und um die Antworten der Benutzer zu speichern, um ihnen einen Vergleich mit Anderen zu ermöglichen. Diese Unterscheidung kann beim Aufruf von gulp auf der Kommandozeile mit einem Parameter getroffen werden. Soweit verfügbar, wird standardmäßig der ‘online’ Modus verwendet, (siehe config.js).
# Erstellen eines Development Build für den 'offline' Modus
$ gulp --api-mode=offline
Da die Handhabung mit dem Setzen der Umgebungsvariable und das Übergeben des Parameters etwas umständlich ist, sind in package.json npm ein paar Shortcuts definiert, z.B.:
# Erstellen eines Production Build für den 'offline' Modus per gulp Script
$ NODE_ENV=production gulp build --api-mode=offline
# Erstellen eines Production Build für den 'offline' Modus per npm Script
$ npm run build:prod:offline
Der Javascript Code ist in ES6 (bzw. ES2015) verfasst. Als CSS-Preprocessor wird Sass mit der scss Syntax verwendet. Die Code Style Konventionen wurden von den ursprünglichen Entwicklern übernommen und nur an wenigen Stellen leicht angepasst.
Erleichtert wird die Anwendung des definierten Programmierstils durch die Integration in Editoren mit Hilfe von editorconfig. Die entsprechende Konfigurationsdatei ist im Wurzelverzeichnis als .editorconfig zu finden.
Die statische Überprüfung des Javascript Quellcodes wird mit Hilfe von eslint durchgeführt.Für Sass wird sass-lint verwendet und HTML wird mit Hilfe von HTMLHint überprüft.
Die zugehörigen Konfigurationsdateien befinden sich im Wurzelverzeichnis, wie oben in der Auflistung angegeben.
Die grundlegende Architektur der WebApp wurde implementiert auf Basis von preact, von den Entwicklern beworben mit
Fast 3kB alternative to React with the same modern API.
Es ist allerdings keine exakte Reimplementierung, weswegen ein eigener Teil der Dokumentation der Erläuterung der Unterschiede zu React gewidmet ist.
Für Visualisierungen wird die großartige und weit verbreitete Bibliothek D3.js verwendet.
Die folgende Auflistung gibt einen groben Überblick über die Verzeichnisstruktur der Javascript Quelldateien in src/js/. Als Einstieg dient main.jsx bzw. main-offline.jsx für den “offline” Modus. config.js ist die zentrale Konfigurationsdatei der WebApp. In components liegen die Komponenten der preact-WebApp. Der Quellcode für die D3-Visualisierungen befindet sich unter d3.
├── main.jsx // Einstiegspunkt für App in "online" Modus
├── main-offline.jsx // Einstiegspunkt für App in "offline" Modus
├── config.js // Konfiguration der WebApp
├── components/ // Verzeichnis für (p)react Komponenten
│ ├── Index.jsx // Web App Haupt-Komponente
│ └── partials/ // Vezeichnis für Teilkomponenten
├── content/ // Verzeichnis für "offline" Inhalte
├── d3/ // d3 Module
└── utilities/ // Verzeichnis für Hilfs-Bibliotheken und Werkzeuge
Zum Kompilieren von Sass zu CSS wird gulp-sass verwendet, das node-sass benutzt, welches wiederum auf libsass basiert. node-sass hat sich beim wiederholten gedankenlosen Aktualisieren von nodejs und / oder npm als notorischer Nerventöter herausgestellt, daher an dieser Stelle der Verweis zur Troubleshooting Dokumentation von node-sass. Meist reichte im Falle eines Problems aber ein npm rebuild node-sass.
In der Datei main.scss werden alle Stile eingebunden, die in den Partials definiert werden, woraus die endgültige CSS-Datei generiert wird. Die Struktur des src/scss Verzeichnisses sieht folgendermaßen aus:
├── base // Stile für HTML Elemente
├── config // Globale Variable
├── modules // Stile für Module
└── tools // Definierte *mixins* und Funktionen
Generell wird eine “mobile first” Strategie verfolgt. Als Standard-Einheit wird rem verwendet, auf deren Grundlage die Basis-Einheit definiert ist. Da sich alle Größen auf diese Einheit beziehen sollten, wird so das Skalieren des Layouts erleichtert.
Sass wird in diesem Projekt mit der scss-Syntax verwendet. Stilistisch ist es in “oldschool BEM-Style” gehalten, Zitat der ursprünglichen Entwickler. Sie beziehen sich zudem auf bestimmte Guidelines:
Hugo Giraudel wrote an awesome piece on everything you need to know about Sass, it’s called Sass Guidelines and you should really have a look at it. I agree with this guideline in almost all points, but I try to keep something more simple, and some things more strict, the linter will let you know :)
ʕ̡̢̡ॢ•̫͡ॢ•ʔ̢̡̢
Regeln mit Browser-spezifischen Präfixen (vendor prefixes) werden dem CSS automatisch durch autoprefixer hinzugefügt. Die Liste der zu unterstützenden Browser ist in package.json unter browserslist zu finden.
Alle in diesem Projekt verwendeten Bilddateien befinden sich unter src/img/.
Icons in Form von SVG-Dateien befinden sich im Unterordner src/img/sprites und werden im Build-Prozess mittels gulp-svg-sprite zu einem Sprite zusammengefasst. Sie wie folgt in HTML referenziert werden:
<svg class="icon icon--arrow-left">
<use xlink:href="assets/img/sprites.svg#icon--arrow-left"/>
</svg>
Stil-Definitionen für Icons sind unter src/scss/modules/_icons.scss zu finden. Für die Unterstützung von Fragmentbezeichnern (fragment identifier) in Internet Explorer wird svgxuse verwendet.
Ursprünglich wurde dieses Modul als Auftragsarbeit von einer externen Firma entwickelt. Unglücklicherweise wurden während der Entwicklungsphase keine Code-Audits durchgeführt und die Qualität des gelieferten Codes ließ zu wünschen übrig. Teile des Codes mussten komplett neu entwickelt werden, da eine Fehlerbehebung nicht anders möglich erschien. Dies führte zu einer heterogenen Software-Architektur, da bei der Neuentwicklung in erster Linie auf Wartbarkeit und Verständlichkeit Wert gelegt wurde und einer angemessenen Dokumentierung.
Teile des Codes mussten neu entwickelt werden, da insbesondere die originale Implementierung der Visualisierungs-Komponente größtenteils unwartbar war. Einfache Fehler zu beheben war nur schwer möglich, da der Code mit einer unerkennbaren Logik in Module aufgeteilt war und gängige Techniken und von d3 ignoriert wurden.
Ein “Modul” mit dem Namen “redraw” war nur dazu gedacht, SVG-Elemente, die in anderen Code-Fragmenten erstellt wurden, zu aktualisieren. ﴾͡๏̯͡๏﴿
Um Wartbarkeit als Feature zu integrieren, wurde stattdessen die damals übliche Vorgehensweise implementiert (General update pattern). Inzwischen wird diese schon wieder als veraltet bezeichnet, und die Verwendung von selection.join empfohlen.
Multiple-Choice-Fragen wurden ursprünglich gegen die Labels der Antworten validiert. Dabei wurde die Javascript Funktion parseInt auf alphanumerische Strings angewendet, bzw. unflexible Reguläre Ausdrücke verwendet, die scheitern würden, sobald sich die Struktur der Labels oder die natürliche Sprache änderte. ( ・_・)ノ⌒●~*
Um dieses Problem zu beheben, wurde dem offline-Datensatz die notwendigen Daten hinzugefügt, um mathematische Validierung über Funktionen zu ermöglichen. Für jede Frage wird nun eine Referenz zu einer Validator-Funktion angegeben, die die Daten, die mit der gegebenen Antwort verknüpft sind, mit dem errechneten Ergebnis vergleicht. Aktuell ist die online-Version des Moduls nicht funktionsfähig, da die API noch nicht aktualisiert wurde (Stand Juni 2020).
Die aktuelle Implementierung ist nach wie vor sehr unflexibel bezogen auf die Anordnung der verschiedenen Fragetypen. Die Fragen sind in einem zweidimensionalen Array angeordnet, wobei der erste Index den Fragetyp und der zweite die Reihenfolge innerhalb der Gruppe definiert. Verschiedene Fragetypen können daher momentan nicht willkürlich abgewechselt werden. Immerhin wurde der größte Fehler behoben, der beim einfachen Umordnen von Fragen innerhalb einer Gruppe zum “abstürzen” der Web App führte. ٩(̾●̮̮̃̾•̃̾)۶
Die Implementierung des Aufgabentyps zum Manipulieren von Graphen war fehlerhaft. Das Ergebnis wurde nur dann richtigerweise als korrekt erkannt, wenn exakt das Intervall wie in der Aufgabenstellung ausgewählt wurde. Mehrere “falsche Lösungen” konnten gefunden werden. Dadurch konnte der Nutzer mit der folgenden Aufgabe fortführen, auch wenn die Steigung des betrachteten Intervalls außerhalb der Toleranzgrenzen lag. Auch waren Konstellationen möglich, bei denen der betrachtete Teil des Graphen außerhalb des sichtbaren Bereichs lag. Transformationen des Auswahlbereichs wurden durch das Parsen von Werten aus dem SVG DOM mit Hilfe von wackeligen Regulären Ausdrücken durchgeführt. Fügte man der Transformation eine Rotation hinzu, funktionierte nichts mehr. Das weckt die Vermutung, dass die Geometrie der Anfasser aus diesem Grund zweimal definiert wurde, siehe 5.
Dieser Teil wurde nahezu komplett neu implementiert, da der ursprüngliche Code zusätzlich zu den beschriebenen Fehlern unverständlich, verworren und dadurch unwartbar war. Es wurden “Techniken” verwendet, bei denen ein Modul in den SVG-DOM hineinschrieb und ein anderes Modul diesen Wert wieder auslas. ⊂(©෴©)つ
Zur Verbesserung der Verständlichkeit wurde eine Controller-zentrische Architektur in Verbindung mit spezifischem Event-Dispatching gewählt. Zur Validierung der Nutzer-gewählten Auswahl werden drei Auswahlbereiche definiert: die reference range bezeichnet den ursprünglich ausgewählten Bereich, auf den der benutzerdefinierte Bereich (selected range) initialisiert wird. Der Graph wird anhand letzterer transformiert. Die solution range definiert die exakte Lösung und wird für die Validierung der Nutzerauswahl verwendet. Sobald der betrachtete Teil des Graphen sichtbar ist und das Validierungskriterium der Frage erfüllt ist, wird die benutzerdefinierte Auswahl als Lösung anerkannt und das Fortfahren zur folgenden Frage wird ermöglicht.
Die Geometrie der Anfasser zum Skalieren der Graphen wurde zweimal definiert, einmal für die horizontale und einmal für die vertikale Version, auch wenn sie sich nur durch eine 90 Grad Rotation unterscheiden (ganz der Wahrheit entsprechend ist das nicht, die inneren Linien waren in einem Fall falsch ausgerichtet). Auch dieser Code war so verworren und verständlich wie ein Stück Hirn in Aspik. In einem Aufruf der Funktion bezog sich die Variable “width” tatsächlich auf die Breite, im anderen Fall auf die Höhe (und umgekehrt bei “height”). Dieser Fakt wurde, vermutlich aus Scham, mit keinem Kommentar erwähnt. ℃ↂ_ↂ
Die Geometrie ist nun nur einmal definiert und wird als SVG Symbol jeweils referenziert und durch Matrizen transformiert. Nebenbei wurde ein zeichnerisches Problem behoben.
Dies sind nur Beispiele für die Probleme, die der ursprüngliche Code mitgebracht hat. Die Liste ist sicherlich nicht vollständig und es befinden sich höchstwahrscheinlich nach wie vor Eigenheiten und Fehler im Code, die man hinterfragen kann bzw. sollte.