D3.js v6.2 - get data index in listener function - selection.on('click', listener)

Viewed 1434

In previous versions it was possible to retrieve the index i by: selection.on("click", function(d,i){...}

However, that does not seem to work anymore in the latest version since the first parameter is always the event object. How can I get the index of the datum in the listener function?

let data = [2,5,8,9]

d3.select("body").selectAll("p")
  .data(data)
  .enter()
  .append("p")
  .text(d=>d)
  .on("mouseover", function(e,d,i){
    //console.log(e); --> event
    console.log(d); 
    console.log(i);
    // i should be the index of the hovered element
  })
<script src="https://d3js.org/d3.v6.min.js"></script>

When a specified event is dispatched on a selected element, the specified listener will be evaluated for the element, being passed the current event (event) and the current datum (d), with this as the current DOM element (event.currentTarget).

Official documentation: https://github.com/d3/d3-selection/blob/v2.0.0/README.md#selection_on

2 Answers

The Observable notebook on the new features in d3-selection 2.0 suggests using a local variable to preserve the index:

Listeners are no longer passed the selected element’s index (i) or siblings (nodes). These were rarely used, had confusing behavior if elements were re-selected, and could cause memory leaks. If you need them, bake them into the element’s data or use a local variable instead.

This can be done with little effort along the following lines:

let data = [2,5,8,9]

const index = d3.local();            // Local variable for storing the index.

d3.select("body").selectAll("p")
  .data(data)
  .enter()
  .append("p")
    .text(d=>d)
    .each(function(d, i) {
      index.set(this, i);            // Store index in local variable.
    })
    .on("mouseover", function(e,d,i){
      console.log(d); 
      console.log(index.get(this));  // Get index from local variable.
    });
<script src="https://d3js.org/d3.v6.min.js"></script>

Note, that although the above example uses a separate call to .each() for clarity this could also be done in the existing call to .text() for sake of brevity and performance.

I'm afraid the index was removed in this update. If you're looking for one, the easiest way to get it is to append it to the data as a property (.data(data.map((d,i) => ({ value: d, i: i }))) for example) or look up the index in the selection of nodes that you have:

let data = [2,5,8,9]

const p = d3.select("body").selectAll("p")
  .data(data)
  .enter()
  .append("p")
  .text(d=>d)
  .on("mouseover", function(e,d){
    console.log(d); 
    console.log(p.nodes().indexOf(this));
  })
<script src="https://d3js.org/d3.v6.min.js"></script>

Related