On D3 and Arrow Functions

D3 implements a useful pattern where functions that operate on selections are given the selected DOM element via this. It allows us to use the selection’s bound data and the DOM element within the same context.

selection.on('mouseenter', function(d) {
d3.select(this).text(d.name);
});

This is possible because Javascript allows D3 to bind any object to this when calling our function, primarily via bind(), call(), or apply().

When making the switch to ES6, it seems reasonable that we’d want to use the syntactically delectable arrow functions to replace all our callback functions. Their succinct syntax makes them especially attractive for short, attribute retrieving callbacks, which are quite prevalent in D3-based code.

However, arrow functions have an additional feature that prevents D3 from passing us a custom this and so our code breaks or leads to the wrong behavior.

selection.on('mouseenter', (d) => {
d3.select(this).text(d.name);
});
// ➜ 💩

The reason for this is a feature called Lexical this. Lexical, in this context, is just a Harvard-degree way of saying static. In the olden days this was determined dynamically based on the way a function was called. It gave us a lot of flexibility, but also required discipline and a good understanding of all the ways in which this could be manipulated. Arrow functions, on the other hand, always determine the value of this from the surrounding context, i.e., statically. This strict definition gives us predictability but it also means it cannot be changed at runtime. So while D3 does attempt to set its value for us, Javascript discards it and uses the lexical reference instead. In the above example it’s possible that this actually refers to the global window object if there is no other surrounding scope, which would explain why it doesn’t work.

Before you start Googling, let me save you the finger cramp. It’s not possible to change this.

But there’s another way. The function signature for functions that operate on selections was changed in D3 v4 to include the selection group. From the release notes:

The standard arguments are the element’s datum (d), the element’s index (i), and the element’s group (nodes), with this as the element.

So now we can use the group along with the provided index to access the current DOM element.

selection.on('mouseenter', (d, i, nodes) => {
d3.select(nodes[i]).text(d.name);
});

Perhaps not quite as elegant, but it lets us keep the best of both worlds and avoid inconsistent function definition syntax. Note that this still points to the DOM element if we choose to use the “classic” function definition as before.

Thanks to Nikolay Dimitrov for pointing out the updated function definition so that we may have our cake and eat it too.

Written by

Code it right!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store