跳到內容

d3-zoom

範例 · 縮放和平移 讓使用者透過限制檢視範圍來關注感興趣的區域。它使用直接操作:按一下並拖曳來平移(轉換),旋轉滾輪來縮放(縮放),或用觸控縮放。縮放和平移廣泛用於網頁地圖,但也可以用於密集時間序列和散佈圖等視覺化。

縮放行為是一種靈活的抽象,處理各種輸入模式和瀏覽器怪癖。縮放行為與 DOM 無關,因此你可以將其與 HTML、SVG畫布 搭配使用。你可以將 d3-zoom 與 d3-scaled3-axis 搭配使用,以縮放座標軸。你可以使用 zoom.scaleExtent 來限制縮放,並使用 zoom.translateExtent 來限制平移。你可以將 d3-zoom 與其他行為結合使用,例如 d3-drag 用於拖曳,以及 d3-brush 用於焦點 + 背景

縮放行為可以使用 zoom.transform 以程式方式控制,讓你能夠實作驅動顯示或分階段處理動畫導覽的使用者介面控制項,以瀏覽你的資料。 平滑縮放轉場基於 Jarke J. van Wijk 和 Wim A.A. Nuij 的“平滑且高效的縮放和平移”

另請參閱 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,則預設為視窗 範圍 的中心。

轉換可以指定為縮放轉換或傳回縮放轉換的函數;類似地,可以指定為二元素陣列 [x, y] 或傳回此類陣列的函數。如果為函數,則會針對每個選取的元素呼叫它,傳遞目前的事件 (event) 和資料 d,其中 this 的內容為目前的 DOM 元素。

此函數通常不會直接呼叫,而是透過選取.call轉換.call呼叫。例如,要立即將縮放轉換重設為單位轉換

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

要平順地將縮放轉換在 750 毫秒內重設為單位轉換

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

此方法需要您完整指定新的縮放轉換,而且不會強制套用已定義的縮放範圍平移範圍(如果有的話)。若要從現有轉換衍生新的轉換,並強制套用縮放和平移範圍,請參閱便利方法縮放.translateBy縮放.scaleBy縮放.scaleTo

縮放.translateBy(選取, x, y)

來源 · 如果選取是選取,則平移選取元素的目前縮放轉換xy,使得新的tx1 = tx0 + kxty1 = ty0 + ky。如果選取是轉換,則定義一個「縮放」補間,平移目前的轉換。此方法是縮放.transform的便利方法。xy平移量可以指定為數字或傳回數字的函數。如果為函數,則會針對每個選取的元素呼叫它,傳遞目前的資料 d和索引 i,其中 this 的內容為目前的 DOM 元素。

縮放.translateTo(選取, x, y, p)

來源 · 如果選取是選取,則平移選取元素的目前縮放轉換,使得給定的位置⟨x,y⟩出現在給定的點p。新的tx = px - kxty = py - ky。如果未指定p,則預設為視窗範圍的中心。如果選取是轉換,則定義一個「縮放」補間,平移目前的轉換。此方法是縮放.transform的便利方法。xy座標可以指定為數字或傳回數字的函數;類似地,p點可以指定為二元素陣列 [px,py] 或函數。如果為函數,則會針對每個選取的元素呼叫它,傳遞目前的資料 d和索引 i,其中 this 的內容為目前的 DOM 元素。

zoom.scaleBy(selection, k, p)

原始碼 · 如果 selection 是選取,縮放 選取元素的目前縮放轉換k,因此新的 k₁ = k₀k。參考點 p 會移動。如果未指定 p,它會預設為視窗範圍的中心。如果 selection 是轉場,定義一個「縮放」補間,轉換目前的轉換。這個方法是 zoom.transform 的便利方法。k 縮放因子可以指定為數字或傳回數字的函式;類似地,p 點可以指定為二元素陣列 [px,py] 或函式。如果為函式,它會為每個選取元素呼叫,傳遞目前資料 d 和索引 i,其中 this 的內容為目前的 DOM 元素。

zoom.scaleTo(selection, k, p)

原始碼 · 如果 selection 是選取,縮放 選取元素的目前縮放轉換k,因此新的 k₁ = k。參考點 p 會移動。如果未指定 p,它會預設為視窗範圍的中心。如果 selection 是轉場,定義一個「縮放」補間,轉換目前的轉換。這個方法是 zoom.transform 的便利方法。k 縮放因子可以指定為數字或傳回數字的函式;類似地,p 點可以指定為二元素陣列 [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]#zoomTransform),給定目前的 transform視窗範圍平移範圍。預設實作嘗試確保視窗範圍不會超出平移範圍。

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 時縮放因子會加倍,Δ 為 -1 時縮放因子會減半。

