Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -1,36 +1,93 @@
name: Deploy Angular to GitHub Pages
name: CI and deploy to GitHub Pages

on:
push:
branches:
- main # or master, depending on your default branch
- main
pull_request:

# Least privilege by default; only the deploy job may push to gh-pages.
permissions:
contents: read

jobs:
build-and-deploy:
verify:
runs-on: ubuntu-latest
concurrency:
group: verify-${{ github.ref }}
cancel-in-progress: true

steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: 🟢 Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v7
with:
node-version: 25
node-version-file: .nvmrc
cache: npm

- name: 📦 Install dependencies
run: npm install
run: npm ci

- name: 🔍 Lint
run: npm run lint

# Vitest runs the unit tests in Playwright's headless Chromium (version pinned by package-lock).
# Not cached on purpose: Playwright advises against it, restoring takes about as long as the
# download, and --with-deps has to install the Linux system packages either way.
- name: 🎭 Install Playwright Chromium
run: npx playwright install --with-deps --only-shell chromium

# The test command lives in package.json ("test:ci") so the runner can be swapped in one place.
- name: 🧪 Unit tests
run: npm run test:ci
env:
# The runner image points CHROME_BIN at its own Google Chrome, which the Angular test
# builder would launch instead of the pinned Playwright Chromium.
CHROME_BIN: ''

- name: 🛠️ Build Angular app
run: npm run build -- --base-href=/glyphspace/ --configuration production

- name: 📄 Create .nojekyll file
run: echo > dist/glyphspace/browser/.nojekyll

# The builder writes the licence list next to browser/, which is the only folder published.
- name: 📜 Publish third-party licences
run: cp dist/glyphspace/3rdpartylicenses.txt dist/glyphspace/browser/

- name: 📤 Upload site artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v7
with:
name: site
path: dist/glyphspace/browser/
include-hidden-files: true # keep .nojekyll
if-no-files-found: error
retention-days: 1

deploy:
needs: verify
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: gh-pages-deploy
cancel-in-progress: false

steps:
- name: 📥 Download site artifact
uses: actions/download-artifact@v8
with:
name: site
path: site

- name: 🚀 Deploy to gh-pages
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/glyphspace/browser/
publish_dir: ./site
publish_branch: gh-pages
enable_jekyll: false # this adds the .nojekyll file automatically
enable_jekyll: false # this adds the .nojekyll file automatically
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
/.sass-cache
/connect.lock
/coverage
# Vitest browser mode: screenshots of failed tests (next to the spec) and their attachment copies
__screenshots__/
/.vitest-attachments
/libpeerconnection.log
npm-debug.log
testem.log
Expand All @@ -43,7 +46,8 @@ Thumbs.db

