コンテンツへスキップ

d3-drag

· ドラッグアンドドロップは、空間要素を操作するための一般的なインタラクション方法です。ポインタをオブジェクトに移動し、押したままにして掴み、「ドラッグ」して新しい場所に移動し、離して「ドロップ」します。D3のドラッグビヘイビアは、ドラッグアンドドロップのための柔軟な抽象化を提供します。たとえば、力指向グラフのノードをドラッグできます。

Force-Directed Graph

または、衝突する円のシミュレーション。

Force Dragging II

ドラッグビヘイビアは、要素の移動だけではありません。ドラッグジェスチャに応答するさまざまな方法があります。たとえば、散布図で要素を輪ゴムで囲む、またはキャンバスに線を引くために使用できます。

Line Drawing

ドラッグビヘイビアは、d3-zoomのような他のビヘイビアと組み合わせることができます。

Drag & Zoom II

ドラッグビヘイビアはDOMに依存しないため、SVG、HTML、Canvasで使用できます!また、Voronoiオーバーレイや最寄りのターゲット検索などの高度な選択手法で拡張することもできます。

Circle Dragging IVCircle Dragging II

ドラッグビヘイビアはマウスとタッチの入力を統合し、ブラウザ特有の問題を回避します。将来的には、Pointer Eventsもサポートする予定です。

drag()

ソース · 新しいドラッグビヘイビアを作成します。返されるビヘイビア、dragは、オブジェクトと関数の両方であり、通常はselection.callを介して選択された要素に適用されます。

js
const drag = d3.drag();

drag(selection)

ソース · 指定されたselectionにこのドラッグビヘイビアを適用します。この関数は通常直接呼び出されず、selection.callを介して呼び出されます。たとえば、ドラッグビヘイビアをインスタンス化して選択に適用するには

js
d3.selectAll(".node").call(d3.drag().on("start", started));

内部的に、ドラッグビヘイビアはselection.onを使用して、ドラッグに必要なイベントリスナーをバインドします。リスナーは.dragという名前を使用するため、その後、次のようにドラッグビヘイビアをunbindできます。

js
selection.on(".drag", null);

ドラッグビヘイビアを適用すると、-webkit-tap-highlight-colorスタイルが透明に設定され、iOSでのタップハイライトが無効になります。異なるタップハイライトの色が必要な場合は、ドラッグビヘイビアを適用した後にこのスタイルを削除するか再適用してください。

drag.container(container)

ソース · containerが指定されている場合、コンテナアクセッサを指定されたオブジェクトまたは関数に設定し、ドラッグビヘイビアを返します。containerが指定されていない場合、現在のコンテナアクセッサを返します。デフォルトは

js
function container() {
  return this.parentNode;
}

ドラッグジェスチャのcontainerは、後続のドラッグイベントの座標系を決定し、event.xとevent.yに影響を与えます。コンテナアクセッサによって返される要素は、その後pointerに渡され、ポインタのローカル座標が決定されます。

デフォルトのコンテナアクセッサは、開始入力イベントを受信した(dragを参照)元の選択の要素の親ノードを返します。これらの要素は通常親に対して位置付けられているため、SVGまたはHTML要素をドラッグする場合によく適しています。ただし、Canvasでグラフィカル要素をドラッグする場合は、コンテナを開始要素自体として再定義することが望ましい場合があります。

js
function container() {
  return this;
}

または、コンテナは要素を直接指定することもできます。たとえば、drag.container(canvas)のように。

drag.filter(filter)

ソース · filterが指定されている場合、イベントフィルタを指定された関数に設定し、ドラッグビヘイビアを返します。filterが指定されていない場合、現在のフィルタを返します。デフォルトは

js
function filter(event) {
  return !event.ctrlKey && !event.button;
}

フィルタが偽の値を返す場合、開始イベントは無視され、ドラッグジェスチャは開始されません。したがって、フィルタはどの入力イベントが無視されるかを決定します。デフォルトのフィルタは、セカンダリボタンのmousedownイベントを無視します。これらのボタンは通常、コンテキストメニューなど、他の目的のために使用されるためです。

drag.touchable(touchable)

ソース · touchableが指定されている場合、タッチサポート検出器を指定された関数に設定し、ドラッグビヘイビアを返します。touchableが指定されていない場合、現在のタッチサポート検出器を返します。デフォルトは

js
function touchable() {
  return navigator.maxTouchPoints || ("ontouchstart" in this);
}

タッチイベントリスナーは、ドラッグビヘイビアが適用されたときに、対応する要素に対して検出器が真の値を返す場合にのみ登録されます。デフォルトの検出器は、タッチ入力可能なほとんどのブラウザでうまく機能しますが、すべてではありません。たとえば、Chromeのモバイルデバイスエミュレータは検出に失敗します。

drag.subject(subject)

