Skip to content

D3 Update

ralfz123 edited this page Nov 20, 2020 · 4 revisions

Enter-Update-Exit concept

Als je aan de slag gaat met D3, is het van cruciaal belang om te weten wat de Enter-Update-Exit methode inhoudt. Dat maakt D3 namelijk zo intelligent en krachtig. We beginnen natuurlijk met een <div> die daarbinnen ook een aantal <div>'s heeft. Dat ziet er dan zo uit:

Voorbeeld code
<!-- Content -->
<div class="wrapper">
  <div></div>
  <div></div>
</div>

Dan hebben we ook nog een stuk data, die je wil visualiseren door middel van D3. De dataset is dan:

const data = ['Sjaak', 'Klaas', 'Pieter', 'Harm']

Allright, dat was de HTML en de data. Nu willen we D3 erbij betrekken, om deze <div>'s te laten vullen met de data die we hebben. We selecteren (.select()) dus eerst de container die om de <div>'s heen zit. In die container zitten 2 <div>'s, die willen we juist ook selecteren. Maar dit doen we in 1 keer, want ja , Frontend Developers zijn lui en doen graag zo min mogelijk. Daarom gebruiken we hier .selectAll('div'), oftewel: je selecteert alle <div>-elementen. Daarna wil je deze elementen vullen met de data. Dat doen we door .data(data) te gebruiken. De code ziet er als volgt uit:

d3.select('.wrapper')
  .selectAll('div')
  .data(data)

Nou oke, dat ging goed. Maar als we nou even goed kijken naar hoeveel <div>-elementen we hebben en hoeveel data-elementen, zien we dat er een verschil in aantal is. Er is meer data (4 waardes totaal) aanwezig dan dat er <div>'s zijn (2 totaal). We komen nu op een belangrijk aspect aan, die in 2 zinnen te vertellen is:

  • Als het aantal data-elementen in het array hoger is dan het aantal aanwezige DOM-elementen, dan moeten we meer DOM-elementen krijgen.
  • Als het aantal aanwezige DOM-elementen hoger is dan het aantal data-elementen in het array, dan moeten we de overige DOM-elementen verwijderen.

D3 heeft daar wat op bedacht, zodat dat voor jou gedaan wordt; .enter() en .exit()

.Enter()

Enter() analyseert welke DOM-elementen er allemaal aanwezig zijn en gaat na hoeveel er nog missen, gebaseerd op de data die is ingeladen. De enter() komt terug met een selectie die aangeeft welke elementen nog missen. Door .append() erachter te zetten, voegt dat de nodige DOM-elementen toe.

d3.select('.wrapper')
  .selectAll('div')
  .data(data)
  .enter()
  .append('div')

De nodige <div>'s worden aangevuld, de HTML code is nu:

<!-- Toegevoegde <div>'s (totaal: 2) -->
<div class="wrapper">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

.Exit()

Exit komt terug met een exit selectie die aangeeft dat er DOM-elementen verwijderd moeten worden. Hierachter volgt dan .remove().

d3.select('.wrapper')
  .selectAll('div')
  .data(data)
  .exit()
  .remove('div')

Update pattern in mijn dataviz

Het updaten van de dataviz gebeurt op het moment dat je bijvoorbeeld een filteroptie aanklikt. Ik heb dat in mijn eigen project toegepast op het moment dat ik op de filterknop "Bezet" druk, dat hij dan alle bezette laadpalen laat zien. Hij moet er dan ook voor zorgen dat hij dus alleen deze data laat zien en niet de andere data, want die moet verwijderd worden. Hij laadt dus de nieuwe data in en plot deze data opnieuw op de kaart. De overige data wordt verwijderd. Dit proces gaat vervolgens zo in zijn werk:

  1. Eerst wil ik de data plots plotten op de map. Dat doe ik door eerst de data in te laten, dan .enter() en daarna circles aan te maken ( .append(circle) ). Vervolgens door de x- en y-coΓΆrdinaten aan te geven tekent hij de dataplots.
function dots (dotData) {
		const g = d3.select('g')
		const projection = d3.geoMercator().scale(6000).center([5.116667, 52.17]);
	
		g.selectAll('circle')
		.data(dotData)
		.enter()
		.append('circle')
		.attr('cx', function (d) {return projection([d.point.lng, d.point.lat])[0];})
		.attr('cy', function (d) {return projection([d.point.lng, d.point.lat])[1];})
		.attr('r', '.2px')
		.attr('class', 'circle')
		.attr('fill', 'rgba(10,10,230,0.623)')
		.attr('stroke', 'rgb(10,10,235)')
  }
  1. Op het moment dat ik op een filteroptie klik (in dit geval "Bezet"), dan wordt er de functie updatingMapBusy aangeroepen die de data filtert en die data stuurt hij vervolgens naar de functie die het update pattern is, namelijk function reassignDots(data, color, strokeColor).
	d3.select('input#busy') // Filterbutton "Bezet" via DOM
	.on("click", function clicking() {
		updatingMapBusy(dotData)
	});

  function updatingMapBusy(data) {
	  const chargingValues = data.filter(function(d){ return Number(d.status.charging) > 0 && Number(d.status.available >= 0)})
	  reassignDots(chargingValues, "rgba(201, 14, 14, 0.541)", "rgb(179, 0, 0)")
	}
  1. In deze functie laadt hij eerst de nieuwe data in en daarna tekent hij de dataplots op de map doordat je na .enter() nieuwe cirkels 'append'. De overige cirkels die ik niet nodig heb laat ik verwijderen door .exit().remove(). Nu is de dataviz geΓΌpdatet βœ…
Update pattern code
function reassignDots(data, color, strokeColor) {
		const dots = g.selectAll('circle') 
		              .data(data) // Assign new data to the dots

		dots
		  .attr('cx', function (d) {return projection([d.point.lng, d.point.lat])[0];})
		  .attr('cy', function (d) {return projection([d.point.lng, d.point.lat])[1];})
		  .attr('fill', color)
		  .attr('stroke', strokeColor)

		dots.enter()
		  .append('circle')
		  .attr('r', '.2px')
		  .attr('fill', color)
		  .attr('stroke', strokeColor)
		  .attr('cx', function (d) {return projection([d.point.lng, d.point.lat])[0];})
		  .attr('cy', function (d) {return projection([d.point.lng, d.point.lat])[1];})
					
		dots.exit()
		  .remove() // Removes not-needed dataplots
}