既存のD3.jsのコードをアロー関数で置き換える際に変な動作を起こしてしまったのでメモしておきます。
追記 (2020/08/29)
2020年8月にリリースされた D3 v6 ではイベントリスナの書き方が変わりました。
詳しくは D3 v6 アロー関数使用時の移行ガイドへ。以下は v5 以前のコードとなります。
D3.jsでは、DOMにイベントを与えるselection.on(typenames, listener)
で、イベントリスナ内でそのDOMを選択したいときにthis
を使うことが多いです。
例えば、以下のようなコードがよく使われます。
d3.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {return xScale(0);})
.attr("y", function(d) {return yScale(d.name);})
.attr("width", function(d) {return xScale(d.value) - xScale(0);})
.attr("height", yScale.bandwidth())
.attr("fill", "white")
.on("mouseover", function() {
d3.select(this)
.transition().duration(500)
.attr("fill", "orange");
})
.on("mouseout", function() {
d3.select(this)
.transition().duration(500)
.attr("fill", "white");
});
このコードに使われている関数をアロー関数で置き換えると、最後の二つ.on("mouseover", listener)
と.on("mouseout", listener)
で設定されたイベントリスナーは正常に動作しません。これはアロー関数のthisを束縛しないという性質によるものです。
アロー関数に置き換えつつ、今まで通りの動作を与えるには、以下のようにlistenerの引数にd, i, nodes
を取り、this
と書いていたところをnodes[i]
と書くことで解決します。
d3.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d) => xScale(0))
.attr("y", (d) => yScale(d.name))
.attr("width", (d) => xScale(d.value) - xScale(0))
.attr("height", yScale.bandwidth())
.attr("fill", "white")
.on("mouseover", (d, i, nodes) => {
d3.select(nodes[i])
.transition().duration(500)
.attr("fill", "orange");
})
.on("mouseout", (d, i, nodes) => {
d3.select(nodes[i])
.transition().duration(500)
.attr("fill", "white");
});
selection.on(typenames, listener)
ではイベントリスナーの引数に、datum(d)、index(i)、nodes
が渡されます。
-
datum (d)
: 要素にバインドされているデータ(datumはdataの単数形) -
index (i)
: セレクションの中の順番 -
nodes
: NodeList(要素の集合)
d3.selectAll("rect")
.on("click", (d, i, nodes) => {
console.log(d)
//=> datum (= data[i])
console.log(i);
//=> index (0, 1, 2, ...)
console.log(nodes);
//=> NodeList [rect, rect, rect, ...]
// d3.selectAll("rect").nodes()で得られるものと同じ
console.log(nodes[i]);
//=> rect
});
既存のコードを無理にアロー関数に置き換える必要はありませんが、置き換えたときにこういうことが起きる可能性はあると思うので、備忘録として残しておきます。