Selecting Null: What Is the Reason Behind Selectall(Null) in D3

Selecting null: what is the reason behind selectAll(null) in D3?

tl;dr

The objective of using selectAll(null) is to guarantee that the "enter" selection always corresponds to the elements in the data array, containing one element for every element in the data.


The "enter" selection

To answer your question, we have to briefly explain what is an "enter" selection in D3.js. As you probably know, one of the main features of D3 is the ability of binding data to DOM elements.

In D3.js, when one binds data to DOM elements, three situations are possible:

  1. The number of elements and the number of data points are the same;
  2. There are more elements than data points;
  3. There are more data points than elements;

In the situation #3, all the data points without a corresponding DOM element belong to the "enter" selection.

Thus, In D3.js, "enter" selections are selections that, after joining elements to the data, contains all the data that don't match any DOM element. If we use an append function in an "enter" selection, D3 will create new elements, binding that data for us.

This is a Venn diagram explaining the possible situations regarding number of data points/number of DOM elements:

Sample Image

Binding data to already existing DOM elements

Let's break your proposed snippet for appending circles.

This...

var circles = svg.selectAll("circle")
.data(data)

... binds the data to a selection containing all circles. In D3 lingo, that's the "update" selection.

Then, this...

.enter()
.append("circle");

... represents the "enter" selection, creating a circle for each data point that doesn't match a selected element.

Sure, when there is no element (or a given class) in the selection, using that element (or that class) in the selectAll method will work as intended. So, in your snippet, if there is no <circle> element in the svg selection, selectAll("circle") can be used to append a circle for each data point in the data array.

Here is a simple example. There is no <p> in the <body>, and our "enter" selection will contain all the elements in the data array:

var body = d3.select("body");var data = ["red", "blue", "green"];var p = body.selectAll("p")  .data(data)  .enter()  .append("p")  .text(d=> "I am a " + d + " paragraph!")  .style("color", String)
<script src="https://d3js.org/d3.v4.min.js"></script>

What is Null doing when it is inserted into an attribute function in d3?

Before anything else, please keep just one issue per question here at Stack Overflow.

Regarding the null, the API explains it:

A null value will remove the specified attribute.

That applies to other methods, such as style().

In this example, the circle has a CSS color ("red"). The D3 code sets another color ("green"), and the null reverts to the original CSS color.

const circle = d3.select("circle");let i = 0;setInterval(function() {  circle.style("fill", (++i) % 2 ? "green" : null);}, 1000)
circle {  fill: red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><svg>  <circle r="50" cx="100" cy="75"></circle></svg>

In d3.js, skip append() for null data

One option is to represent your data differently, so that you aren't dependent on the index to compute the x-coordinate. For example, if you represented each datum as an object (e.g., {x: 0, y: 0.2840042}) then you could compute the x-coordinate as x(d.x) rather than x(i).

Another option would be to set the radius to zero when the value is null, so the circles are hidden: circle.attr("r", function(d) { return d == null ? 0 : 3; }). Or, you could hide the circles: circle.style("display", function(d) { return d == null ? "none" : null; }).

You could also remove the null elements after appending them: circle.filter(function(d) { return d == null; }).remove(). That would work for the initial creation, but I wouldn't recommend it because the index would change if you reselected the elements later.

Typescript: Can't call selectAll of Transition and Selection union: This expression is not callable.

For your function's parameter maybeTransition you are specifying a union type expecting it to be exactly that, a union of both types:

Selection<BaseType, unknown, BaseType, unknown> | Transition<BaseType, unknown, BaseType, unknown>

Unfortunately and totally counter-intuitively, a union type behaves quite differently from what you expect! The documentation tells us that

A union type is a type formed from two or more other types, representing values that may be any one of those types.

So far, so good. However, when it comes to the properties of the united types

TypeScript will only allow an operation if it is valid for every member of the union.

If you think of your interfaces / types as sets of properties this would, mathematically speaking, be an intersection rather than a union. I have always considered this a misnomer. A fact that is also addressed by the documentation in small print:

It might be confusing that a union of types appears to have the intersection of those types’ properties. This is not an accident - the name union comes from type theory. The union number | string is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

Looking at the type definitions for Selection and Transition you will notice that there are no compatible methods in either type leaving you with an empty type featuring no properties at all. Hence, there is no way of calling a method .selectAll() on that empty type which is basically what the error message is telling you.

There are two ways around that:

  1. Use intersection types. You could modify your type definition to

     Selection<BaseType, unknown, BaseType, unknown> & Transition<BaseType, unknown, BaseType, unknown>

Although this will work I would discourage the use because you are blending two distinct interfaces into one type to "just make it work". You are not extending an interface nor are you enriching it, but you are simply mixing in overloaded definitions of the .selectAll() method which feels somewhat sketchy.


  1. Narrow your typing. Preferably, you should keep using the union type and narrow the type down to either a Selection or a Transition when using it. This can be done by employing an instanceof type guard. That way, TypeScript is able to differentiate between both types:
import { Selection, selection, Transition, transition, BaseType } from "d3";

function myFunc(
maybeTransition: Selection<BaseType, unknown, BaseType, unknown> |
Transition<BaseType, unknown, BaseType, unknown>
) {
let texts;
if (maybeTransition instanceof selection) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Selection
} else if (maybeTransition instanceof transition) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Transition
}
}

