iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Modern Web

三十天成為D3.js v7 好手系列 第 15

Day15-D3 的 Zoom 縮放

本篇大綱:d3.zoom( )、zoom 旗下的API、範例

上一篇看完讓人燒腦的Force之後,我們這次來看看 D3 另一個有趣的特效:Zoom 縮放~它算是圖表中蠻常見的功能,有時候圖表的資料太多或是太密集,就能透過縮放功能讓使用者能更方便的瀏覽圖表並找到想要的資訊。這個功能雖然比 Froce 簡單 (我覺得啦),但它有不少細節跟參數需要設定,今天就來仔細的看一下吧!

d3.zoom( )

當我們使用 D3 設定縮放功能時,它其實同時包含了平移縮放兩種功能,我們能使用滑鼠滾輪或手指觸控的方式在畫面上進行相對應的操作。由於 Zoom 會跟多種原生 DOM 事件相呼應,官方文件也列出每個原生的事件會進行哪種Zoom 的操作

原生事件 zoom 事件
mousedown (滑鼠點擊開始) start
mousemove (滑鼠移動) zoom
mouseup (滑鼠放開) end
dblclick (滑鼠雙擊) 放大
wheel (滾輪) zoom
touchstart (觸控開始) 放大
touchmove (觸控移動) zoom
touchend (觸控結束) end

了解哪些動作會進行 zoom 對應的行為後,我們來看看官方Zoom文件提供的許多 API 能夠對 zoom 進行哪些縮放相關設定:

d3.zoom( )

從官方文件得知,當我們使用 d3.zoom( )時,它會建立一個縮放的行為並回傳一個 zoom 方法(也是物件),接著我們要用 selection.call( ) 去呼叫這個方法並綁訂到 DOM 元素上
https://ithelp.ithome.com.tw/upload/images/20210927/20134930DiSlbc1zNu.jpg

建立好zoom之後,D3也提供了非常多 zoom 旗下的API 以供我們調整縮放的細節
https://ithelp.ithome.com.tw/upload/images/20210927/20134930Z9Qta3FEum.jpg

有需要的人可以上官網看每個API的詳細設定,我們這邊就先挑比較常用的幾個講解

zoom.transform( )、zoom.translateBy( )、zoom.translateTo( )

這幾個方法主要是是用來調整 zoom 的位置。當我們使用 d3.zoom 建立一個 zoom 行為時,zoom 行為的相關狀態會被儲存到 DOM 元素上,而這個相關的狀態可以透過兩種方式改變

  1. 使用者的互動行為
  2. zoom.transform 方法

之後透過 .translateBy( ),可以調整 transform 的 x, y 值;透過 .tramslateTo( ) 則可以將 tramsform 置於正中間

zoom.extent([x0, y0], [x1, y1])

這個API 是用來設定viewport的範圍,[x0, y0] 代表svg 左上方的起點位置,[x1, y1] 則代表終點的座標,藉由這個方法就能把縮放的畫布限縮在某個範圍的viewport 中

const zoom = d3.zoom()
                  .extent([[0, 0], [250, 250]])

zoom.scaleExtent([k0, k1])

設定縮放係數的大小範圍,k0 代表縮放的最小值,k1則是縮放的最大值,縮放的比例則會被限制在最小值跟最大值中間,預設是 [0, ∞]。如果兩個值都設定為1的話,就代表不能放大或縮小

zoom.duration( )

這個 API 是用來設定滑鼠雙擊/觸控雙擊時,zoom縮放的變換時長,如果沒有特別設定的話預設是250毫秒。

zoom.on( )

主要是用來監聽縮放事件的方法。如果想要取消某些原生事件,讓它不要與縮放事件呼應,也可以用 zoom.on( ) 把事件設定為null,例如:

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

介紹完這幾個 Zoom 相關的設定後,我們實際來寫一些範例吧!

範例一:Zoom 基礎範例

我們先來做一個最基礎的圓點縮放範例。首先,先建立一個svg畫面,並加上一個圓點點

// html
<div class="zoom1"></div>

// js

const svg = d3.select('.zoom1')
              .append('svg')
              .attr('width', width)
              .attr('height', height)

// 加個圓點點
const circle = svg.append('circle')
                  .attr("id", "dot")
                  .attr("cx", 150)
                  .attr("cy", 150)
                  .attr("r", 40)
                  .attr("fill", "#69b3a2");

接著,我們來建立縮放事件

