Clearing up Some Lingering Confusion

DOM element/node

One thing we haven't made a huge deal about in class is the difference between a DOM element and a d3 selection. Here is the difference. Suppose we have an HTML snippet like this:

<div id="plot">
    <svg>
    <circle></circle>
    </svg>
</div>

Here, <svg>, <div>, <circle> are all DOM elements or, interchangably, DOM nodes. You can access and interact with these DOM elements via JavaScript's built-in functions. For example, to access the <div> node, we can use:

document.getElementById('plot') //returns the DOM node

DOM nodes have built in properties like clientWidth, and these can be accessed directly from the node:

document.getElementById('plot').clientWidth //returns a number

selection

However, by themselves, plain DOM nodes don't come with many built-in functions. We can't, for example, set attributes and styles directly by using .style() or .attr(). The code below doesn't work:

document.getElementById('plot').style('width','400px'); //WRONG!!

BUT, by turning plain DOM nodes into a d3 selection, a wealth of functions become available. To do that, we use d3.select() or d3.selectAll():

var node = document.getElementById('plot');
d3.select(node) //this is a selection!
    .style('width','400px'); 

If the above looked a little weird to you, that's because we are used to creating selection using CSS selectors. Both methods work:

d3.select('#plot').style('width','400px');
d3.select()/d3.selectAll() + DOM node = d3 selection

Good to know...

Full list of functions available to selection are here:

Besides the ones we commonly use, like

  • selection.attr()
  • selection.data()/selection.datum()
  • selection.append()

...we also need to be proficient with these

  • selection.filter()
  • selection.each()
  • selection.call()
  • selection.node()
  • selection.on()

Please take a moment to review, and let me know if anything doesn't make sense.

First of all, review the previous section on what a selection is. Then take a look at the API reference for selection.call()

It should be clear from looking at the API reference: selection.call(function[, arguments…]) that

  1. selection.call() expects one argument, a function (we'll ignore the arguments for now)
  2. That function expects one argument, which is the selection itself

Not as convoluted as it sounds--let's see how it works:

var circle = svg.append('circle')
    .attr('r',3)
    .call(colorMeRed);

function colorMeRed(selection){
    selection
        .style('fill','red')
        .style('stroke','white')
        .style('stroke-width','1px');
}
                        

Here, the circles selection calls the colorMeRed function, passing into the function the circles selection. The end result is the same as:

var circle = svg.append('circle')
    .attr('r',3)
    .style('fill','red')
    .style('stroke','white')
    .style('stroke-width','1px');
                        

Why use selection.call()?

While the above example is trivial, selection.call() is useful when you want to apply the same operations repeatedly to different selections. Suppose we want to change the fill, stroke and stroke-width of several different selections; using selection.call(), we can put all the tedious code in a function, and call it repeatedly.

svg.append('rect').call(colorMeRed);
svg.append('path').call(colorMeRed);
svg.selectAll('.green-circles').call(colorMeRed);
...
//no need to use .style(...).style(...).style(...) in a row
                        

We know that a selection can refer/contain multiple DOM elements:

var circles = d3.selectAll('circle')
    .data([43,22,56,39,100]) //array of 5 elements
    .enter()
    .append('circle');

In the above example, the circles variable is a selection that refers to 5 DOM elements.

selection.each() lets you access individual elements in a selection

selection.each(function) lets you access each element individually. This function allows two arguments:

  1. First argument d is the datum bound to the current element
  2. Second argument i is the index/order of the current element
  3. Not an argument, but this refers to the current DOM element itself
Continuing the example above:

circles.each(function(d,i){
    //This function will run 5 times

    console.log(d); //in succession, this will log 43, 22, 56, 39, 100
    console.log(i); //in turn, this will log 0, 1, 2, 3, 4

    d3.select(this).attr('r',d/2); //this will set the 'r' attr of each circle to be 1/2 of its datum
});

Tricky meaning of this

Within the callback function for selection.each(), this refers to the individual DOM element. Remember, DOM element is NOT the same as selection. To turn it into a selection, we still had to use d3.select() to unlock d3's full capabilities.