3 Enter update exit explained - GiovanniKaaijk/frontend-data GitHub Wiki

In D3 you are using enter, update and exit to create elements, update elements or remove elements from your visualization.

  • The enter selection contains placeholder elements for any new elements.
  • The update selection contains existing elements that have had their data attributes updated.
  • The exit selection which are the remaining elements that have not had their data attributes updated.

The enter selection

For the following example, I will use the following data:

myData = [{
  firstName: Giovanni,
  surName: Kaaijk
}, {
  firstName: John,
  surName: Doe
}]

The following pattern is often used:

svg.selectAll('Element')
  .data(myData)
  .enter()
  .append('text')
  .text((d) => { return 'Hello ' + d.firstName });

The element usually is a rect, circle or text element, but this can also be a class or id. MyData is often an array of objects, containing data that belongs to a single element. D3 checks how many items the MyData array contains, creating an element for each item, in this case a text element containing Hello + firstname. Since the selectAll(Element) selection created an empty selection, the update and exit selections will be empty if used.

Update

After this function has been run, elements will have been created in the svg. If we want to change these elements when their data changes, we use the following pattern:

// First we create the elements (shown above)
svg.selectAll('Element')
  .data(myData)
  .enter()
  .append('text')
  .text((d) => { return 'Hello ' + d.firstName })

// Then we select these elements, bind new data and change the text value of these elements
svg.selectAll('Element')
  .data(myData)
  .enter()
  .selectAll('text')
  .text((d) => { return 'hello ' + d.surName })

First we select the same elements that we created above, then we rebind the data and change the first name for each element to the surname.

Exit

For this example, we change the value of our first person in the data:

myData[0].firstName = Foo

Now the first element of our array has changed, the second element has not. If we rerun the code shown above, we will enter the exit selection. The exit selection selects every element that has not been changed.

// We run the same code as before, since we want to change the first name that we just changed
svg.selectAll('Element')
  .data(myData)
  .enter()
  .selectAll('text')
  .text((d) => { return 'hello ' + d.firstName })
// We now enter the exit selection, here 'John Doe' is selected because his data has not been changed. Then we remove the element from the visualization. remove() removes every element that has been selected.
  .exit()
  .remove()

Usually when you are using exit(), you also use remove() to remove these objects.

Usage in my own project

My data:

{
  geometry: {type: "Polygon", coordinates: Array(1)}
  id: "AFG"
  properties: {name: "Afghanistan", count: 75}
}
Here geometry contains all the data for geoJSON to render the map. Properties contains the data for the tooltip and the heatmap.

Enter

let scaleColor = d3.scaleSqrt()
        .domain([0, (state.highestCount)])
        .range(['#ffffff', 'red']);
g.selectAll('path')
    .data(state.dataCount) 
    .enter()
    .append('path')
        .attr('d', pathCreator)
        .attr('class', 'country')
        .style('stroke', 'black')
        .style('stroke-opacity', 0.2)
        .style('fill', "white")
        .on('mouseover', function() {
            d3.select(this)
                .style('stroke-opacity', 1)
        })
        .on('mouseout', function() {
            d3.select(this)
                .style('stroke-opacity', 0.2)
            tooltip.style("visibility", "hidden")
        })
        .on("click", (d) => { historicMoments(d, state.timeFilter.firstValue, state.timeFilter.secondValue);
            tooltip
            .style("visibility", "visible")
            .text(d.properties.name + ': ' + d.properties.count + ' objecten')
        })
        .on("mousemove", (d) => {
            tooltip
            .style("top", (event.pageY-40)+"px")
            .style("left",(event.pageX-35)+"px")})
        .transition()
        .duration(300)
        .style('fill', (d) => scaleColor(d.properties.count))

First I'm creating all country paths, then I am binding functions to each path to be run on click, mousemove, mouseover and mouseout. At the very end I'm binding the colors to the paths, based on their count. For the scaleSqrt function, the growth is sub-linear, which means that more of the input values fall in the last part of the output range, this prevents one country from becoming too dominant.

Update

svg.selectAll('g')
        .data(state.dataCount)
        .enter()
        .selectAll('.country')
        .transition()
        .duration(300)
        .style('fill', (d) => scaleColor(d.properties.count))

For the update selection, I select all countries again, together with the new data. I then style them again with a new fill. I'm not using .exit() .remove() in this case because I want to unchanged countries to be displayed.

Exit

let text = g.selectAll('text').data(eventArray)
    text
      .enter()
      .append('text')
      .text( (d) => { return d.Colony + ' ' + d.PeriodOfTime })
      .attr("font-family", "sans-serif")
      .attr("font-size", "16px")
      .attr("x", () => { return 20 })
      .attr("y", (d) => { return d.y })
      .exit()
      .remove()

I use the exit() selection in the colonial history section. When a user clicks on a new country, the colony history should change to the other country his history. The previous country its history should be deleted, which is what I am doing with .remove ().