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?
Solution 1:
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:
- The current datum (
d
) - The current index (
i
) - The current group (
nodes
) -
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/