This might look clumsy but it is due to the type-safety of TypeScript itself. Although both Selection as well as Transition are modelled to look alike in Vanilla JavaScript the type-safety of TypeScript sometimes comes at the cost of more verbose code.

You also have to keep in mind that all of the above holds true for the texts variable which is also a union of both types. You will have to deal with that the same way later on in your code. Depending on your overall design it might be worth considering a totally different approach, though.

First data is ignored without using selectAll in d3.js

To understand what happened, you have to understand what is an "enter" selection. The example at the bottom of this post briefly explains both the "enter" selection and why you have to selectAll("p") in your second snippet, even if you don't have any <p> in the document.

Now, let's see your snippets:

Your first snippet:

In your first snippet, data is [0, 1, 2]. So, you have 3 elements. When you select the body, there is clearly a body in the DOM. So, you associate the first datum (0) to this element (body). Now, you have 2 data not associated to any DOM element: 1 and 2. These two data are your "enter" selection. When you append the <p>, your enter selection has only those two numbers.

Your second snippet

In your second snippet, data is again [0, 1, 2]. The difference is that now you select all <p>... but there is none. This is the "placeholder" in the example I linked. As there is no <p> in the DOM to associate with the data, your enter selection has all 3 data: 0, 1 and 2.

As I wrote in the example:

If in your "enter" selection you select something that doesn't exist, your "enter" selection will always contain all your data.


The role of placeholders in "enter" selections

What is an enter selection?

In D3.js, when one binds data to DOM elements, three situations are possible:

  1. The number of elements and the number of data points are the same;
  2. There are more elements than data points;
  3. There are more data points than elements;

In the situation #3, all the data points without a corresponding DOM element belong to the enter selection. Thus, In D3.js, enter selections are selections that, after joining elements to the data, contains all the data that don't match any DOM element. If we use an append function in an enter selection, D3 will create new elements binding that data for us.

This is a Venn diagram explaining the possible situations regarding number of data points/number of DOM elements:

Sample Image

As we can see, the enter selection is the blue area at the left: data points without corresponding DOM elements.

The structure of the enter selection

Typically, an enter selection has these 4 steps:

  1. selectAll: Select elements in the DOM;
  2. data: Counts and parses the data;
  3. enter: Comparing the selection with the data, creates new elements;
  4. append: Append the actual elements in the DOM;

This is a very basic example (look at the 4 steps in the var divs):

var data = [40, 80, 150, 160, 230, 260];

var body = d3.select("body");

var divs = body.selectAll("div")
.data(data)
.enter()
.append("div");

divs.style("width", function(d) { return d + "px"; })
.attr("class", "divchart")
.text(function(d) { return d; });

And this is the result (jsfiddle here):

Sample Image

Notice that, in this case, we used selectAll("div") as the first line in our "enter" selection variable. We have a dataset with 6 values, and D3 created 6 divs for us.

The role of placeholders

But suppose that we already have a div in our document, something like <div>This is my chart</div> at the top. In that case, when we write:

body.selectAll("div")

we are selecting that existent div. So, our enter selection will have only 5 datum without matching elements. For instance, in this jsfiddle, where there is already a div in the HTML ("This is my chart"), this will be the outcome:

Sample Image

We don't see the value "40" anymore: our first "bar" disappeared, and the reason for that is that our "enter" selection now has only 5 elements.

What we have to understand here is that in the first line of our enter selection variable, selectAll("div"), those divs are just placeholders. We don't have to select all the divs if we are appending divs, or all the circle if we are appending circle. We can select different things. And, if we don't plan to have an "update" or an "exit" selection, we can select anything:

var divs = body.selectAll(".foo")//this class doesn't exist, and never will!
.data(data)
.enter()
.append("div");

Doing this way, we are selecting all the ".foo". Here, "foo" is a class that not only doesn't exist, but also it's never created anywhere else in the code! But it doesn't matter, this is only a placeholder. The logic is this:

If in your "enter" selection you select something that doesn't exist, your "enter" selection will always contain all your data.

Now, selecting .foo, our "enter" selection have 6 elements, even if we already have a div in the document:

Sample Image

And here is the corresponding jsfiddle.

Selecting null

By far, the best way to guarantee that you are selecting nothing is selecting null. Not only that, but this alternative is way faster than any other.

Thus, for an enter selection, just do:

selection.selectAll(null)
.data(data)
.enter()
.append(element);

Here is a demo fiddle: https://jsfiddle.net/gerardofurtado/th6s160p/

Conclusion

When dealing with "enter" selections, take extra care to do not select something that already exists. You can use anything in your selectAll, even things that don't exist and will never exist (if you don't plan to have an "update" or an "exit" selection).

The code in the examples is based on this code by Mike Bostock: https://bl.ocks.org/mbostock/7322386

In D3, why Do I need to choose select a dummy object to create something?

The strong part of d3 is in modifying objects it previously created. If you don't provide a dummy object, d3 would need an explicit check to see whether it is already created or not. By providing a dummy object, such test isn't needed any more. Also, on a complicated page, the dummy object gives the exact position into the html tree to place the d3 object.

Note that d3 is very open minded, and if you really want you can create the element yourself, especially for examples where the whole page is just one d3 object.



Related Topics



Leave a reply



Submit