ソース · subjectが指定されている場合、サブジェクトアクセッサを指定されたオブジェクトまたは関数に設定し、ドラッグビヘイビアを返します。subjectが指定されていない場合、現在のサブジェクトアクセッサを返します。デフォルトは

js
function subject(event, d) {
  return d == null ? {x: event.x, y: event.y} : d;
}

ドラッグジェスチャのsubjectは、「ドラッグされているもの」を表します。mousedownまたはtouchstartなどの開始入力イベントを受信したときに、ドラッグジェスチャが開始される直前に計算されます。その後、このジェスチャの後のドラッグイベントで、event.subjectとして公開されます。

デフォルトのサブジェクトは、開始入力イベントを受信した元の選択(dragを参照)の要素のdatumです。このdatumが未定義の場合、ポインタの座標を表すオブジェクトが作成されます。SVGで円要素をドラッグする場合、デフォルトのサブジェクトは、ドラッグされている円のdatumです。Canvasでは、デフォルトのサブジェクトはキャンバス要素のdatumです(キャンバスのどこをクリックしたかに関係なく)。この場合、カスタムサブジェクトアクセッサの方が適しています。たとえば、特定の検索radius内でマウスに最も近い円を選択するアクセッサなどです。

js
function subject(event) {
  let n = circles.length,
      i,
      dx,
      dy,
      d2,
      s2 = radius * radius,
      circle,
      subject;

  for (i = 0; i < n; ++i) {
    circle = circles[i];
    dx = event.x - circle.x;
    dy = event.y - circle.y;
    d2 = dx * dx + dy * dy;
    if (d2 < s2) subject = circle, s2 = d2;
  }

  return subject;
}

ヒント

必要に応じて、上記はquadtree.findsimulation.find、またはdelaunay.findを使用して高速化できます。

返される対象は、xプロパティとyプロパティを公開するオブジェクトである必要があります。これにより、ドラッグジェスチャ中に対象とポインタの相対位置を維持できます。対象がnullまたはundefinedの場合、このポインタに対するドラッグジェスチャは開始されません。ただし、他の開始タッチは依然としてドラッグジェスチャを開始する可能性があります。drag.filterも参照してください。

ドラッグジェスチャの対象は、ジェスチャ開始後に変更できません。対象アクセサは、selection.onリスナーと同じコンテキストと引数で呼び出されます。現在のイベント(event)とデータdで、thisコンテキストは現在のDOM要素です。対象アクセサの評価中、eventはbeforestart ドラッグイベントです。開始入力イベントにアクセスするにはevent.sourceEventを使用し、タッチ識別子にアクセスするにはevent.identifierを使用します。event.xとevent.yはコンテナを基準とした相対座標であり、pointerを使用して計算されます。

drag.clickDistance(distance)

ソースコード · distanceが指定されている場合、mousedownとmouseupの間のマウスの移動距離の最大値を設定し、その後にクリックイベントが発生します。mousedownとmouseupの間のいずれかの時点で、マウスがmousedown時の位置からの距離がdistance以上になると、mouseup後のクリックイベントは抑制されます。distanceが指定されていない場合、現在の距離閾値を返します(デフォルトは0)。距離閾値はクライアント座標で測定されます(event.clientXevent.clientY)。

drag.on(typenames, listener)

ソースコード · listenerが指定されている場合、指定されたtypenamesに対するイベントlistenerを設定し、ドラッグ動作を返します。同じタイプと名前でイベントリスナーが既に登録されている場合、新しいリスナーが追加される前に既存のリスナーが削除されます。listenerがnullの場合、指定されたtypenamesに対する現在のイベントリスナーを削除します(存在する場合)。listenerが指定されていない場合、指定されたtypenamesに一致する現在割り当てられている最初のリスナーを返します(存在する場合)。指定されたイベントがディスパッチされると、各listenerselection.onリスナーと同じコンテキストと引数で呼び出されます。現在のイベント(event)とデータdで、thisコンテキストは現在のDOM要素です。

typenamesは、空白で区切られた1つ以上のtypenameを含む文字列です。各typenametypeであり、オプションでピリオド(.)とnameが続きます(例:drag.foodrag.bar)。nameにより、同じtypeに対して複数のリスナーを登録できます。typeは次のいずれかである必要があります。

  • start - 新しいポインタがアクティブになった後(mousedownまたはtouchstart時)。
  • drag - アクティブなポインタが移動した後(mousemoveまたはtouchmove時)。
  • end - アクティブなポインタが非アクティブになった後(mouseup、touchend、またはtouchcancel時)。

dispatch.onの詳細を参照してください。

