In onze webapplicatie HortiHeat ontwikkelden we een statistiekenpagina, om de gebruiker meer inzicht te geven in zijn of haar bezoekers. Een belangrijk onderdeel hiervan is de bezoekersdata per land. Dat hadden we kunnen weergeven via een tabel met cijfers en landvlaggen, maar een interactieve wereldkaart is natuurlijk een stuk interessanter. Niet alleen voor onze klant, maar ook voor ons om te ontwikkelen! In dit artikel neem ik je mee in mijn aanpak en vertel ik hoe ik met React, GeoJSON en D3.js aan de slag ben gegaan om deze interactieve kaart te bouwen.
Goed, we gaan dus een interactieve wereldkaart maken. Dan is het eerst belangrijk om helder te krijgen wat de kaart precies moet kunnen of beter gezegd: welke uitdagingen er opgelost moeten worden.
- Landinformatie vanuit de back-end moet gekoppeld kunnen worden aan een interactieve kaart, inclusief de juiste vertalingen.
- De kaart moet ondersteuning bieden voor interactieve zoom- en panfunctionaliteit.
- Landen moeten dynamisch ingekleurd worden op basis van bezoekersaantallen, vergelijkbaar met een heatmap of choropletenkaart.
- De oplossing moet responsive én performant zijn. Je wilt tenslotte niet meerdere megabytes aan kaartdata inladen voor een eenvoudige visualisatie.
Waar te beginnen?
Ik ben begonnen zoals elke front-ender: eerst kijken of er een mooie React-package bestaat die al mijn problemen oplost. Al snel ontdekte ik dat de meeste libraries nét niet deden wat ik nodig had, of simpelweg niet meer actief onderhouden werden. Tijd om een andere weg in te slaan.
Tijdens die zoektocht kwam ik opnieuw GeoJSON tegen. Ik had er al vaker van gehoord, maar er nooit echt mee gewerkt. GeoJSON is in feite een standaardformaat gebaseerd op JSON waarmee je geografische data kunt beschrijven. Denk aan landen, provincies, steden of complete werelddelen, allemaal opgebouwd uit coördinaten.
Omdat ik in het verleden al eens met D3 had gewerkt, besloot ik daar opnieuw naar te kijken. D3 is een JavaScript-library waarmee je data kunt omzetten in interactieve visualisaties binnen de browser. Al snel ontdekte ik dat D3 perfect kan samenwerken met GeoJSON. Op basis van die data kun je namelijk een SVG-projectie genereren en in dit geval, van een wereldkaart.
Mijn vervolgstap was dan ook duidelijk: een klein prototype bouwen waarin ik GeoJSON-data inlaadde en met D3 projecteerde naar SVG-paden. Alsdat werkte, had ik een solide basis om verder op te bouwen. Pas daarna wilde ik de kaart écht interessant maken door bezoekersaantallen per land te verwerken en interactieve elementen toe te voegen.
Tijd voor een proof-of-concept
Als eerste stap moest ik uitzoeken waar ik überhaupt GeoJSON-data vandaan kon halen. Na wat rondzoeken kwam ik terecht bij Natural Earth, een bron voor open source geografische datasets.
Voor deze toepassing had ik geen extreem gedetailleerde wereldkaart nodig, dus koos ik voor de dataset op schaal 1:110m. Die biedt meer dan genoeg detail voor een interactieve wereldkaart, terwijl de bestandsgrootte beperkt blijft. Hoe gedetailleerder de geometrie, hoe groter het bestand en hoe zwaarder de rendering wordt. En zoals eerder genoemd was performance een belangrijke vereiste binnen deze implementatie.
Om niet afhankelijk te zijn van externe bronnen tijdens runtime heb ik de dataset rechtstreeks in het project opgenomen. Daardoor hebben we altijd toegang tot de data en voorkomen we extra afhankelijkheden.
Daarnaast wilde ik voorkomen dat het GeoJSON-bestand telkens opnieuw geladen wordt. Het bestand is immers al snel zo’n 500 KB groot. Daarom heb ik gekozen voor een eenvoudige fetch-implementatie in combinatie met een kleine cache-wrapper. Zodra de data eenmaal geladen is, kan die opnieuw gebruikt worden zonder onnodige requests of extra verwerking.
D3 projectie
Daarna ben ik met D3 aan de slag gegaan om op basis van de GeoJSON-data een kaartprojectie te renderen. Hiervoor heb ik gebruikgemaakt van geoNaturalEarth1. De eerste stap was het definiëren van een zogeheten *projection*. Door de projectie goed af te stemmen, blijft de kaart ook tijdens het inzoomen scherp en gedetailleerd.