over 1 year ago

關於Selection 與 Data綁定

主要參考文章 How Selections Work,非常清楚地用文字搭配圖表展示D3 Selection運作與Data綁定的機制;以下內容節錄並意譯(非逐字翻譯)部分文章。

A Subclass of Array

你可能會認為Selection是一個包含DOM 元素的陣列,但這是錯的;Selection是Array的子類,除了保留Array原有的特性外(如 forEach,Map,Filter),還多加上了操作被選定元素的方法,例如attrstyle
然而,D3有提供原生Array更方便的替代方式,例selection.each,或是複寫原本的函式,如selection.filter或selection.sort

另外之所以說Selection並不是單純字面上的Array,主要是因為Selection是Array of Array of elements
Selection包含group陣列,而group陣列中又放置element

var selection = d3.select("body")
(selection) <--> (group) <--> (body)

如果要存取body,也可以透過selection[0][0]這種原生Array的存取方式喔;
同樣的selectAll也是包含一個group陣列

var selection = d3.selectAll("h2");
(selection) <--> (group) <--> (h2)
                          |-> (h2)
                          |-> (h2)

d3.select和d3.selectAll都確切包含一個group,如果想要包含多個group就要用多個selectAll合併,原本就有group中的elements會組成新的group,每個group都會有parentNode

d3.selectAll("tr").selectAll("td");
//第一個selectAll,此時parent為document

(selection) <--> (group) <--> (tr)
                          |-> (tr)
                          |-> (tr)
//第二個selectAll,此時parent為tr                     

(selection) <--> (group) <--> (td)
                          |-> (td)
                          |-> (td)
              |->(group) <--> (td)
                          |-> (td)
                          |-> (td)
Non-Grouping Operations

從上述得知,selectAll和select差別在於selectAll可以創造新的group,而select保留當前的group;而且在select中,舊有group中的每一個element正好對應到新group的一個元素(1 to 1的概念吧,原文為
The select method differs because there is exactly one element in the new selection for each element in the old selection),所以select可以直接將data從parent直接傳給child,而selectAll必須使用 data-jion。
另外,append和insert是從select延伸而來,所以也可以直接傳遞data。

d3.selectAll("section");
(selection) <--> (group) <--> (section)
                          |-> (section)
                          |-> (section)
//此時parentNode還是document喔

d3.selectAll("section").append("p");
(selection) <--> (group) <--> (p)
                          |-> (p)
                          |-> (p)
Null Elements

在select時難免會遇到在group中找不到element的時候,此時D3會塞入null,而null其實不太會影響運作,在style()和 attr()時會自動跳過

//假設只有一個section中包含aside

d3.selectAll("section").select("aside");
(selection) <--> (group) <--> (null)
                          |-> (null)
                          |-> (aside)
Bound to Data

有點訝異的是 Data並不屬於selection的一部分,反而是屬於element本身,D3將資料放在element底下的__data__屬性當中(初始為undefined,且通常不會直接操作該屬性);所以不管selection如何,屬element的datum並不會受到影響,也可以隨時被取得。
要將datum綁定到element上有三種方式

  1. 綁定到一群元素上:selection.data
  2. 直接賦值到特定元素上:selection.datum
  3. 從parent直接繼承:append, insert, 或是 select
//bad
document.body.__data__ = 42;
//good
d3.select("body").datum(42);
//h1的__data__一樣是42
d3.select("body").datum(42).append("h1");
What is data

在D3中的Data包含多種型態,Array、JSON Object Array、2D Array等都是。
有趣的是,在D3中 selection.data定義的事一個資料的群組而非屬於每個元素(defines data per-group rather than per-element),所以說在 selection的元素群組 <--> selection.data資料群組必須有個連結的管道,也就是pairing key
預設D3會依照陣列排序生成Key,然後依照陣列順序將Data綁定到DOM元素上,也可以透過自定義方式決定配對。
D3提供的三種配對模式為
Update - 資料有相對應的元素
Enter - 資料沒有相對應的元素
Exit - 資料有相對應的元素的刪除,保留資料沒有對應的元素
這部分可以看網站內的動畫比較好理解。

總結

一開始接觸D3,總覺得selection、group和data綁定好像很簡單,但如果沒有弄仔細就容易出現未知的錯誤(圖表就是不出來)
花點時間了解基本的內容對於操作D3就更加容易上手。

← D3初體驗-基本語法Select/Data/Axis/Scale與製作Bar Chart JS - 關於Object、Function的屬性、作用域與種種 →
 
comments powered by Disqus