コンテンツへスキップ

d3-zoom

· パンとズームを使用すると、ユーザーはビューを制限することで関心のある領域に焦点を当てることができます。これは直接操作を使用します。クリックアンドドラッグでパン(移動)、ホイールを回してズーム(スケール)、またはタッチでピンチします。パンとズームは、Webベースのマッピングで広く使用されていますが、高密度な時系列や散布図などの視覚化にも使用できます。

ズーム動作は柔軟な抽象化であり、驚くほど多様な入力方法とブラウザの癖を処理します。ズーム動作はDOMに依存しないため、HTML、SVG、またはCanvasで使用できます。d3-scaled3-axisズーム軸で使用してd3-zoomを使用できます。zoom.scaleExtentを使用してズームを制限し、zoom.translateExtentを使用してパンを制限できます。d3-zoomをd3-dragなどの他の動作と組み合わせてドラッグd3-brushフォーカス+コンテキストで使用できます。

ズーム動作は、zoom.transformを使用してプログラムで制御できます。これにより、ディスプレイを駆動するユーザーインターフェースコントロールを実装したり、データを通してアニメーションツアーを段階的に実行したりできます。スムーズなズームトランジションは、Jarke J. van WijkとWim A.A. Nuijによる“Smooth and efficient zooming and panning”に基づいています。

マップのパンとズームの例については、d3-tileも参照してください。

zoom()

ソースコード · 新しいズーム動作を作成します。返される動作、zoomは、オブジェクトと関数の両方であり、通常はselection.callを介して選択された要素に適用されます。

zoom(selection)

ソースコード · 指定されたselectionにこのズーム動作を適用し、パンとズームを許可するために必要なイベントリスナーをバインドし、まだ定義されていない場合は、選択された各要素のズーム変換を同一変換に初期化します。

この関数は通常直接呼び出されず、代わりにselection.callを介して呼び出されます。たとえば、ズーム動作をインスタンス化して選択に適用するには

js
selection.call(d3.zoom().on("zoom", zoomed));

内部的に、ズーム動作はselection.onを使用して、ズームに必要なイベントリスナーをバインドします。リスナーは名前.zoomを使用するため、その後、次のようにズーム動作のバインドを解除できます。

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

ホイールによるズームのみを無効にする(ネイティブスクロールと干渉しないようにするなど)には、選択にズーム動作を適用した後に、ズーム動作のホイールイベントリスナーを削除できます。

js
selection
    .call(zoom)
    .on("wheel.zoom", null);

あるいは、zoom.filterを使用して、どのイベントがズームジェスチャーを開始できるかをより細かく制御します。

ズーム動作を適用すると、-webkit-tap-highlight-colorスタイルが透明に設定され、iOSでのタップハイライトが無効になります。異なるタップハイライトカラーが必要な場合は、ドラッグ動作を適用した後にこのスタイルを削除するか、再適用します。

ズーム動作は、ズーム動作自体ではなく、ズーム動作が適用された要素にズーム状態を保存します。これにより、ズーム動作を独立したズームで複数の要素に同時に適用できます。ズーム状態は、ユーザーの操作またはzoom.transformを介したプログラムによる変更によって変更できます。

ズーム状態を取得するには、ズームイベントリスナー内の現在のズームイベントevent.transformを使用するか(zoom.onを参照)、特定のノードのzoomTransformを使用します。後者は、ズーム状態をプログラムで変更する場合(ズームインとズームアウトのボタンを実装する場合など)に役立ちます。

zoom.transform(selection, transform, point)

ソースコード · selectionが選択の場合、選択された要素の現在のズーム変換を指定されたtransformに設定し、開始、ズーム、終了のイベントを即座に発行します。

selectionがトランジションの場合、interpolateZoomを使用して指定されたtransformへの「ズーム」トゥイーンを定義し、トランジション開始時に開始イベント、トランジションの各ティックでズームイベント、トランジション終了時(または中断時)に終了イベントを発行します。トランジションは、指定されたpoint周辺の視覚的な動きを最小限に抑えようとします。pointが指定されていない場合、デフォルトでビューポート範囲の中央になります。

transformは、ズーム変換として、またはズーム変換を返す関数として指定できます。同様に、pointは、2要素配列[x, y]として、またはそのような配列を返す関数として指定できます。関数の場合は、選択された各要素に対して呼び出され、現在のイベント(event)とデータdが渡され、thisコンテキストは現在のDOM要素になります。

