Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve DOM target from drag callback when `this` is not available

The documentation for d3.drag states the DOM element target of the drag event will be available in this to the callback:

When a specified event is dispatched, each listener will be invoked with the same context and arguments as selection.on listeners: the current datum d and index i, with the this context as the current DOM element.

But my call back is an object instance and this points to that object. So I need another way of accessing the current DOM element that is normally passed in this. How can I do it?

like image 669
spinkus Avatar asked Dec 19 '22 05:12

spinkus


1 Answers

Use the second and the third arguments together to get this when this is not available:

d3.drag().on(typename, function(d, i, n) {
  //here, 'this' is simply n[i]
})

For a detailed explanation, have a look at the article below that I wrote to deal with this in arrow functions. The issue is different from yours, but the explanation is the same.

Here is a basic demo, try to drag a circle and look at the console:

var data = d3.range(5)
var svg = d3.select("body")
  .append("svg")
  .attr("width", 400)
  .attr("height", 100);
var circle = svg.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("cy", 50)
  .attr("cx", function(d) {
    return 50 + 50 * d
  })
  .attr("r", 10)
  .attr("fill", "tan")
  .attr("stroke", "black")
  .call(d3.drag()
    .on("start", function(d, i, n) {
      console.log(JSON.stringify(n[i]))
    }))
<script src="https://d3js.org/d3.v4.min.js"></script>

PS: I'm using JSON.stringify on the D3 selection because Stack snippets freeze if you try to console.log a D3 selection.


Using "this" with an arrow function

Most of functions in D3.js accept an anonymous function as an argument. The common examples are .attr, .style, .text, .on and .data, but the list is way bigger than that.

In such cases, the anonymous function is evaluated for each selected element, in order, being passed:

  1. The current datum (d)
  2. The current index (i)
  3. The current group (nodes)
  4. this as the current DOM element.

The datum, the index and the current group are passed as arguments, the famous first, second and third argument in D3.js (whose parameters are traditionally named d, i and p in D3 v3.x). For using this, however, one doesn’t need to use any argument:

.on("mouseover", function(){
    d3.select(this);
});

The above code will select this when the mouse is over the element. Check it working in this fiddle: https://jsfiddle.net/y5fwgopx/

The arrow function

As a new ES6 syntax, an arrow function has a shorter syntax when compared to function expression. However, for a D3 programmer who uses this constantly, there is a pitfall: an arrow function doesn’t create its own this context. That means that, in an arrow function, this has its original meaning from the enclosing context.

This can be useful in several circumstances, but it is a problem for a coder accustomed to use this in D3. For instance, using the same example in the fiddle above, this will not work:

.on("mouseover", ()=>{
    d3.select(this);
});

If you doubt it, here is the fiddle: https://jsfiddle.net/tfxLsv9u/

Well, that’s not a big problem: one can simply use a regular, old fashioned function expression when needed. But what if you want to write all your code using arrow functions? Is it possible to have a code with arrow functions and still properly use this in D3?

The second and third arguments combined

The answer is yes, because this is the same of nodes[i]. The hint is actually present all over the D3 API, when it describes this:

...with this as the current DOM element (nodes[i])

The explanation is simple: since nodes is the current group of elements in the DOM and i is the index of each element, nodes[i] refer to the current DOM element itself. That is, this.

Therefore, one can use:

.on("mouseover", (d, i, nodes) => {
    d3.select(nodes[i]);
});

And here is the corresponding fiddle: https://jsfiddle.net/2p2ux38s/

like image 157
Gerardo Furtado Avatar answered Dec 20 '22 19:12

Gerardo Furtado