# Miscellaneous
/.angular/cache
/src/assets/__pycache__
__pycache__/
.pytest_cache/
/src/assets/tests
/.claude
/.angular
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
86 changes: 38 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@ Glyphspace is a complete rewrite of the [Glyphboard application](https://github.

See the running demo on Github pages: [https://visual-engineers.org/glyphspace/](https://visual-engineers.org/glyphspace/).

## Usage
## Using the Wizard

... coming soon.
The preprocessing wizard guides you through six steps to prepare your data for visualization:

1. **Upload Data** — Load your CSV file.
2. **Select Columns** — Choose columns to keep; ID and low-value fields can be deselected.
3. **Configure Data & Features** — Set encoding, scaling, missing-value and outlier handling per column.
4. **Projection** — Select columns for dimensionality reduction and pick projection methods (PCA, UMAP, t-SNE).
5. **Glyphs & Color** — Choose glyph features and color encoding for the dashboard's initial view (both changeable later).
6. **Review & Process** — Review your configuration and process the data.

**Undo/Redo:** Use the buttons in the wizard's title bar or Ctrl+Z / Ctrl+Shift+Z (Cmd on Mac) to step back and forth through your changes.

**Try with sample data:** Step 1 offers a Wine Quality (red) dataset as a starter (P. Cortez et al., UCI Machine Learning Repository, CC BY 4.0).

Developer notes on the wizard's architecture are in [docs/wizard.md](docs/wizard.md).

## Data Import

### Dynamic Import

Inside of the running application, you can upload suitable CSV files into the filesystem of Micropython running as a web assembly behind the scenes (see below for CSV file structure). You can also upload ZIP folders with thumbnails for your data. The filename must be the same as your CSV file and the contained files should be named according to the IDs of the data rows.
Inside of the running application, you can upload suitable CSV files into the filesystem of Pyodide (Python compiled to WebAssembly, loaded from the jsDelivr CDN; the version is set in `src/app/workers/pyodide.worker.ts`; see below for CSV file structure). You can also upload ZIP folders with thumbnails for your data. The filename must be the same as your CSV file and the contained files should be named according to the IDs of the data rows.

### Preprocessed Data

Expand All @@ -26,7 +39,7 @@ You can either process CSV files via the command line using:
python tools/process.py <your_csv_file>
```

Which creates the necessary data files inside the `src/assets/data` folder of the project and recreates the file `src/defauult-dataset.ts` which the application picks up after starting. You can also store thumbnails in the folder `src/assets/data/thumbnails/<your_data_name>`, which are in JPG format and have the ID of your data items as filename.
Which creates the necessary data files inside the `src/assets/data` folder of the project and recreates the file `src/default-dataset.ts` which the application picks up after starting. You can also store thumbnails in the folder `src/assets/data/thumbnails/<your_data_name>`, which are in JPG format and have the ID of your data items as filename.

All columns except IDs and positional information are converted with a label encoder if they are strings and then normalized for processing of the meta data (including a histogram, min, max, etc.) as well as a PCA, UMAP, and tSNE multidimensional reduction.

Expand Down Expand Up @@ -55,65 +68,42 @@ Notable changes:
- in feature.json `default-context` is renamed to `defaultcontext` for the same reasons
- IDs are treated as strings

## Angular Development Instructions

This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.13.

### Development server

To start a local development server, run:

```bash
ng serve
```

Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.

### Code scaffolding
## Development

Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
**Node version:** v24 (see `.nvmrc`)

```bash
ng generate component component-name
```

For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:

```bash
ng generate --help
```

### Building

To build the project run:
### Setup

```bash
ng build
npm ci # Install dependencies (prefer ci over install for reproducible builds)
npx playwright install chromium # Once per machine: browser for the unit tests
npm start # Start local dev server at http://localhost:4200/
npm test # Run unit tests (Vitest in headless Chromium, watch mode)
npm run test:ci # Run unit tests once (CI mode)
npm run lint # Run ESLint
npm run build # Build for production (output in dist/)
```

This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.

### Running unit tests
### Python tools

To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
The project includes Python preprocessing tools that run locally via `tools/process.py` (CLI; the dataset file generation lives in `tools/dataset_common.py`, the in-browser import is the wizard's pipeline in `src/assets/preprocessing_processor*.py`):

```bash
ng test
python tools/process.py <your_csv_file> # Process a CSV and generate src/default-dataset.ts
python tools/process.py # Rebuild default dataset list
python -B -m pytest -p no:cacheprovider tools/tests # Run Python tests
```

### Running end-to-end tests
Other tools:
- `tools/make-sample-csv.py` — Generate sample data

For end-to-end (e2e) testing, run:

```bash
ng e2e
```
### Deployment

Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
The app is deployed to GitHub Pages on every push to `main`. See `.github/workflows/deploy.yaml` for the workflow (runs tests, lints, builds on PRs; publishes on merge to main).

### Additional Resources
## Licence

For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
GlyphSpace is licensed under the MIT License (see [LICENSE](LICENSE)). It bundles [DruidJS](https://github.com/saehm/DruidJS) for the dimensionality reductions, which is licensed under the LGPL-3.0-or-later. The deployed site ships the LGPL and GPL texts and a notice in `licenses/` (source: `src/licenses/`), plus `3rdpartylicenses.txt` listing all bundled packages.

<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
Expand Down
22 changes: 21 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,29 @@ Eigenständiges Folge-Arbeitspaket zu A2, umgesetzt und gemergt (7 neue Unit-Tes

---

## Review-Runde (September 2026)

Ergebnis eines Code-, UI- und Barrierefreiheits-Reviews des Integrations-Branches. Details für Entwickler: `docs/wizard.md`.

- **Korrektheit / Undo** — Jede Nutzeraktion ist genau ein Undo-Schritt: Schritt 3 füllte die Historie beim Öffnen (Ausreißerzählung), „Reset to Default“/„Apply to same type“ und die Master-Checkbox erzeugten mehrere Einträge, Projektionsänderungen umgingen Historie und Speicherung. Toast-„Undo“ macht nur noch die eigene Aktion rückgängig (`undoIfLatest`). Kein Undo während der Verarbeitung (inkl. FastMap). Schließen während der Schritt-2→3-Animation, doppelter Start in Schritt 6 und der Shift-Klick-Bereich nach Filterwechsel korrigiert.
- **Sicherheit** — Python-Code-Injection über Spaltennamen im CSV-Header bzw. Dateinamen behoben: Werte gehen nur noch als Daten an Pyodide (`workers/pyodide-calls.ts`). Dateinamen wie `pandas.py` oder `../` werden abgelehnt; ein Zip namens `lib.zip` kann `/lib` nicht mehr löschen. Worker-Anfragen haben Request-IDs. Stored XSS im Dashboard-Tooltip behoben: CSV-Werte, Spaltennamen und IDs wurden per `bypassSecurityTrustHtml` als HTML eingefügt; der Tooltip rendert sie jetzt als Text.
- **Pipeline-Optionen jetzt wirksam** — Wirkungslos waren bisher: Fehlende Werte „Remove rows“ je Spalte, Ausreißer „Remove“/„Cap“, Encodings „Normalize“/„Standardize“, „Remove duplicates“ bei CSVs ohne ID-Spalte, UMAP-Mindestabstand 0. Außerdem behoben: Absturz bei Label-Encoding nach Zeilenlöschung, verrutschte Tooltip-Werte, String-Füllwert in Zahlenspalten, Ausreißer bei Boolean-Spalten, vertauschte „Breitengrad“/„Längengrad“. Neue Fehlerklasse K8 (Füllwert passt nicht). 140 Python-Tests (`tools/tests/`).
- **Fehlende Werte / Datenqualität** — Tooltips zeigen „missing“ statt „0“ (bzw. „nan“ aus der CLI). Fehlende Zahlen und Datumswerte werden erst nach der Skalierung auf 0 gesetzt und verzerren die echten Werte nicht mehr (vorher z. B. alle Datumswerte in ~4 % des Bereichs, fehlende Daten als 1970-01-01). Boolean-Beschriftungen waren vertauscht, wenn die erste Zeile „True“ war. Die Auswahlfelder in Schritt 3 zeigten „None“/„Keep“, obwohl eine andere Einstellung aktiv war. CLI (`tools/process.py`): Beschriftungen nach One-Hot-Spalten verschoben, Tooltips für Kategorien mit Unterstrich, Kategorien bei Label-Encoding mit fehlenden Werten — behoben.
- **Schritt-Aufteilung** — Schritt 4 „Projection“ und Schritt 5 „Glyphs & Color“ statt eines gemeinsamen Visualisierungsschritts; Review ist Schritt 6. Schritte über Namen statt Nummern (`shared/constants/step-info.ts`), gespeicherte Zustände werden migriert.
- **Wizard-UI** — Undo/Redo in der Titelleiste neben „Start over“ (Icon `restart_alt`, Bestätigung als nativer Dialog). Schritt 3 zeigt die Folgen einer Einstellung mit echten Zahlen der Daten; Schritt 1 bietet Beispieldaten (Wine Quality, CC BY 4.0); die Schrittleiste fasst getroffene Entscheidungen zusammen. Laufende Hintergrund-Projektionen lassen sich einzeln abbrechen und neu starten.
- **Barrierefreiheit** — Wizard als modaler Dialog (Fokusfalle, Escape, Fokus-Rückgabe), Fokus und Ansage bei Schrittwechsel, Datei-Upload per Tastatur, Beschriftungen für alle Bedienelemente, Tastatur-Alternative zum Drag-and-drop der Glyph-Merkmale, Live-Regions für Status und Toasts, Tooltips per Fokus öffnen und per Escape schließen, Fortschrittsbalken und Fehlermeldungen für Screenreader.
- **Design / Größen** — Eine Typo-Skala (11–24 px, keine Schrift unter 11 px), Abstands-, Icon- und z-index-Tokens, einheitliche Inhaltsbreite in allen Schritten, gemeinsame Farbtokens für Fehler/Warnung/Erfolg statt ~40 einzelner Hex-Werte. Alle Text- und Fokusfarben erfüllen WCAG-Kontrast (Akzent `#00737d` statt `#00bcd4` für Text, Fokusring und Primär-Buttons). Schritte 3–5 stapeln sich unter 1024 px, nichts wird bei 200 % Zoom abgeschnitten; `prefers-reduced-motion` wird im ganzen Wizard beachtet.
- **Abhängigkeiten / CI / Bundle** — Angular 21.2.24 (Sicherheits-Patches), three 0.186, zone.js 0.16, Pyodide 0.29.5; `gsap` durch die Web Animations API ersetzt; ungenutzte Pakete entfernt; `npm audit` ohne Befund. Wizard wird per `@defer` nachgeladen (Initial-Bundle 1,47 → 1,12 MB), nur noch die genutzte Icon-Schriftart. druidjs 0.9 (Projektionen insgesamt ~2,7× schneller, korrigiertes UMAP/SAMMON; Lizenz jetzt LGPL-3.0-or-later — Lizenztexte werden unter `licenses/` mit ausgeliefert, `3rdpartylicenses.txt` wird veröffentlicht). Tests: Karma + Jasmine durch Vitest ersetzt (Browser-Modus, headless Chromium über Playwright). CI: Node 24 (`.nvmrc`), `npm ci`, Lint + Tests + Build vor dem Deploy.
- **Aufräumen** — `projection-preview` von 1.397 auf 186 Zeilen (Engine ausgelagert), Projektions-Worker von ~420 auf 62 Zeilen, toter In-Browser-CSV-Pfad (`processor.py`, Worker-Nachricht `process`) entfernt, `tools/dataset_common.py` nur noch für die lokale CLI, toter Code entfernt, Texte durchgehend Englisch.

---

## Ausblick — offen, aber nicht im Umsetzungsrahmen

Identifizierte, aber bewusst nicht umgesetzte Punkte:

- **Methodenspezifische Standardwerte je Projektionsmethode** — laut Anforderungskatalog bewusst außerhalb des Rahmens.
- **Manuelles Beenden laufender Reduktionsmethoden** — aktuell lässt sich eine hängende/langlaufende Dimensionsreduktion nur über „Start over" stoppen, was den gesamten Wizard zurücksetzt (Konfiguration geht verloren). Ein gezielter Abbruch je Methode (bzw. globales „Verarbeitung abbrechen" ohne Reset) sowie ein Timeout pro Methode fehlen. Technische Grundlage vorhanden (isolierte Web-Worker, `ProjectionService.terminateAllWorkers()`), aber ohne nutzerseitige Steuerung. Offen für eine spätere Erweiterung.
- **Manuelles Beenden laufender Reduktionsmethoden** — umgesetzt für Hintergrund-Projektionen (Abbrechen/Neu starten je Methode, „Stop all“; `ProjectionService.cancel/retry/terminateAllWorkers`). Der primäre Pyodide-Lauf bleibt nicht abbrechbar: den Worker zu beenden würde die geladene Python-Laufzeit und die hochgeladene Datei verwerfen. Ein Timeout je Methode fehlt weiterhin.
- **Fehlende Werte bei Skalierung „None“** — landen bei 0 in Originaleinheiten (z. B. Breitengrad 0°, Datum 1970); betrifft nur Spalten ohne Skalierung (Koordinaten standardmäßig).
- **Upgrades** — Angular 22 (Komponenten standardmäßig OnPush; eigener PR, Angular 21 erhält Sicherheitsupdates bis ca. Mai 2027), Pyodide 314 (pandas 3; kein sichtbarer Nutzen, größerer Download; die Pipeline ist dafür vorbereitet und getestet). Erledigt: druidjs 0.9 (ändert die Einbettungen; TriMaps Parameter `weight_adj` gibt es dort nicht mehr, der Regler „Weight Adjustment“ wurde daher entfernt — TriMap läuft mit den Standardwerten der Bibliothek).
- **LLE vorübergehend ausgeblendet** — druidjs 0.9 liefert falsche LLE-Einbettungen (Eigenvektor-Shift ≈ 0, Power-Iteration findet die größten statt der kleinsten Eigenvektoren); wieder einblenden, sobald upstream behoben (`enableLLE` aus `HIDDEN_PROJECTION_METHODS` in `projection-methods.ts` entfernen).
35 changes: 16 additions & 19 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
"inlineStyleLanguage": "scss",
"assets": [
"src/assets",
"src/favicon.ico"
"src/favicon.ico",
{ "glob": "*.txt", "input": "src/licenses", "output": "licenses" }
],
"styles": [
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/material-icons/iconfont/filled.css",
"src/styles.scss"
],
"scripts": [],
Expand All @@ -41,8 +42,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "1MB",
"maximumError": "2MB"
"maximumWarning": "1.25MB",
"maximumError": "1.7MB"
},
{
"type": "anyComponentStyle",
Expand Down Expand Up @@ -80,25 +81,21 @@
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"builder": "@angular/build:unit-test",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"runner": "vitest",
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
"browsers": [
"ChromiumHeadless"
],
"styles": [
"src/styles.scss"
"browserViewport": "1280x720",
"setupFiles": [
"src/test-setup.ts"
],
"scripts": [],
"webWorkerTsConfig": "tsconfig.worker.json"
"coverageReporters": [
"html",
"text-summary"
]
}
},
"lint": {
Expand Down
Loading
Loading