この関数は通常直接呼び出されず、代わりにselection.callまたはtransition.callを介して呼び出されます。たとえば、ズーム変換を同一変換に即座にリセットするには

js
selection.call(zoom.transform, d3.zoomIdentity);

ズーム変換を750ミリ秒でスムーズに同一変換にリセットするには

js
selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);

このメソッドでは、新しいズーム変換を完全に指定する必要があり、定義済みのスケール範囲移動範囲は適用されません。既存の変換から新しい変換を導出し、スケール範囲と移動範囲を適用するには、利便性メソッドzoom.translateByzoom.scaleBy、およびzoom.scaleToを参照してください。

zoom.translateBy(selection, x, y)

ソースコード · selection が選択範囲の場合、選択された要素の現在のズーム変換xおよびyだけ平行移動します。新しいtx1 = tx0 + kx および ty1 = ty0 + ky となります。selection がトランジションの場合、「zoom」tweenを定義し、現在の変換を平行移動します。このメソッドは、zoom.transform のための便宜的なメソッドです。x および y の平行移動量は、数値または数値を返す関数として指定できます。関数の場合は、選択された各要素に対して呼び出され、現在のデータdとインデックスiが渡されます。thisコンテキストは現在のDOM要素です。

zoom.translateTo(selection, x, y, p)

ソースコード · selection が選択範囲の場合、指定された位置⟨x,y⟩が指定された点pに表示されるように、選択された要素の現在のズーム変換平行移動します。新しいtx = px - kx および ty = py - ky となります。p が指定されていない場合、ビューポート範囲の中央にデフォルト設定されます。selection がトランジションの場合、「zoom」tweenを定義し、現在の変換を平行移動します。このメソッドは、zoom.transform のための便宜的なメソッドです。x および y 座標は、数値または数値を返す関数として指定できます。同様に、p 点は、2要素配列[px,py]または関数として指定できます。関数の場合は、選択された各要素に対して呼び出され、現在のデータdとインデックスiが渡されます。thisコンテキストは現在のDOM要素です。

zoom.scaleBy(selection, k, p)

ソースコード · selection が選択範囲の場合、選択された要素の現在のズーム変換k倍に拡大縮小します。新しいk₁ = k₀k となります。基準点pは移動します。p が指定されていない場合、ビューポート範囲の中央にデフォルト設定されます。selection がトランジションの場合、「zoom」tweenを定義し、現在の変換を平行移動します。このメソッドは、zoom.transform のための便宜的なメソッドです。k 倍率は、数値または数値を返す関数として指定できます。同様に、p 点は、2要素配列[px,py]または関数として指定できます。関数の場合は、選択された各要素に対して呼び出され、現在のデータdとインデックスiが渡されます。thisコンテキストは現在のDOM要素です。

zoom.scaleTo(selection, k, p)

ソースコード · selection が選択範囲の場合、選択された要素の現在のズーム変換k拡大縮小します。新しいk₁ = k となります。基準点pは移動します。p が指定されていない場合、ビューポート範囲の中央にデフォルト設定されます。selection がトランジションの場合、「zoom」tweenを定義し、現在の変換を平行移動します。このメソッドは、zoom.transform のための便宜的なメソッドです。k 倍率は、数値または数値を返す関数として指定できます。同様に、p 点は、2要素配列[px,py]または関数として指定できます。関数の場合は、選択された各要素に対して呼び出され、現在のデータdとインデックスiが渡されます。thisコンテキストは現在のDOM要素です。

zoom.constrain(constrain)

ソースコード · constrain が指定されている場合、変換制約関数を指定された関数に設定し、ズーム動作を返します。constrain が指定されていない場合、現在の制約関数を返します。デフォルトは

js
function constrain(transform, extent, translateExtent) {
  var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
      dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
      dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
      dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
  return transform.translate(
    dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
    dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
  );
}

制約関数は、現在のtransformビューポート範囲、および平行移動範囲が与えられた場合、[transform]#zoomTransform) を返す必要があります。デフォルトの実装では、ビューポート範囲が平行移動範囲の外に出ないようにします。

zoom.filter(filter)

ソースコード · filter が指定されている場合、フィルターを指定された関数に設定し、ズーム動作を返します。filter が指定されていない場合、現在のフィルターを返します。デフォルトは

js
function filter(event) {
  return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}