zoom.extent(extent)

原始碼 · 如果指定了 extent,則將視窗範圍設定為指定的點陣列 [[x0, y0], [x1, y1]],其中 [x0, y0] 是視窗的左上角,而 [x1, y1] 是視窗的右下角,並傳回此縮放行為。extent 也可指定為傳回此類陣列的函數;如果是函數,則會針對每個選取的元素呼叫它,並傳遞目前的資料 d,其中 this 的內容為目前的 DOM 元素。

如果未指定 extent,則傳回目前的範圍存取器,其預設值為 [[0, 0], [width, height]],其中 width 是元素的 用戶端寬度,而 height 是其 用戶端高度;對於 SVG 元素,則使用最近的祖先 SVG 元素的 viewBox,或 widthheight 屬性。或者,考慮使用 element.getBoundingClientRect

視窗範圍會影響數個函數:視窗中心在 zoom.scaleByzoom.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)

Source · 如果指定 extent,則將平移範圍設定為指定點陣列 [[x0, y0], [x1, y1]],其中 [x0, y0] 是世界左上角,而 [x1, y1] 是世界右下角,並傳回此縮放行為。如果未指定 extent,則傳回目前的平移範圍,其預設值為 [[-∞, -∞], [+∞, +∞]]。平移範圍會限制平移,且可能會導致縮小時平移。它在互動和使用 zoom.scaleByzoom.scaleTozoom.translateBy 時強制執行;但是,在使用 zoom.transform 明確設定轉換時,不會強制執行。

zoom.clickDistance(distance)

Source · 如果指定 distance,則設定滑鼠在 mousedown 和 mouseup 之間移動的最大距離,這將觸發後續的 click 事件。如果在 mousedown 和 mouseup 之間的任何時間點,滑鼠與其在 mousedown 上的位置距離大於或等於 distance,則會抑制 mouseup 之後的 click 事件。如果未指定 distance,則傳回目前的距離閾值,其預設值為零。距離閾值以客戶端座標測量 (event.clientXevent.clientY).

zoom.tapDistance(distance)

Source · 如果指定 distance,則設定雙點輕觸手勢在第一次 touchstart 和第二次 touchend 之間移動的最大距離,這將觸發後續的雙擊事件。如果未指定 distance,則傳回目前的距離閾值,其預設值為 10。距離閾值以客戶端座標測量 (event.clientXevent.clientY).

zoom.duration(duration)

原始碼 · 如果指定了 duration,則將雙擊和雙點觸控的縮放轉場時間設定為指定的毫秒數,並傳回縮放行為。如果未指定 duration,則傳回目前的持續時間,預設為 250 毫秒。如果持續時間不大於零,則雙擊和雙點觸控會觸發縮放轉換的即時變更,而不是啟動平滑轉場。

若要停用雙擊和雙點觸控轉場,您可以在將縮放行為套用至選取範圍後,移除縮放行為的 dblclick 事件監聽器

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

zoom.interpolate(interpolate)

原始碼 · 如果指定了 interpolate,則將縮放轉場的內插工廠設定為指定的函式。如果未指定 interpolate,則傳回目前的內插工廠,預設為 interpolateZoom 以實作平滑縮放。若要套用兩個檢視之間的直接內插,請改用 interpolate

zoom.on(typenames, listener)

原始碼 · 如果指定了 listener,則設定指定 typenames 的事件 listener,並傳回縮放行為。如果已為相同的類型和名稱註冊事件監聽器,則在新增新的監聽器之前會移除現有的監聽器。如果 listener 為 null,則移除指定 typenames 的目前事件監聽器(如果有的話)。如果未指定 listener,則傳回目前指定 typenames 所配對的第一個目前已指派監聽器(如果有的話)。當指定的事件被觸發時,每個 listener 都會使用與 selection.on 監聽器相同的內容和引數來呼叫:目前的事件 (event) 和資料 d,其中 this 內容為目前的 DOM 元素。

typenames 是包含一個或多個 typename 的字串,各 typename 以空白分隔。每個 typename 都是一個 type,後方可選擇加上一個句點 (.) 和一個 name,例如 zoom.foozoom.bar;名稱允許為相同的 type 註冊多個監聽器。type 必須是下列其中之一

  • start - 縮放開始後(例如在 mousedown 時)。
  • zoom - 縮放轉換變更後(例如在 mousemove 時)。
  • end - 縮放結束後(例如在 mouseup 時)。

