Skip to content

Documentatie Joost

Joost Krebbers edited this page May 21, 2026 · 10 revisions

🐟 Visdeurbel Wereldkaart Mei 2026

1. Wat heb ik gedaan?

1.1 HTML-structuur & layout

  • Navbar gebouwd met branding (logo + naam) en navigatielinks
  • Page header met eyebrow-tekst, grote serif-titel en decoratieve emoji's
  • Periodeknoppenbalk (week / maand) met laad-indicator
  • Stats-balk met vier kaartjes: totaal events, landen, vis gespot, gesloten
  • Hoofd-layout in twee kolommen: kaartpaneel (links) en zijbalk (rechts)
  • Zijbalk met per-land lijst, legendablok en live event-feed
  • Footer met branding en contactgegevens

1.2 SVG-wereldbol (D3 + TopoJSON)

  • Orthografische projectie ingesteld via d3.geoOrthographic() — dit geeft het echte boleffect
  • Radiale gradiënten voor oceaan (lichtblauw glanseffect) en een subtiele shine-overlay bovenop de bol
  • Dropshadow-filter op de oceaanbol voor dieptegevoel
  • Graticule (breedte- en lengtegraadlijnen) getekend als dunne achtergrondlijnen
  • TopoJSON-landkaart geladen van CDN en omgezet naar GeoJSON features
  • Landvulling, hover-darkening en outline-stroke geïmplementeerd

2.3 Interactie — slepen, zoomen, aanraking

  • Muisdrag-rotatie: mousedown slaat beginhoek op, mousemove berekent delta × gevoeligheid (0.3), projection.rotate() bijgewerkt
  • Touchscreen-ondersteuning: dezelfde logica voor touchstart / touchmove
  • Scroll-to-zoom: wheel-event past projection.scale() aan tussen min 150 en max 800
  • Auto-rotatie via requestAnimationFrame-lus (0.012 graden per milliseconde)
  • Auto-rotatie pauzeert bij interactie en hervat na 4 seconden inactiviteit
  • Reset-knop herstelt schaal en rotatie naar beginpositie

1.4 Data laden & verwerken

  • loadData() haalt NDJSON op (newline-delimited JSON) en parseert elke regel afzonderlijk
  • aggregate() groepeert events per land op basis van ISO alpha-2 → numerieke TopoJSON-ID mapping (249 landen)
  • Per land worden bijgehouden: events, uploads, dismissals, steden, vissoorten, uren van de dag, apparaattype, OS, browser
  • normalizeOS() en normalizeBrowser() mappen ruwe user-agent strings naar schone categorieën
  • firstKnown() helper slaat "unknown"/"Overig"-waarden over bij het bepalen van topFish, topOS, topBrowser
  • Periodewissel laadt nieuwe data, dimpt de bol tijdens laden, en update alle views atomisch

1.5 Tien kaartmodi

Modus Wat het toont
Choropleth Logaritmische kleurschaal op event-aantallen per land
Bubble Proportionele cirkels (sqrt-schaal) op landcentroïden
Upload % RdYlGn-gradiënt voor verhouding uploadedFish / totaal
Taarten Mini-taartdiagrammen upload vs. dismiss per land
Lijnen Great-circle lijnen van elk land naar Utrecht, dikte ∝ bezoeken
Apparaat Taartjes desktop (amber) vs. mobiel (paars) vs. overig
Vis soort Landkleur op meest geziene vissoort
Tijdstip Gemiddeld actief uur: nacht / ochtend / middag / avond
OS Landkleur op meest gebruikte besturingssysteem
Browser Landkleur op meest gebruikte browser

1.6 Tooltip & zijbalk

  • Tooltip toont contextgevoelige rijen afhankelijk van de actieve modus (aantallen, percentages, tijdslabels, vis breakdown)
  • Country-list in de zijbalk gesorteerd op events, met vlag-emoji, naam en mini progress bar
  • Event-feed toont de laatste 30 events live, elk met vlag, eventnaam en tijdstempel
  • isVisible() berekent via dot-product of een coördinaat aan de voorkant van de bol zit, zodat overlays aan de achterkant worden verborgen

