D3.jsでは、selection.on(イベント名, イベントリスナー, オプション)という形で任意の要素にイベントリスナーを設定できる。イベント名には、ブラウザがサポートするすべてのDOMイベントタイプが使える。

なお、selectionはDOMオブジェクトのラッパーで、単一でも複数でも同じようなインターフェースでDOMを操作できる。

addEventListenerとの違い

element.addEventListener()selection.on()は、渡される引数が同じで、一見すると挙動も同じかと思える。

ただ、以下の点で微妙に異なる。

複数のリスナーの指定方法

ただ、D3.jsのselectionオブジェクトは同名のイベントに指定されたイベントリスナーを上書きする。

js
document.body.addEventListener("click", () => console.log("First listener"));
document.body.addEventListener("click", () => console.log("Second listener"));

d3.select("body").on("click", () => console.log("First listener with D3.js"));
d3.select("body").on("click", () => console.log("Second listener with D3.js"));

// 結果
// First listener
// Second listener
// Second listener with D3.js

同名のイベントに複数のリスナーを指定するには、イベント名の末尾に.をつけて任意の識別子を付与する必要がある。

js
d3.select("body").on("click.first", () =>
  console.log("First listener with D3.js")
);
d3.select("body").on("click.second", () =>
  console.log("Second listener with D3.js")
);

// 結果
// First listener with D3.js
// Second listener with D3.js

イベントリスナーに渡される値

指定した要素で指定したイベントが発生すると、イベントリスナーにはイベントオブジェクトと現在のデータが渡される。

js
d3.select("body")
  .selectAll("p")
  .data([1, 2, 3])
  .join("p")
  .text((d) => d)
  .on("click", (event, d) => console.log(d));

// 結果
// 1 ←1をクリックした場合
// 2 ←2をクリックした場合
// 3 ←3をクリックした場合

イベントリスナーのthis

イベントリスナーのthisは、イベントリスナーを指定したDOM要素(event.currentTarget)となる。

ただ、イベントリスナーをアロー関数で定義すると、そのthisには関数が定義されたスコープが入る。例えばトップレベルではグローバルオブジェクト、あるいはundefinedが入る(どちらになるかはStrictモードによる)。

最近はアロー関数が主流であるため、イベントの発生元を取得したい時はevent.currentTargetを使うのが無難かと思う。

イベントリスナーの削除

イベントリスナーは、selection.on(イベント名, null)で削除できる。

off()とかはないので要注意である。

イベントの座標の取得

event.pageXevent.pageYでも座標を取得できるが、ローカルの座標系に変換する際にはD3.jsが用意するd3.pointer(event, target)あるいはd3.pointers(event, target)といったメソッドを使うと便利である。

両者は、渡された引数をもとに、ターゲット要素の左上隅を基準にして変換した座標(SVG要素内での相対座標)あるいは座標の配列を返す。

If target is not specified, it defaults to the source event’s currentTarget property, if available. If the target is an SVG element, the event’s coordinates are transformed using the inverse of the screen coordinate transformation matrix.

target が指定されない場合、デフォルトではソースイベントのcurrentTargetプロパティが使われる。 もしtargetがSVG 要素の場合、イベントの座標はスクリーン座標変換行列の逆行列を用いて変換される。

引用元:pointer(event, target)

"the inverse of the screen coordinate transformation matrix. "だなんて、なんとカッチョいい響きだろう(意味はわからないが)。

例えば、SVG要素にリスナーを指定すれば、pointer(event)でイベントのSVG内での座標が取れる。また、リスナーの中でpointer(event, 任意のシェイプ要素など)のようにすると、任意の要素の座標が取れる。これらを組み合わせれば、いい感じに図形の位置やサイズを操作できそうである。

参考資料