既存のD3.jsのコードをアロー関数で置き換える際に変な動作を起こしてしまったのでメモしておきます。

D3.jsでは、DOMにイベントを与えるselection.on(typenames, listener)で、イベントリスナ内でそのDOMを選択したいときにthisを使うことが多いです。

例えば、以下のようなコードがよく使われます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  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]と書くことで解決します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  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(要素の集合)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  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
    });

既存のコードを無理にアロー関数に置き換える必要はありませんが、置き換えたときにこういうことが起きる可能性はあると思うので、備忘録として残しておきます。

広告