-
Notifications
You must be signed in to change notification settings - Fork 0
d3 visualisation
Check hier de omschrijving van mijn concept zodat je de visualisatie goed kan volgen en snapt welke keuzes ik maak in mijn proces.
(Ik heb mijn concept voor de eerste FD oplevering beperkt tot het loggen van van parkeerplaatsen met en zonder invaliden plekken en oplaadpunten. Dit zou normaal gesproken stap 2 zijn voor de visualisatie maar dit draai ik dus om. Dit heeft te maken met dat ik nog geen reactie had van de Routedatabank of ik hun data online mag gebruiken.)
Ik ben begonnen met het opschonen en formateren van de RDW data zodat ik deze kan gebruiken binnen mijn visualisatie. De code hiervan is hier te vinden, en mijn documentatie en leerdoelen hier.
Ik wil een map gaan maken, mijn eerste stap is het weergeven van Nederland als geoKaart, zodat ik hierop mijn datapunten kan gaan plotten. Voor deze stappen heb ik deze Curran Course gevolgd en de nuttige stappen betrokken op mijn eigen project.
Als eerste stap worden alle svg items geselecteerd om hier later de data aan te kunnen koppelen. Ook wordt hier bepaald hoe de kaart er uit komt te zien 'projectiontype' en wordt de kaart geschaald naar een zichtbare grootte.
//Hier worden alle svg geselecteerd zodat je daar later de data aan kan koppelen
const svg = d3.select("svg");
//hier wordt de variable van de projection aan gemaakt om aan te geen hoe je de map wil weergeven ('projectiontype'). geoMercator is een d3 functie die een platte kaart terug geeft.
const projection = d3
.geoMercator()
.center([6, 52]) // en met center kan je de volledige kaart verplaatsen naar waar nodig
.scale(5000); // Scale wordt gebruikt om wat in te zoomen
const pathGenerator = d3.geoPath().projection(projection);
// geoPath will convert the data path into an svg path string that we can use on svg pathsIk heb in plaats van de wereldkaart een API gevonden die de Nederlandse kaart als svg weergeeft.
source: https://cartomap.github.io/nl/wgs84/gemeente_2020.topojson
In het volgnde stuk code wordt de dataset gecombineerd met de svg selectie. Er wordt een path toegevoegd met als waarde de data uit de dataset. Ook wordt een class toegevoegd zodat de svg kan worden gestyled in de css, en een title (naam van de gemeente) zodat je in de visualisatie iets per gemeente zou kunnen doen.
d3.json("https://cartomap.github.io/nl/wgs84/gemeente_2020.topojson").then(
(data) => {
const gemeentes = topojson.feature(data, data.objects.gemeente_2020);
g.selectAll("path")
.data(gemeentes.features)
.enter()
.append("path")
.attr("class", "gemeente")
.attr("d", pathGenerator)
.append("title")
.text((d) => d.properties.statnaam);
}
);Zo ziet het object er uit van 1 gemeente in de dataset. De arcs worden gebruikt om de gemeente te tekenen met d3 en de statsnaam wordt als tilte toegevoegd aan dit figuur.
{
"statcode":"GM0184",
"statnaam":"Urk",
"jrstatcode":"2020GM0184",
"rubriek":"gemeente",
"FID":"cbs_gemeente_2020_gegeneraliseerd.43"
},
"id":"GM0184"
},
{
"arcs":[
[
-121,
-176,
184,
185,
186,
-139,
187,
-178
]
]}And this was the endresult of the code above:
Om de data te kunnen plotten heb ik de grote dataset van RDW opgeschoont. Ik heb alle waardes die ik uit deze dataset nodig heb in een nieuw JSON bestand gezet en gebruik deze om de parkeergarages op de kaart te plotten.
De code werkt ongeveer hetzelfde als dat ik de kaart van Nederland heb gemaakt. Ik voeg svg's van cirkels toe aan de punten van de dataset en voeg er een class aan toe om ook deze weer te kunnen stylen met d3. cx en cy geven de longitude en latitude van de cirkels aan den projection zorgt ervoor dat deze coordinaten worden omgezet naar pixel data.
d3.json(
"https://raw.githubusercontent.com/SimonPlanje/frontend-data/main/onlineData/longLatDisabled.json"
).then((data) => {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "parkSpots")
.attr(
"cx",
(d) =>
projection([
d.accessPointLocation[0].longitude,
d.accessPointLocation[0].latitude,
])[0]
)
.attr(
"cy",
(d) =>
projection([
d.accessPointLocation[0].longitude,
d.accessPointLocation[0].latitude,
])[1]
)
.attr("r", "2px");
});Zo ziet de visualisatie er nu uit:
Omdat het voor mij lastig wordt om een dataset die niet online mag komen te gebruiken, begin ik met het stuk wat eerst was bedoeld als uitbreiding van het concept. Dit is het toevoegen van de data: invaliden plekken en oplaadpunten. Ik wil per locatie kunnen filteren of er invaliden plekken zijn en/of oplaadpunten.
Hiervoor heb ik een form geschreven met 4 verschillende checkboxes in de html:
<form>
<label
><input type="checkbox" class="checkbox" value="both" checked />Opladen +
invaliden</label
>
<label
><input type="checkbox" class="checkbox" value="charging" checked />Alleen
opladen</label
>
<label
><input type="checkbox" class="checkbox" value="disabled" checked />Alleen
invaliden</label
>
<label
><input type="checkbox" class="checkbox" value="none" checked />Geen
invaliden en geen oplaadpunt</label
>
</form>Nu kan ik deze checkboxes selecteren op de class met d3:
d3.selectAll('.checkbox').each(function(d){Voor elke checkbox schrijf ik een functie om de value van de checkboxes op te halen, en ik herbruik deze variable straks ook om de id's van de cirkels aan de checkboxes te koppelen.
cb = d3.select(this);
group = cb.property("value");Vervolgens schrijf ik een if statement om te checken welk van de geselecteerde checkboxes is gechecked. cd selecteerd hier de property van de checkboxes. Als deze inderdaad 'checked' is dan worden de cirkels geselecteerd aan de hand van de value van de checkboxes. In de dataset heb ik id's toegevoegd aan de parkeerplaatsen die overeenkomen met de values van de checkboxes. Nu kan ik deze dus aan elkaar koppelen door alles te selecteren met .+deValueVanDeCheckbox. Als de checkbox is gechecked dan moet de cirkel worden weergegeven dus is de opacity 1 en heeft het een radius.
if (cb.property("checked")) {
g.selectAll("." + group)
.transition()
.duration(1000)
.style("opacity", 1)
.attr("r", radius);
}Als de checkbox niet is gechecked gebeurd er het zelfde als hier boven alleen dan wordt er aan de cirkels een opacity 0 en geen radius meegegeven waardoor ze niet meer zichtbaar zijn.
else{
g.selectAll('.'+group)
.transition()
.duration(1000)
.style('opacity', 0)
.attr('r', 0)
}
```Deze functie moet niet altijd blijven lopen daarom staat dit in een functie update(), deze wordt alleen aangeroepen wanneer de checkboxes worden gebruikt, dit kan worden gechecked door de volgende functie buiten deze update() functie te schrijven:
d3.selectAll(".checkbox").on("change", update);Als er inderdaad een change is in de checkboxes dan kan update worden aangeroepen en loopt de functie door de cirkels heen.
update();Check hier de volledige functie
Het volgende doel is om een legenda te maken zodat de gebruiker kan zien wat de waardes van de kleuren zijn die de cirkels hebben. Hiervoor gebruik ik dezelfde kleuren variable als ik heb gebruikt voor het plotten van de cirkels:
var color = d3
.scaleOrdinal()
.domain(["none", "both", "disabled", "charging"])
.range(["var(--pink2)", "var(--yellow)", "var(--orange)", "var(--lime)"]);Deze variable kan ik gebruiken als de data voor de legenda, hier staat namelijk alles in wat een legenda nodig heeft. Ik selecteer de svg zodat de legenda niet in de group komt van de kaart, anders zou de legenda mee in en uit zoomen.
svg.selectAll("svg").data(color.domain());Voor de legenda gebruik ik cirkels om de kleuren weer te geven met text daarachter. Dus voeg ik de cirkels toe aan de svg. De kleur wordt de waarde d dit komt overeen met de kleuren uit de variable color.
.enter().append('circle')
.attr('r', 6)
.attr('fill', d => color(d))
.attr('stroke', d => color(d))
.attr('fill-opacity', .3)Om de cirkels los van elkaar te positioneren gebruik ik de index parameter en transform translate functie. hierbij gaat elke cirkel steeds verder naar onder door elke index weer keer 30 te doen.
.attr('transform', (d, i) =>
`translate(${100},${i * 30 + 20})`)Nu staan er 4 cirkels onder elkaar met de goede kleur. Ik pas precies dit zelfde patroon toe alleen dan voor de text. Deze positioneer ik alleen verder naar links zodat het naast de cirkels komt te staan.
svg
.selectAll("svg")
.data(color.domain())
.enter()
.append("text")
.text((d) => d)
.style("fill", "white")
.attr("alignment-baseline", "middle")
.attr("transform", (d, i) => `translate(${120},${i * 30 + 20})`);Als laatst heb ik ook zoom nog toegevoegd als extra interactie. Het heeft weinig code nodig, want d3 doet alles voor je met de functie zoom().
svg.call(d3.zoom().on("zoom", zoomed));
function zoomed({ transform }) {
g.attr("transform", transform);
}Het is belangrijk dat er een groep wordt aangemaakt, op deze groep wordt dan de zoom functie gezet en dan kan alleen deze groep worden ingezoomd en uitgezoomd.
const g = svg.append("g");Alles wat nu wordt aangeroepen met g.selectAll('g').enter.append('') enz.. kan worden gentransformeerd met de muis en scroll.