How does d3.js allow us to use second parameter of function as index of dataset?

Tags: ,



Let’s say I have svg with circles in there.

let svg = d3.select("body").append("svg").attr("width", 600).attr("height", 100)
let dataset = [5, 10, 20]
let circles = svg.selectAll("circle").data(dataset).enter().append("circle")

and I want to make circle’s location dynamic by the index of the dataset and radius with dataset’s values

circles.attr("cx", (d,i)=>(i*50)+25)
       .attr("cy", 50)
       .attr("r", (d)=>d)

I technically could pass in i for the “r”‘s function. But I do not have to. That makes this optional parameter. I get that.

But I want to learn how this is possible in javascript language. So I digged deeper by looking at the implementation of d3.js trying to understand how they allow such an optional paremter. But I am struggling to understand what is passing the dataset’s current item as well as index of it.

Answer

At the end of the day your question asks about a very known Javascript feature: in Javascript you can pass less arguments than parameters, or more arguments than parameters. In the former the parameters without arguments are undefined, in the latter the extra arguments are simply ignored.

If you look at the source code you shared you’ll see that selection.attr() internally uses selection.each, which is just this:

export default function(callback) {

  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
      if (node = group[i]) callback.call(node, node.__data__, i, group);
    }
  }

  return this;
}

The most important part is this one:

callback.call(node, node.__data__, i, group);

As you can see, the callback gets passed (via call) node as this, and then 3 arguments:

  • node.__data__: this is the datum
  • i: this is the index
  • group: this is the current group.

Therefore, even if your function inside the attr method doesn’t have the second parameter, it still gets passed the second (and third) argument, regardless.

Here is an example passing more arguments than parameters:

function callback(arg1) {
  console.log(arg1);
}

callback.call(null, "foo", "bar", "baz");

And here is one passing less arguments than parameters:

function callback(arg1, arg2, arg3, arg4, arg5) {
  console.log(arg1)
  console.log(arg2)
  console.log(arg3)
  console.log(arg4)
  console.log(arg5)
}

callback.call(null, "foo", "bar", "baz");


Source: stackoverflow