フィルターには、現在のイベント(event)とデータdが渡され、thisコンテキストは現在のDOM要素です。フィルターが偽を返す場合、開始イベントは無視され、ズームジェスチャーは開始されません。したがって、フィルターはどの入力イベントが無視されるかを決定します。デフォルトのフィルターは、通常はコンテキストメニューなど、他の目的のために意図されているため、セカンダリボタンのmousedownイベントを無視します。

zoom.touchable(touchable)

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

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

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

zoom.wheelDelta(delta)

ソースコード · delta が指定されている場合、ホイールデルタ関数を指定された関数に設定し、ズーム動作を返します。delta が指定されていない場合、現在のホイールデルタ関数を返します。デフォルトは

js
function wheelDelta(event) {
  return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * (event.ctrlKey ? 10 : 1);
}

ホイールデルタ関数によって返される値Δは、WheelEventに応答して適用されるスケーリング量を決定します。transform.k 倍率は2Δ倍されます。たとえば、Δが+1の場合、倍率は2倍になり、Δが-1の場合、倍率は半分になります。

zoom.extent(extent)

ソースコード · extent が指定されている場合、ビューポート範囲を指定された点の配列[[x0, y0], [x1, y1]]に設定します。[x0, y0]はビューポートの左上隅、[x1, y1]はビューポートの右下隅です。そして、このズーム動作を返します。extent は、このような配列を返す関数として指定することもできます。関数の場合は、現在のデータdが渡され、thisコンテキストは現在のDOM要素です。

extent が指定されていない場合、現在の範囲アクセッサを返します。デフォルトは[[0, 0], [width, height]]で、widthは要素のclient widthheightは要素のclient heightです。SVG要素の場合、最も近い祖先SVG要素のviewBox、またはwidthおよびheight属性が使用されます。または、element.getBoundingClientRectの使用を検討してください。

ビューポート範囲は、いくつかの関数に影響します。zoom.scaleByおよびzoom.scaleToによる変更中は、ビューポートの中央が固定されたままになります。ビューポートの中央と寸法は、interpolateZoomによって選択されるパスに影響します。そして、ビューポート範囲は、オプションの平行移動範囲を適用するために必要です。

zoom.scaleExtent(extent)

ソースコード · extent が指定されている場合、スケール範囲を指定された数値の配列[k0, k1]に設定します。ここで、k0は最小許容倍率、k1は最大許容倍率です。そして、このズーム動作を返します。extent が指定されていない場合、現在のスケール範囲を返します。デフォルトは[0, ∞]です。スケール範囲は、拡大縮小を制限します。これは、対話時やzoom.scaleByzoom.scaleTozoom.translateByを使用する場合に適用されます。ただし、zoom.transformを使用して変換を明示的に設定する場合は適用されません。

ユーザーが既にスケール範囲の対応する限界に達している状態でホイール操作によってズームしようとすると、ホイールイベントは無視され、ズームジェスチャーは開始されません。これにより、拡大後にはズーム可能な領域を超えてスクロールしたり、縮小後には上にスクロールしたりできます。スケール範囲に関係なく、ホイール入力でのスクロールを常に防止したい場合は、ホイールイベントリスナーを登録してブラウザのデフォルト動作を防止してください。

js
selection
    .call(zoom)
    .on("wheel", event => event.preventDefault());

zoom.translateExtent(extent)

ソースコード · extent が指定されている場合、平行移動範囲を指定された点の配列[[x0, y0], [x1, y1]]に設定します。[x0, y0]はワールドの左上隅、[x1, y1]はワールドの右下隅です。そして、このズーム動作を返します。extent が指定されていない場合、現在の平行移動範囲を返します。デフォルトは[[-∞, -∞], [+∞, +∞]]です。平行移動範囲はパンを制限し、縮小時に平行移動を引き起こす可能性があります。これは、対話時やzoom.scaleByzoom.scaleTozoom.translateByを使用する場合に適用されます。ただし、zoom.transformを使用して変換を明示的に設定する場合は適用されません。

zoom.clickDistance(distance)

ソースコード · distance が指定されている場合、mousedown と mouseup の間にマウスが移動できる最大距離を設定します。この距離を超えると、mouseup 後のクリックイベントは抑制されます。distance が指定されていない場合、現在の距離の閾値(デフォルトは 0)を返します。距離の閾値はクライアント座標(event.clientX および event.clientY)で測定されます。

zoom.tapDistance(distance)

ソースコード · distance が指定されている場合、ダブルタップジェスチャで最初の touchstart と 2 番目の touchend の間に移動できる最大距離を設定します。この距離を超えると、ダブルクリックイベントは抑制されます。distance が指定されていない場合、現在の距離の閾値(デフォルトは 10)を返します。距離の閾値はクライアント座標(event.clientX および event.clientY)で測定されます。

