D3 interactie - RoyCsuka/frontend-data GitHub Wiki

Zonder enige oefening of tutorial te volgen ben ik gewoon meteen in D3 gedoken met het voorbeeld van Laurens. In de tweede week ben ik begonnen aan D3 en heb ik er als eerst voor gezorgd dat de map werkte.

Volgorde van mijn code

Hieronder leg ik stap voor stap uit hoe mijn code in elkaar steekt.

Algemene variabele

Maak algemene waardes aan en importeer mijn modules die de data schoonmaken.

import { select , geoNaturalEarth1} from 'd3'
import { feature } from 'topojson'
import { cleanedArr } from './cleanData.js';
import { drawMap } from './drawMap.js';

// Eigen query
const query = `mijn query`

// Mijn end-point
const endpoint = "https://api.data.netwerkdigitaalerfgoed.nl/datasets/ivo/NMVW/services/NMVW-14/sparql"

// Algemene svg variabele om mijn HTML svg element te kunnen selecteren
const svg = select('svg')

Map settings die ik mee geef in een parameter aan het einde van mijn code
const mapSettings = {
    projection: geoNaturalEarth1().rotate([-11,0]),
    circleDelay: 11
}

// Global data variable
let data

// De standaard waarde
let centuryVar = 2000;

Hoofdfunctie

Nadat alle algemene variable aan zijn gemaakt en alle modules + data is ingeladen is het tijd om mijn hoofdfunctie uit te voeren.


// Voer mijn hoofdfunctie uit
makeVisualization()

// Our main function which runs other function to make a visualization
async function makeVisualization(){
    //Draw the map using a module
    drawMap(svg, mapSettings.projection)
    //Use the cleanedArr module to get and process our data
    data = await cleanedArr(endpoint, query)

    setUpCenturys(data)
    
    // klik op het eerste element en zorg ervoor dat de onchange wordt getriggerd
    clickFirstItem()

}

Hierboven worden verschillende functies stap voor stap uitgevoerd.

Stap 1

Teken eerst de map d.m.v. een module die Laurens geschreven heeft. De enige code die ik hieraan veranderd heb is hieronder te vergelijken. Als externe bron heb ik deze code ook gebruikt: http://bl.ocks.org/piwodlaiwo/c0e10c375a7704f18b1bc813bc5eeddb