ドラッグジェスチャ中にdrag.onを使用して登録されたリスナーを変更しても、現在のドラッグジェスチャには影響しません。代わりに、event.onを使用する必要があります。これにより、現在のドラッグジェスチャに対する一時的なイベントリスナーを登録することもできます。**ドラッグジェスチャ中は、アクティブなポインタごとに個別のイベントがディスパッチされます。** たとえば、複数の指で同時に複数の対象をドラッグする場合、2本の指が同時にタッチを開始した場合でも、各指に対して開始イベントがディスパッチされます。ドラッグイベントの詳細を参照してください。

dragDisable(window)

ソースコード · 指定されたwindowでのネイティブのドラッグアンドドロップとテキスト選択を防止します。mousedownイベントのデフォルトアクションの防止(#9を参照)の代替として、このメソッドはmousedown後に望ましくないデフォルトアクションを防止します。サポートされているブラウザでは、これはdragstartイベントとselectstartイベントのキャプチャ、関連するデフォルトアクションの防止、およびそれらの伝播の即時停止を意味します。選択イベントをサポートしていないブラウザでは、user-select CSSプロパティがドキュメント要素でnoneに設定されます。このメソッドは、mousedownで呼び出され、mouseupでdragEnableが続くことを目的としています。

dragEnable(window, noclick)

ソースコード · 指定されたwindowでのネイティブのドラッグアンドドロップとテキスト選択を許可します。dragDisableの効果を元に戻します。このメソッドは、mouseupで呼び出され、mousedownでdragDisableが先行することを目的としています。noclickがtrueの場合、このメソッドはクリックイベントも一時的に抑制します。クリックイベントの抑制は0ミリ秒のタイムアウト後に期限切れになるため、現在のmouseupイベントに続くクリックイベントのみを抑制します(存在する場合)。

ドラッグイベント

ドラッグイベントリスナーが呼び出されると、最初の引数として現在のドラッグイベントを受け取ります。eventオブジェクトはいくつかのフィールドを公開します。

  • target - 関連するドラッグ動作
  • type - 文字列“start”、“drag”または“end”;drag.onを参照。
  • subject - ドラッグ対象。drag.subjectで定義されます。
  • x - 対象の新しいx座標;drag.containerを参照。
  • y - 対象の新しいy座標;drag.containerを参照。
  • dx - 前回のドラッグイベント以降のx座標の変化。
  • dy - 前回のドラッグイベント以降のy座標の変化。
  • identifier - 文字列“mouse”または数値のタッチ識別子
  • active - 現在のアクティブなドラッグジェスチャの数(開始と終了時、これは含まない)。
  • sourceEvent - 基になる入力イベント(mousemoveやtouchmoveなど)。

event.activeフィールドは、一連の同時ドラッグジェスチャにおける最初の開始イベントと最後の終了イベントを検出するのに役立ちます。最初のドラッグジェスチャが開始されると0になり、最後のドラッグジェスチャが終了すると0になります。

eventオブジェクトはevent.onメソッドも公開します。

この表は、ドラッグ動作がネイティブイベントをどのように解釈するかを示しています。

イベントリスニング要素ドラッグイベントデフォルトは防止される?
mousedown⁵selectionstartいいえ¹
mousemove²window¹dragはい
mouseup²window¹endはい
dragstart²window-はい
selectstart²window-はい
click³window-はい
touchstartselectionstartいいえ⁴
touchmoveselectiondragはい
touchendselectionendいいえ⁴
touchcancelselectionendいいえ⁴

使用されたすべてのイベントの伝播は即座に停止されます。ドラッグジェスチャを開始する一部のイベントを防止する場合は、drag.filterを使用してください。

¹ iframeの外側のイベントをキャプチャするために必要です。#9を参照。
² アクティブなマウスベースのジェスチャ中にのみ適用されます。#9を参照。
³ 一部のマウスベースのジェスチャの直後のみ適用されます。drag.clickDistanceを参照。
⁴ タッチ入力でのクリックエミュレーションを許可するために必要です。#9を参照。
⁵ タッチジェスチャの終了から500ミリ秒以内にある場合無視されます。クリックエミュレーションを想定しています。

event.on(typenames, listener)

ソースコード · drag.onと同等ですが、現在のドラッグジェスチャのみに適用されます。ドラッグジェスチャが開始される前に、現在のドラッグイベントリスナーのコピーが作成されます。このコピーは現在のドラッグジェスチャにバインドされ、event.onによって変更されます。これは、現在のドラッグジェスチャのイベントのみを受信する一時的なリスナーに役立ちます。たとえば、この開始イベントリスナーは、クロージャとして一時的なドラッグおよび終了イベントリスナーを登録します。

js
function started(event) {
  const circle = d3.select(this).classed("dragging", true);
  const dragged = (event, d) => circle.raise().attr("cx", d.x = event.x).attr("cy", d.y = event.y);
  const ended = () => circle.classed("dragging", false);
  event.on("drag", dragged).on("end", ended);
}