zoom.duration(duration)

ソースコード · duration が指定されている場合、ダブルクリックとダブルタップでのズームトランジションの時間をミリ秒単位で設定し、ズーム動作を返します。duration が指定されていない場合、現在の時間(デフォルトは 250 ミリ秒)を返します。時間が 0 より大きくない場合、ダブルクリックとダブルタップはスムーズなトランジションを開始するのではなく、ズーム変換に瞬時の変更をトリガーします。

ダブルクリックとダブルタップのトランジションを無効にするには、選択要素にズーム動作を適用した後に、ズーム動作の dblclick イベントリスナーを削除します。

js
selection
    .call(zoom)
    .on("dblclick.zoom", null);

zoom.interpolate(interpolate)

ソースコード · interpolate が指定されている場合、ズームトランジションの補間ファクトリを指定された関数に設定します。interpolate が指定されていない場合、現在の補間ファクトリ(デフォルトはスムーズなズームを実装するためのinterpolateZoom)を返します。2 つのビュー間の直接補間を適用するには、代わりにinterpolate を使用してください。

zoom.on(typenames, listener)

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

typenames は、空白で区切られた 1 つ以上の typename を含む文字列です。各 typenametype で、オプションでピリオド(.)と name が続きます(例:zoom.foozoom.bar)。name を使用すると、同じ type に複数のリスナーを登録できます。type は次のいずれかでなければなりません。

  • start - ズーム開始後(mousedown など)。
  • zoom - ズーム変換の変更後(mousemove など)。
  • end - ズーム終了後(mouseup など)。

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

ズームイベント

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

  • event.target - 関連付けられたズーム動作
  • event.type - 文字列 "start"、"zoom"、または "end"。 zoom.on を参照してください。
  • event.transform - 現在のズーム変換
  • event.sourceEvent - 基になる入力イベント(mousemove や touchmove など)。

ズーム動作はさまざまなインタラクションイベントを処理します。

イベントリスニング要素ズームイベントデフォルト防止?
mousedown⁵選択範囲startいいえ¹
mousemove²ウィンドウ¹zoomはい
mouseup²ウィンドウ¹endはい
dragstart²ウィンドウ-はい
selectstart²ウィンドウ-はい
click³ウィンドウ-はい
dblclick選択範囲複数⁶はい
wheel⁸選択範囲zoom⁷はい
touchstart選択範囲複数⁶いいえ⁴
touchmove選択範囲zoomはい
touchend選択範囲endいいえ⁴
touchcancel選択範囲endいいえ⁴

消費されたすべてのイベントの伝播は即座に停止されます。

¹ iframe 外のイベントをキャプチャするために必要です。 d3-drag#9 を参照してください。
² アクティブなマウスベースのジェスチャ中にのみ適用されます。 d3-drag#9 を参照してください。
³ 一部のマウスベースのジェスチャの直後のみ適用されます。 zoom.clickDistance を参照してください。
⁴ タッチ入力でのクリックエミュレーションを許可するために必要です。 d3-drag#9 を参照してください。
⁵ タッチジェスチャの終了から 500 ミリ秒以内であれば無視されます。クリックエミュレーションを想定しています。
⁶ ダブルクリックとダブルタップは、start、zoom、end イベントを生成するトランジションを開始します。 zoom.tapDistance を参照してください。
⁷ 最初の wheel イベントは start イベントを生成します。150 ミリ秒間 wheel イベントが受信されないと、end イベントが生成されます。
スケール範囲の対応する制限に既に達している場合、無視されます。

zoomTransform(node)

ソースコード · 指定された node の現在の変換を返します。node は通常、selection ではなく DOM 要素である必要があります。(選択範囲は、異なる状態の複数のノードで構成される可能性があり、この関数は単一の変換のみを返します。)選択範囲がある場合は、最初にselection.node を呼び出してください。

js
var transform = d3.zoomTransform(selection.node());

イベントリスナー のコンテキストでは、node は通常、入力イベントを受信した要素です(event.transform と等しくなります)、this

js
var transform = d3.zoomTransform(this);

内部的には、要素の変換は element.__zoom として格納されますが、直接アクセスするのではなく、このメソッドを使用する必要があります。指定された node に定義済みの変換がない場合、最も近い祖先の変換を返し、存在しない場合は恒等変換を返します。返される変換は、次のような形式の 2 次元変換行列を表します。