Wat ik van Laurens zijn code heb gebruikt
function drawCountries(container, pathGenerator) {
  d3.json('DIT HEB IK VERANDER').then(data => {
    const countries = feature(data, data.objects.DIT HEB IK VERANDER);
    console.log(countries.features)
    container
      .selectAll('path')
      .data(countries.features)
      .enter()
      .append('path')
      .attr('class', 'country')
      .attr('d', pathGenerator)
  })
}
Wat ik van de externe code van het internet heb gebruikt Een andere JSON file ingeladen (https://piwodlaiwo.github.io/topojson//world-continents.json) en in Laurens zijn code vervangen.
d3.json('https://piwodlaiwo.github.io/topojson//world-continents.json')
Mijn uiteindelijke code
import { geoPath } from 'd3'
import { feature } from 'topojson'

export function drawMap(container, projection){
	const pathGenerator = geoPath().projection(projection)
  setupMap(container, pathGenerator)
  drawCountries(container, pathGenerator)
}

function setupMap(container, pathGenerator){
  container
    .append('path')
    .attr('class', 'sphere')
    .attr('d', pathGenerator({ type: 'Sphere' }))
}

function drawCountries(container, pathGenerator) {
  d3.json('https://unpkg.com/[email protected]/world/110m.json').then(data => {
    const countries = feature(data, data.objects.countries);
    console.log(countries.features)
    container
      .selectAll('path')
      .data(countries.features)
      .enter()
      .append('path')
      .attr('class', 'country')
      .attr('d', pathGenerator)
  })
}

Stap 2

Clean mijn data in Javascript zie mijn readme

Stap 3

Met de setUpCenturys(data) functie geef ik de data mee die schoongemaakt & getransfomeerd is en maak ik een input veld aan met D3 met daarin radio buttons. Ik heb naar Laurens zijn voorbeeld gekeken en heb er de volgende stukjes uitgehaald:

Laurens zijn code
//This awesome function makes dynamic input options based on our data!
//You can also create the options by hand if you can't follow what happens here
function setupInput(fields){
  const form = d3.select('form')
    .style('left', '16px')
    .style('top', '16px')
    .append('select')
    .on('change', selectionChanged)
    .selectAll('option')
    .data(fields)
    .enter()
    .append('option')
    .attr('value', d => d)
    .text(d => d) 
  console.log("form",form)
}

Van Laurens zijn code heb ik de .on('change' selectionChanged) gebruikt die een functie triggerd als er iets anders wordt geselecteerd van de input fields.

function setUpCenturys(data) {

    const form = d3.select('form')
        .selectAll('input')
        .data(data)
        .enter()
            .append('label')
                .append('span')
                    .text(d => d.key)
                .append('input')
                    .attr('type', 'radio')
                    .attr('name', 'century')
                    .attr('value', d => d.key)
                    .on('change', selectionChanged)

}

Wat is er veranderd?

  1. De styling is weg (dit doe ik met css)
  2. De .append('select') want ik gebruikt radio buttons dus dat ziet er anders uit
  3. De selectAll('option') heb ik aangepast naar: selectAll('input')
  4. Ik geef attribute waardes mee aan mijn input fields en ik heb deze genest in een span
  5. Er wordt een span element aangemaakt die de eeuw waarde toont.
  6. on change wordt als laatst aangeroepen

Stap 4

clickFirstItem() is een functie die ervoor zorgt dat het eerste element van de radion button wordt aangeklikt zodra die is aangemaakt met de code hieronder die ik zelf geschreven heb:

function clickFirstItem(){
    document.querySelector("#form label:first-of-type span input").click();
}

Stap 5

Nu .on('change', selectionChanged) is getriggerd als het element is aangemaakt wordt de selectionChanged() functie uitgevoerd die de volgende dingen doet:

Checkt wat de waarde is

"this" verwijst naar het geklikte input field (wat in dit geval 2000 is).

// Laurens zijn code
centuryVar = this ? parseInt(this.value) : centuryVar

Zet een class op het geklikte element

Als de class .active bestaat remove .active en voeg .active weer toe aan het huidige geklikte element.

    if (document.querySelector('.active')) {
        document.querySelector("form .active").classList.remove('active')
        this.closest('span').closest('label').classList.add('active')
    } else {
        this.closest('span').closest('label').classList.add('active')
    }

Krijg data van de geklikte waarde

De code hieronder gaat opzoek naar de key die overeen komt met "this.value" in de data.

// Laurens heeft mij hiermee geholpen
let arrOfSelectedData = data.find(element => element.key == this.value);

Toon geklikte jaartal + het totaal aantal objecten

In mijn titel staat namelijk het volgende: Titel van de waards

Mijn HTML is hieronder te zien. In de titel heb ik twee elementen aangemaakt die ik kan selecteren met JavaScript.

<h2>Totaal <b></b> objecten tussen het jaar <b></b></h2>

De code hieronder zorgt ervoor dat het laatste "b" element in de html wordt vervangen door de geselecteerde eeuw en de geselecteerde eeuw + 100 (wat dus als resultaat in deze situatie 2000 & 2100 geeft)

document.querySelector("p b:last-of-type").innerHTML =  centuryVar + " & " + (centuryVar + 100);

Bij de eerste regel zorg ik ervoor dat er een array terug komt van alle items en in een variabele gestopt wordt. Bij de tweede regel zorg ik ervoor dat alle waardes uit de eerste regel bij elkaar opgeteld worden en in een variabele gestopt wordt. Bij de derde regel wordt de eerste "b" geselecteerd in de HTML en vervangen door het resultaat uit de tweede regel

let amountOfCountryValues = arrOfSelectedData.values.map(e => e.value).map(v => v.amountOfCountryItems);
let amountOfAllItems = d3.sum(amountOfCountryValues)
document.querySelector('p b:first-of-type').innerHTML =  amountOfAllItems;

Minimale waarde en maximale waarde om de scale te bepalen van de cirkels

Hieronder bereken ik de minimale en maximale waarde door de waardes op te roepen die ik in een variabele heb gezet (let arrOfSelectedData = data.find(element => element.key == this.value);.

    // Credits to: https://stackoverflow.com/questions/11488194/how-to-use-d3-min-and-d3-max-within-a-d3-json-command/24744689
    let max = d3.entries(amountOfCountryValues)
        .sort(function(a, b) {
            return d3.descending(a.value, b.value);
        })[0].value;
    let min = d3.entries(amountOfCountryValues)
        .sort(function(a, b) {
            return d3.ascending(a.value, b.value);
        })[0].value;

Wat de D3 functie hierboven doet is het volgende:

  1. Geef aan wat de enteries zijn en zet de functie in een variabele let max = d3.entries(amountOfCountryValues)
  2. Sorteer de waardes met: .sort(function(a, b) {
  3. Geef de gesorteerde waarde terug in een bepaalde volgorde return d3.descending(a.value, b.value);
  4. Sluit functie en selecteer de eerste waarde uit deze nieuwe volgorde })[0].value;

Het zelfde doe ik voor de minimale waarde maar dan i.p.v. "d3.descending" uit stap 3 gebruik ik "d3.ascending"

De nieuwe waarde niet meer nesten

Dit stukje hieronder heeft kris geschreven ik snap niet helemaal wat het doet maar ik begrijp het resultaat.

// props van Kris
    const flattened = arrOfSelectedData.values.reduce((newArray, countries) => {
        newArray.push(countries.value)
        return newArray.flat()
    }, [])

Het witte blok achter de selectie verplaatsen naar de juiste positie

Gif van witte blok

Dit witte blok verplaats ik door een functie aan te roepen die "moveWhiteBlok()" heet

Hoe deze functie eruit ziet:


// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
function moveWhiteBlok(){
    let getTop = document.querySelector('.active').getBoundingClientRect().top;
    document.querySelector('.active-bk').style.top = getTop - 63.40625;
}

Deze functie krijgt het aantal pixels van het label terug die .active heeft. De .active is een class die zich steeds verplaatst (zoals in het gifje te zien is .active de dik gedrukte tekst). En deze pixels worden als inline styling gezet op het witte blok als top: (pixel waarde komt hier);. Van de pixel waarde wordt de hoogte van het witte blok er nog afgetrokken zodat het blok op de juiste positie komt.

Het plaatsen van de cirkels en teksten met D3

Aan het einde van de functie setUpCenturys(data) run ik de functie plotLocations(svg, flattened, mapSettings.projection, min, max) met een aantal parameters die terug komen in mijn D3 plotLocations functie.

De parameters van mijn functie:

  1. min, max Deze twee parameters geven de minimale en maximale waardes mee van hoe groot de cirkels mag zijn op basis van een scale die ik aanmaak binnen de functie.
  2. svg, mapSettings.projection Zijn algemene variabele die ik mee geef
  3. flattend Is de array van de geselecteerde data die geen geneste array meer is.

Hoe mijn D3 plotLocations functie werkt kun je kijken op mijn andere pagina van mijn wiki waar ik uitleg wat enter update en exit inhoud.

⚠️ **GitHub.com Fallback** ⚠️