Did you know that you can navigate the posts by swiping left and right?

D3 Selections: Enter, Update and Exit

December 01, 2017, December 01, 2017 | Comments

category: VISUALIZATION
d3 javascript

D3’s selection plays a core function of its full suite. We all know D3 stands for data-driven documents which means the visualization is based on data to manipulate DOM. d3-selection module would manipulate DOM to bind data with it for changing style, modifying attributes or updating/inserting/removing elements. Let’s first generate a series of shapes to start with. For simplicity, I use Symbols under d3-shape module to generate the following seven symbols.

If you open the developer console of browser, you will find the DOM structure as follows:

alt text

1. Selection

Selections allow us to select DOM elements with compliance to CSS selectors. For example, d3.select() would select the first matching element in the specified string.

selectionDiv = d3.select("#example"); // select the first DOM element with #example
selectionG = d3.select("#example).select("g"); // select the fisrt g child from #example
selectionAllG = d3.select("#example).selectAll("g"); // select all g children from #example

alt text

Once we have a selection of elements, we can apply operators to it in order to manipulate things such as styles and attributes.

d3.select("#example").select("g").select("path").attr("fill","yellow"); // change the first g element into yellow

Selections would also allow us to create new DOM elements. For example, imagine we have a series of data, [1,2,3,4,5,6,7] that we want to assign to each symbol as labels. We can rely on the selection introduced above to first select the DOM element from which we want to add the texts.

var label = [1,2,3,4,5,6,7];
d3.select("#example")
  .selectAll("text")
  .data(label)
  .enter()
  .append("text")
  .attr("fill","red")
  .attr("font-size","30")
  .attr("x",function(d,i){
                          if(i==0) {return margin.left-10;}
                          else {return margin.left*3*(d-1)-10;}
                          })
  .attr("y",140)
  .text(function(d){return d;});

When checking the browser console, we would find the first g element with modified color “yellow” and there are seven text elements created within svg element #example.

alt text

2. Data Join

If we re-visit the above example, you will find out the way how I made the seven symbols. Here is a snippet of my codes:

var g1 = d3.select("#example2")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");    
g1.append('path')
  .attr('d', d3.symbol().type(d3.symbolCircle).size(2000))
  .attr("fill", "black");
.
.
.
var g7 = d3.select("#example2")
        .append("g")
        .attr("transform", "translate(" + margin.left*18 +"," + margin.top + ")"); 
g7.append('path')
  .attr('d', d3.symbol().type(d3.symbolWye).size(2000))
  .attr("fill", "black");

Basically, I use d3.select() to get DOM element where we want to draw symbols and then append each symbol to the svg element by setting its position and specific type with seven times manually.

Obviously, this is quite manual and inefficient way to visualize many graphs. And it didn’t really need D3 to do it. Now, we want to make use of selection introduced above the achieve the same goal. In order to be data-driven DOM, we also want to bind the data with it for visualization.

var label = [4/3,2,3,4,5,6,7];
var color = d3.scaleOrdinal(d3.schemeCategory10);
    
var svg = d3.select("#example")
            .append("g")
            .attr("width",width)
            .attr("height",height);

svg.selectAll("path")
   .data(label)
   .enter()
   .append("path")
   .attr("transform", function(d,i) { return "translate("+margin.left*(d-1)*3+","+margin.top+")";})
   .attr('d', d3.symbol().type(function(d,i) { return d3.symbols[i];}).size(function(d,i) {return 500*d}))
   .attr("fill", d=>color(d));

var text = [1,2,3,4,5,6,7];
d3.select("#example")
  .selectAll("text")
  .data(text)
  .enter()
  .append("text")
  .attr("fill","red")
  .attr("font-size","30")
  .attr("x",function(d,i){
                          if(i==0) {return margin.left-10;}
                          else {return margin.left*3*(d-1)-10;}
                          })
  .attr("y",140)
  .text(function(d){return d;});

First of all, we create two arrays: label for creating number of symbols and color for different colors using the data in label array. selectAll(“path”) would match existing paths or create new paths if not existing. data(label) would join those paths with label. In other words, we bind each element from label array to each path element and by opening browser console, we get the following information for confirmation.

alt text

It is noticed that from console, there are two attributes: enter and exit. Here, we use enter() and append(path) to create the seven path elements by appending them for the new data. And the following three attr() statements are just use data and index from the data(label) to determine the position of each symbol, the size and color of it. Here is the final visualization:

3. Enter, Update and Exit

The example shown above is a perfect one that the data would bind with DOM elements one by one exactly. In reality, however, the data would be more than the DOM elements which it is going to bind, or less. This is where the Enter, Update and Exit come to play.

Let’s start with the case which the number of DOM elements is fewer than the data. We need Enter to create more DOM to fit the data. For example, if we first have only six symbols, and the label has seven elements. We use the following code to fullfil the task of Enter.

var label1 = [4/3,2,3,4,5,6,7];
svg.selectAll("path")
   .data(label1)
   .enter()
   .append("path")
   .attr("transform", function(d,i) { return "translate("+margin.left*(d-1)*3+","+margin.top+")";})
   .attr('d', d3.symbol().type(function(d,i) { return d3.symbols[i];}).size(function(d,i) {return 500*d}))
   .attr("fill", d=>color(d));

By opening browser console, we will find a different piece of information compared with above:

alt text

And when we have more data than DOM elements, we need Exit to remove redundant DOM to match with the data. Here is the code to use:

var label1 = [4/3,2,3,4,5,6];
svg.selectAll("path")
   .data(label1)
   .exit()
   .remove()
   .append("path")
   .attr("transform", function(d,i) { return "translate("+margin.left*(d-1)*3+","+margin.top+")";})
   .attr('d', d3.symbol().type(function(d,i) { return d3.symbols[i];}).size(function(d,i) {return 500*d}))
   .attr("fill", d=>color(d));

After introducing Enter and Exit, it would be quite easy to understand Update. It is basically using the data() to update DOM elements if the data is updated.


Reference:

(1). D3 API Reference, https://github.com/d3/d3/blob/master/API.md.
(2). d3-selection API, https://github.com/d3/d3-selection.
(3). D3’s enter() and exit(): Under the Hood, http://animateddata.co.uk/lab/d3enterexit/.
(4). How Selections Work, https://bost.ocks.org/mike/selection/.
(5). Thinking with Joins, https://bost.ocks.org/mike/join/.