1.7 Visueel ontwerp (CSS)

  • Volledig CSS custom-properties systeem: --green-dark, --coral, --cream, --lavender, etc.
  • Typografie: Fraunces (serif, branding/koppen) + DM Sans (schreefloos, body)
  • Stats-kaartjes, tabs, knoppen en legenda allemaal in huisstijl van Visdeurbel
  • Loading overlay met animerende vis-emoji die vervaagt zodra data geladen is

2. Hoe lang heeft het geduurd?

Onderdeel Geschatte tijd
SVG-bol & D3 setup ±2–3 uur
Drag / zoom / touch interactie ±1–2 uur
Data pipeline & aggregatie ±2–3 uur
Tien kaartmodi ±3–4 uur
Tooltip & zijbalk ±1–2 uur
CSS & visuele afwerking ±2–3 uur
Periodewissel & bugfixes ±1 uur
Totaal geschat ±12–18 uur

De kaartmodi kostten de meeste tijd — elke modus heeft zijn eigen aggregatielogica, kleurschaal, legenda en tooltip-variant. De D3 orthografische bol met correcte overlay-reprojectie bij elke frame was ook flink debuggen.


3. Wat heb ik geleerd?

3.1 D3.js & geografische visualisatie

  • d3.geoOrthographic() vs. andere projecties — clipAngle: 90 snijdt de achterkant van de bol weg
  • projection.rotate([lambda, phi, gamma]) — lambda is oost-west, phi is noord-zuid kantel
  • pathGen({ type: 'Sphere' }) als oceaan-achtergrond; geoGraticule() voor het coördinatenraster
  • SVG <defs> met radialGradient en feDropShadow voor diepte en glans
  • d3.scaleSqrt() voor bubbles (perceptueel eerlijker dan lineair), d3.scaleSequentialLog() voor choropleth
  • d3.arc() voor taartdiagrammen direct in SVG

3.2 Globe-interactie & animatie

  • requestAnimationFrame-loop met delta-time voor vloeiende, frame-rate-onafhankelijke rotatie
  • Dot-product om te bepalen of een punt aan de voorzijde van de bol zit (isVisible)
  • Drag-sensitivity tuning: 0.3 graden per pixel voelt prettig; te hoog voelt chaotisch
  • event.preventDefault() nodig op wheel om pagina-scroll te blokkeren
  • Touch-events met { passive: true } voor betere scroll-performance op mobiel

3.3 Data & JavaScript patronen

  • NDJSON parsen: text.trim().split('\n').map(l => JSON.parse(l))
  • TopoJSON numeric ID als join-sleutel voor D3 landpaden — je kunt niet direct alpha-2 gebruiken
  • Promise.all() voor parallel ophalen van topologie en eventdata
  • d3.color(hex).darker(0.5) om hover-kleur dynamisch te berekenen op basis van de huidige vulkleur
  • firstKnown() helper: meest voorkomende waarde zoeken terwijl je "unknown" overslaat

3.4 CSS & ontwerp

  • CSS custom properties als centraal design-token systeem — één plek wijzigen, overal bijgewerkt
  • Variable fonts: Fraunces heeft een opsz-axis voor vloeiende grootteovergang
  • SVG-stijlen via D3 .attr() zijn inline — ze overschrijven CSS-klassen, dus zorg voor consistentie

3.5 Architecturale inzichten

  • Scheid render-functies per modus — één functie per tab maakt toevoegen en debuggen makkelijk
  • getCountryFill(d) als centrale dispatcher voorkomt code-duplicatie bij hover/restore-logica
  • overlayGroup() als factory-functie zorgt voor consistente data-lon/data-lat attributen die redraw() nodig heeft

4. Mogelijke vervolgstappen

  • Responsiveness verbeteren voor smallere schermen
  • Echte API-endpoint koppelen in plaats van statische JSON-bestanden
  • Animaties toevoegen bij periodewissel (morph van datapunten)
  • Toegankelijkheid (a11y): ARIA-labels op SVG-elementen, toetsenbordnavigatie
  • Meer vissoorten toevoegen aan de FISH_COLORS mapping
  • Unit tests schrijven voor de aggregate() functie

Clone this wiki locally