// Zoom 事件
const zoom = d3.zoom()
              // 用 on 來監聽縮放事件啟動後,要進行什麼動作
              .on('zoom', function(event) {
                //這邊決定要放大誰,我想放大circle
                circle.attr('transform', event.transform);
	            });

// 用 selection.call 呼叫 zoom 方法
svg.call(zoom)

這樣就完成啦!!
https://i.imgur.com/Wbg74hO.gif

如果我們想要額外進行一些細節設定,也可以使用上面介紹的API來調整

// Zoom 事件
const zoom = d3.zoom()
							// 限定viewport視窗範圍
              .extent([[0, 0], [250, 250]])
							// 限制縮放大小範圍
              .scaleExtent([0, 5])
							// 設定雙擊的動畫時長
              .duration(600)
              .on('zoom', function(event) {
                console.log(event)
                circle.attr('transform', event.transform);
              });

svg.call(zoom)
		// 設定滑鼠滾輪滾動時,不要進行縮放
    .on("wheel.zoom", null);

範例二:Input 調整 zoom 縮放比例

接著我們來看另一個有趣的例子,這個其實是我朋友接到的一個客戶需求,客戶的需求是:

  1. 希望能自己輸入想要的縮放大小
  2. 希望有個reset功能,能回覆到圖表原本的樣子

為了滿足這兩個條件,我們要有一個 reset button,接著再給一個 input,讓客戶能自己輸入想要的縮放數值。好,說幹就幹!

我們用範例一建立的單純圓點來示範,一樣先建立好圓點

//html 
<div class="zoom2"></div>

// js
const width = 500, height = 400
const svg = d3.select('.zoom2')
              .append('svg')
              .attr('width', width)
              .attr('height', height)

// 加個圓點點
const circle = svg.append('circle')
                  .attr("id", "dot")
                  .attr("cx", 150)
                  .attr("cy", 150)
                  .attr("r", 40)
                  .attr("fill", "#69b3a2");

// Zoom 事件
const zoom = d3.zoom()
              .on('zoom', function(event) {
                circle.attr('transform', event.transform); 
              });
              
svg.call(zoom);

接著加上 reset button,並設定reset button 點擊時要觸發的動作:用 d3.zoomIdentity.scale(1) 把畫面回歸到一開始設定的大小

// 加上reset button 
    const resetBtn = d3.select('.zoom2')
                        .append('div')
                        .append('button')
                        .attr('class', 'btn btn-primary')
                        .attr('id', 'reset')
                        .text('reset');
    
    // reset button 觸發
    resetBtn.on('click', function(){
      const transform = d3.zoomIdentity.scale(1)
      svg.call(zoom.transform, transform);
    });

這樣 reset 的功能就完成了~
https://i.imgur.com/FTJcIdL.gif

接著我們來做輸入縮放數值的功能吧,先在畫面上建立 input 跟按鈕

// input 輸入 Zoom 的大小
const div = d3.select('.zoom2')
              .append('div')
              .attr('class', 'mt-3')
                         
const zoomScaleInput = div.append('input')
                          .attr('id', 'zoomscale')
const zoomScaleBtn = div.append('button')
                       .attr('class', 'btn btn-primary ms-2')
                       .text('執行');

接著設定按鈕點擊時,要抓取 input 的數值套用到 d3.zoomIdentity.scale上,最後再呼叫 zoom.transform

// 設定取 value 的值,套用 zoom 的倍數
zoomScaleBtn.on('click', function(){
  const scaleValue = document.getElementById('zoomscale').value;
  const transform = d3.zoomIdentity.scale(`${scaleValue}`)
  svg.call(zoom.transform, transform);
});

這樣就完成啦!不過這個方式有個缺點: d3.zoomIdentity.scale 只能填入大於1的數值。這部分應該還有可以優化的地方,如果大家有想到其他更好的方法,歡迎給我一些建議~~
https://i.imgur.com/Tkwqh2I.gif


好啦,D3 的縮放功能就先講到這邊,等之後結合圖表去使用縮放功能,會更加有趣的~~

Github Page 圖表與 Github 程式碼

這邊附上本章的程式碼與圖表 GithubGithub Page,需要的人請自行取用~


上一篇
Day14-D3 的 Force 原力
下一篇
Day16-D3 的 Brush 刷子
系列文
三十天成為D3.js v7 好手30

尚未有邦友留言

立即登入留言