請參閱 dispatch.on 以取得更多資訊。

縮放事件

當呼叫 縮放事件監聽器 時,它會接收目前的縮放事件作為第一個參數。事件物件會顯示多個欄位

  • event.target - 關聯的 縮放行為
  • event.type - 字串“start”、“zoom”或“end”;請參閱 zoom.on
  • event.transform - 目前的 縮放轉換
  • event.sourceEvent - 基礎輸入事件,例如 mousemove 或 touchmove。

縮放行為會處理各種互動事件

事件監聽元素縮放事件預設已防止?
mousedown⁵選取開始否¹
mousemove²視窗¹縮放
mouseup²視窗¹結束
dragstart²視窗-
selectstart²視窗-
按一下視窗-
按兩下選取多重
滾輪⁸選取縮放⁷
touchstart選取多重否⁴
touchmove選取縮放
touchend選取結束否⁴
touchcancel選取結束否⁴

所有已使用的事件傳播都會 立即停止

¹ 必須擷取 iframe 外部的事件;請參閱 d3-drag#9
² 僅適用於活動中的滑鼠手勢;請參閱 d3-drag#9
³ 僅適用於某些滑鼠手勢後;請參閱 zoom.clickDistance
⁴ 必須允許 在觸控輸入上模擬按一下;請參閱 d3-drag#9
⁵ 如果在觸控手勢結束後 500 毫秒內,則會忽略;假設 模擬按一下
⁶ 按兩下和輕觸兩下會啟動發出開始、縮放和結束事件的轉換;請參閱 zoom.tapDistance
⁷ 第一個滾輪事件會發出開始事件;如果在 150 毫秒內未收到任何滾輪事件,則會發出結束事件。
⁸ 如果已達到 縮放範圍 的對應限制,則會忽略。

zoomTransform(node)

來源 · 傳回指定 node 的目前轉換。請注意,node 通常應該是 DOM 元素,而不是 選取。(選取可能包含多個處於不同狀態的節點,而此函式只會傳回單一轉換。)如果您有選取,請先呼叫 選取.node

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

事件監聽器 的背景下,節點 通常是接收輸入事件的元素(應等於 事件.transform),

js
var transform = d3.zoomTransform(this);

在內部,元素的變換儲存在 element.__zoom;但是,您應該使用此方法,而不是直接存取它。如果給定的 節點 沒有定義變換,則傳回最近祖先的變換,或者如果沒有,則傳回 恆等變換。傳回的變換表示形式為

k 0 tx
0 k ty
0 0 1

的二維 變換矩陣(此矩陣只能表示縮放和平移;未來的版本也可能允許旋轉,儘管這可能不是向後相容的變更)。位置 ⟨x,y⟩ 變換為 ⟨xk + tx,yk + ty⟩。變換物件公開以下屬性

  • transform.x - 沿著 x 軸的平移量 tx
  • transform.y - 沿著 y 軸的平移量 ty
  • transform.k - 縮放因子 k

這些屬性應被視為唯讀;不要變異變換,而是使用 transform.scaletransform.translate 來衍生新的變換。另請參閱 zoom.scaleByzoom.scaleTozoom.translateBy 以取得縮放行為的便利方法。若要建立具有給定 ktxty 的變換

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

若要將變換套用至 Canvas 2D 背景,請使用 背景.translate,然後使用 背景.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₀ 為此轉換的縮放比例。

transform.translate(x, y)

來源 · 傳回平移為 tx1ty1 的轉換,其中 tx0ty0 為此轉換的平移,而 tk 為此轉換的縮放比例。

transform.apply(point)

來源 · 傳回指定 point 的轉換,point 為包含兩個數字元素 [x, y] 的陣列。傳回的點等於 [xk + tx, yk + ty]。

transform.applyX(x)

來源 · 傳回指定 x 座標的轉換,xk + tx

transform.applyY(y)

來源 · 傳回指定 y 座標的轉換,yk + ty

transform.invert(point)

來源 · 傳回指定 point 的反轉換,point 為包含兩個數字元素 [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);
}

比例尺 x 必須使用 interpolateNumber;請勿使用 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);
}

比例尺 y 必須使用 interpolateNumber;請勿使用 continuous.rangeRound,因為這會降低 continuous.invert 的準確度,並可能導致不準確的重新調整比例網域。此方法不會修改輸入比例尺 y;因此 y 代表未轉換的比例尺,而傳回的比例尺代表其轉換的檢視。

transform.toString()

原始碼 · 傳回一個字串,代表對應於此轉換的 SVG 轉換。實作如下

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