k 0 tx
0 k ty
0 0 1

(この行列は、スケールと平行移動のみを表すことができます。将来のリリースでは回転も許可される可能性がありますが、これはおそらく後方互換性のない変更になります。)位置⟨x,y⟩は⟨xk + tx,yk + ty⟩に変換されます。transform オブジェクトは次のプロパティを公開します。

  • transform.x - x 軸に沿った平行移動量 tx
  • transform.y - y 軸に沿った平行移動量 ty
  • transform.k - スケール係数 k

これらのプロパティは読み取り専用と見なす必要があります。変換を変更する代わりに、transform.scaletransform.translate を使用して新しい変換を導き出します。ズーム動作の便利なメソッドについては、zoom.scaleByzoom.scaleTozoom.translateBy も参照してください。指定された ktx、および ty で変換を作成するには

js
var t = d3.zoomIdentity.translate(x, y).scale(k);

Canvas 2D コンテキスト に変換を適用するには、context.translate の後に context.scale を使用します。

js
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);

同様に、CSS を介して HTML 要素に変換を適用するには

js
div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")");
div.style("transform-origin", "0 0");

SVG に変換を適用するには

js
g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");

または、より簡単に、transform.toString を利用して

js
g.attr("transform", transform);

変換の順序は重要です!平行移動はスケールよりも前に適用する必要があります。

zoomIdentity

ソースコード · 恒等変換。k = 1、tx = ty = 0。

new d3.ZoomTransform(k, x, y)

ソースコード · スケール k と平行移動 (x, y) を持つ変換を返します。

transform.scale(k)

ソースコード · スケール k₁k₀k と等しい変換を返します。ここで、k₀ はこの変換のスケールです。

transform.translate(x, y)

ソースコード · 平行移動 tx1ty1tx0 + tk xty0 + tk y と等しい変換を返します。ここで、tx0ty0 はこの変換の平行移動であり、tk はこの変換のスケールです。

transform.apply(point)

ソースコード · 指定された point の変換を返します。これは数値の 2 要素配列 [x, y] です。返される点は [xk + tx, yk + ty] と等しくなります。

transform.applyX(x)

ソースコード · 指定されたx座標の変換結果、xk + tx を返します。

transform.applyY(y)

ソースコード · 指定されたy座標の変換結果、yk + ty を返します。

transform.invert(point)

ソースコード · 指定されたpoint(数値の2要素配列 [x, y])の逆変換を返します。返される点は、[(x - tx) / k, (y - ty) / k] と等しくなります。

transform.invertX(x)

ソースコード · 指定されたx座標の逆変換結果、(x - tx) / k を返します。

transform.invertY(y)

ソースコード · 指定されたy座標の逆変換結果、(y - ty) / k を返します。

transform.rescaleX(x)

ソースコード · ドメインが変換された連続スケールxコピーを返します。連続スケールです。これはまず、スケールのレンジ逆x変換を適用し、次に逆スケールを適用して対応するドメインを計算することによって実装されています。

js
function rescaleX(x) {
  var range = x.range().map(transform.invertX, transform),
      domain = range.map(x.invert, x);
  return x.copy().domain(domain);
}

スケールxinterpolateNumberを使用する必要があります。 continuous.rangeRoundは使用しないでください。これはcontinuous.invertの精度を低下させ、不正確な再スケールされたドメインにつながる可能性があります。このメソッドは入力スケールxを変更しません。したがって、xは変換されていないスケールを表し、返されたスケールは変換されたビューを表します。

transform.rescaleY(y)

ソースコード · ドメインが変換された連続スケールyコピーを返します。連続スケールです。これはまず、スケールのレンジ逆y変換を適用し、次に逆スケールを適用して対応するドメインを計算することによって実装されています。

js
function rescaleY(y) {
  var range = y.range().map(transform.invertY, transform),
      domain = range.map(y.invert, y);
  return y.copy().domain(domain);
}

スケールyinterpolateNumberを使用する必要があります。 continuous.rangeRoundは使用しないでください。これはcontinuous.invertの精度を低下させ、不正確な再スケールされたドメインにつながる可能性があります。このメソッドは入力スケールyを変更しません。したがって、yは変換されていないスケールを表し、返されたスケールは変換されたビューを表します。

transform.toString()

ソースコード · この変換に対応するSVG変換を表す文字列を返します。以下のように実装されています。

js
function toString() {
  return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
}