點(diǎn) API 詳解:層級(jí)數(shù)據(jù)的表示、遍歷與操作)
D3 7 中 d3.hierarchy 根節(jié)點(diǎn) API 詳解層級(jí)數(shù)據(jù)的表示、遍歷與操作【免費(fèi)下載鏈接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:項(xiàng)目地址: https://gitcode.com/GitHub_Trending/d3/d3本文基于 d3 倉(cāng)庫(kù)的官方文檔 hierarchy 展開系統(tǒng)講解d3.hierarchy根節(jié)點(diǎn)的構(gòu)造方式、六個(gè)核心屬性、遍歷/查找/排序/求值全套方法ancestors、descendants、find、path、links、sum、sort、each系列等。讀完本文你可以將 JSON 或分組Map數(shù)據(jù)轉(zhuǎn)成標(biāo)準(zhǔn)層級(jí)樹并正確地把node.value與排序結(jié)果喂給 treemap、tree、pack 等層級(jí)布局。一、hierarchy API 在 d3-hierarchy 模塊中的定位很多數(shù)據(jù)天然是層級(jí)結(jié)構(gòu)的行政區(qū)劃、組織架構(gòu)、文件系統(tǒng)、軟件包d3-hierarchy 模塊為此提供了幾類經(jīng)典可視化節(jié)點(diǎn)連線圖tree、cluster 樹狀圖、鄰接圖partition 冰柱圖、包圍圖treemap、pack 圓形嵌套詳見模塊總覽 d3-hierarchy。所有布局的輸入都不是原始 JSON而是一個(gè)根節(jié)點(diǎn)root node。要得到根節(jié)點(diǎn)有兩條路數(shù)據(jù)已經(jīng)是 JSON 等層級(jí)格式直接傳給d3.hierarchy(data)數(shù)據(jù)是 CSV 等扁平表格先用 stratify 把「name,parent」兩列重組為層級(jí)再交給布局。本文聚焦前者即d3.hierarchy本身。在 d3 7 中該 API 通過匯總包統(tǒng)一導(dǎo)出src/index.js 中的export * from d3-hierarchy;讓d3.hierarchy與d3.stratify等全部可用而 package.json 聲明的依賴為d3-hierarchy: ^3.1.2d3 版本 7.9.0。二、用 d3.hierarchy(data, children) 構(gòu)造根節(jié)點(diǎn)d3.hierarchy(data[, children])從指定的層級(jí)數(shù)據(jù)構(gòu)造根節(jié)點(diǎn)要求data必須是代表根節(jié)點(diǎn)的對(duì)象。文檔給出的標(biāo)準(zhǔn)示例const data { name: Eve, children: [ {name: Cain}, {name: Seth, children: [{name: Enos}, {name: Noam}]}, {name: Abel}, {name: Awan, children: [{name: Enoch}]}, {name: Azura} ] };構(gòu)造層級(jí)const root d3.hierarchy(data);可選的children訪問函數(shù)會(huì)對(duì)每個(gè)數(shù)據(jù)項(xiàng)從根data開始調(diào)用須返回代表子節(jié)點(diǎn)的可迭代對(duì)象iterable不指定時(shí)默認(rèn)為function children(d) { return d.children; }Map 數(shù)據(jù)的隱式轉(zhuǎn)換若data是一個(gè)Map它會(huì)被隱式轉(zhuǎn)換為條目[undefined, data]同時(shí)children訪問函數(shù)的默認(rèn)值變?yōu)閒unction children(d) { return Array.isArray(d) ? d[1] : null; }這讓你可以把 group 或 rollup 的結(jié)果直接傳給d3.hierarchy——分組得到的Map鍵為分組鍵、值為該組的條目數(shù)組天然匹配「鍵 → 子數(shù)據(jù)數(shù)組」的結(jié)構(gòu)無(wú)需手工展開。此外該方法也可用于判斷節(jié)點(diǎn)是否為instanceof d3.hierarchy以及擴(kuò)展節(jié)點(diǎn)原型。節(jié)點(diǎn)屬性root 與每個(gè)子孫共有六個(gè)字段屬性含義node.data傳給d3.hierarchy的原始數(shù)據(jù)與輸入共享引用node.depth根節(jié)點(diǎn)為 0每向后代深一層加 1node.height到任意后代葉節(jié)點(diǎn)的最大距離葉節(jié)點(diǎn)為 0node.parent父節(jié)點(diǎn)根節(jié)點(diǎn)為nullnode.children子節(jié)點(diǎn)數(shù)組葉節(jié)點(diǎn)為undefinednode.value可選的聚合值為節(jié)點(diǎn)與其后代 descendants 之和注意node.data與布局寫入的x/y/r等坐標(biāo)字段并存于同一對(duì)象上d3 v4 起布局直接以這些根節(jié)點(diǎn)為輸入而不是操作原始 JSON從而把輸入數(shù)據(jù)與計(jì)算結(jié)果分離見 CHANGES.md 中 d3-hierarchy 一節(jié)的設(shè)計(jì)說明這也是后文node.copy()能單獨(dú)隔離布局變更的前提。三、祖先、后代與葉節(jié)點(diǎn)ancestors / descendants / leavesnode.ancestors()返回祖先節(jié)點(diǎn)數(shù)組從當(dāng)前節(jié)點(diǎn)開始依次向上直到根節(jié)點(diǎn)。典型用途是鼠標(biāo)懸停時(shí)高亮某節(jié)點(diǎn)的全部上級(jí)。node.descendants()返回后代節(jié)點(diǎn)數(shù)組從當(dāng)前節(jié)點(diǎn)開始按拓?fù)漤樞蚋赶扔谧优帕腥孔訉O。布局完成后調(diào)用它即可拿到帶坐標(biāo)的全部節(jié)點(diǎn)數(shù)組const nodes root.descendants();node.leaves()按遍歷順序返回葉節(jié)點(diǎn)數(shù)組。*葉節(jié)點(diǎn)leaf*指沒有 children 的節(jié)點(diǎn)。四、查找與路徑find / pathnode.find(filter)返回從當(dāng)前node出發(fā)、第一個(gè)使filter返回真值的節(jié)點(diǎn)找不到返回undefined。find是 d3-hierarchy 3.x 新增的 APICHANGES.md 記錄Addnode.find。node.path(target)返回從當(dāng)前node到指定target節(jié)點(diǎn)的最短路徑從起點(diǎn)上溯到兩者的最近公共祖先LCA再下降到target。該方法取代了 d3 v3 時(shí)代的d3.layout.bundle是層級(jí)邊緣捆綁hierarchical edge bundling的基礎(chǔ)原語(yǔ)。五、生成邊數(shù)據(jù)links()node.links()返回當(dāng)前節(jié)點(diǎn)及其全部后代的邊link數(shù)組每條邊是帶source與target屬性的對(duì)象source 為父節(jié)點(diǎn)target 為子節(jié)點(diǎn)。const links root.links(); // 每條 link{ source: 父節(jié)點(diǎn), target: 子節(jié)點(diǎn) }配合 d3-shape 的 link 生成器即可渲染節(jié)點(diǎn)連線圖。從 CHANGES.md 可見links()自 d3 v4 起取代了treemap.links等各布局私有的邊生成方法成為所有層級(jí)布局的統(tǒng)一接口。六、值聚合sum(value) 與 count()node.sum(value)以后序遍歷post-order對(duì)當(dāng)前節(jié)點(diǎn)及每個(gè)后代求值并返回當(dāng)前node。每個(gè)節(jié)點(diǎn)的node.value被設(shè)為該節(jié)點(diǎn)訪問函數(shù)的返回值 所有子節(jié)點(diǎn)value之和。訪問函數(shù)接收節(jié)點(diǎn)的data必須返回非負(fù)數(shù)。兩個(gè)關(guān)鍵細(xì)節(jié)value訪問函數(shù)會(huì)對(duì)節(jié)點(diǎn)和全部后代求值含內(nèi)部節(jié)點(diǎn)若只希望葉節(jié)點(diǎn)貢獻(xiàn)值請(qǐng)對(duì)含子節(jié)點(diǎn)的節(jié)點(diǎn)返回 0。例如作為node.count的替代按葉節(jié)點(diǎn)計(jì)數(shù)root.sum((d) d.value ? 1 : 0);必須在調(diào)用需要node.value的層級(jí)布局之前調(diào)用sum或count例如 treemap// Construct the treemap layout. const treemap d3.treemap(); treemap.size([width, height]); treemap.padding(2); // Sum and sort the data. root.sum((d) d.value); root.sort((a, b) b.height - a.height || b.value - a.value); // Compute the treemap layout. treemap(root); // Retrieve all descendant nodes. const nodes root.descendants();由于 API 支持方法鏈?zhǔn)秸{(diào)用同樣可以寫成d3.treemap() .size([width, height]) .padding(2) (root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value)) .descendants()此示例假設(shè)節(jié)點(diǎn)數(shù)據(jù)帶有value字段。node.count()統(tǒng)計(jì)當(dāng)前節(jié)點(diǎn)下的葉節(jié)點(diǎn)數(shù)量并賦給node.value其每個(gè)后代同理若當(dāng)前節(jié)點(diǎn)本身是葉節(jié)點(diǎn)計(jì)數(shù)為 1返回當(dāng)前node。與sum的關(guān)系見上文。七、重排子節(jié)點(diǎn)sort(compare)node.sort(compare)按指定compare函數(shù)對(duì)當(dāng)前節(jié)點(diǎn)及其每個(gè)后代的 children 執(zhí)行**前序遍歷pre-order**排序并返回當(dāng)前node。與sum的一個(gè)重要區(qū)別compare函數(shù)接收的是兩個(gè)節(jié)點(diǎn)a和b而不是兩個(gè)節(jié)點(diǎn)的 data。約定與Array.prototype.sort一致a應(yīng)在b之前返回負(fù)值反之返回正值否則相對(duì)順序未定義。文檔給出的三組典型用法均建議先sum按「聚合值」降序——circle-packing 的推薦排序root .sum((d) d.value) .sort((a, b) b.value - a.value);先按高度降序、再按值降序——treemap 與 icicle 圖 的推薦排序root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value);先按高度降序、再按 id 升序——tree 與 dendrogram 的推薦排序root .sum((d) d.value) .sort((a, b) b.height - a.height || d3.ascending(a.id, b.id));調(diào)用時(shí)機(jī)若希望新的排序順序影響布局必須在調(diào)用布局之前執(zhí)行node.sort。八、可迭代與三種遍歷Symbol.iterator / each / eachAfter / eachBeforenodeSymbol.iterator返回按**廣度優(yōu)先順序breadth-first order**迭代node后代的迭代器for (const descendant of node) { console.log(descendant); }這是 d3-hierarchy 3.x 新增的能力層級(jí)從此可直接for...of迭代CHANGES.md 記錄Addnode[Symbol.iterator]; hierarchies are now iterable。node.each(function, that)以廣度優(yōu)先順序?qū)ode及每個(gè)后代調(diào)用function某節(jié)點(diǎn)只有在所有更淺層節(jié)點(diǎn)及同層前序節(jié)點(diǎn)都訪問完后才被訪問。回調(diào)參數(shù)依次為當(dāng)前descendant、零基遍歷index、當(dāng)前node即調(diào)用對(duì)象指定that時(shí)作為回調(diào)的this上下文。node.eachAfter(function, that)以**后序遍歷post-order**調(diào)用節(jié)點(diǎn)在所有后代都被訪問之后才被訪問。這是sum內(nèi)部使用的遍歷方向。node.eachBefore(function, that)以**前序遍歷pre-order**調(diào)用節(jié)點(diǎn)在所有祖先都被訪問之后才被訪問sort即以前序執(zhí)行。三個(gè) each 方法的回調(diào)自 d3-hierarchy 3.x 起都會(huì)傳入遍歷 indexCHANGES.mdChangenode.each / eachAfter / eachBefore to pass the traversal index。從倉(cāng)庫(kù)歷史看CHANGES.md 4.0 一節(jié)這些非遞歸遍歷方法正是層級(jí)布局內(nèi)部實(shí)現(xiàn)的基礎(chǔ)——布局tree、treemap 等改為用它們重寫以避免遞歸在大數(shù)據(jù)集上的開銷。九、深拷貝子樹copy()node.copy()返回以當(dāng)前node為根的子樹深拷貝但拷貝共享同一份 data。返回節(jié)點(diǎn)是一棵新樹的根其parent恒為nulldepth恒為 0。典型用途是隔離布局副作用布局會(huì)把x/y/r等坐標(biāo)寫回節(jié)點(diǎn)對(duì)象若想在同一數(shù)據(jù)上嘗試不同布局而不互相污染可以先root.copy()再計(jì)算新布局CHANGES.md 4.0 一節(jié)即以此為例usenode.copy to isolate layout changes。十、實(shí)戰(zhàn)要點(diǎn)小結(jié)扁平表格數(shù)據(jù)先經(jīng) stratify配合 d3.csvParse層級(jí) JSON 直接經(jīng)d3.hierarchyMap如 group 結(jié)果可直接傳入并自動(dòng)映射。需要面積編碼的布局treemap、partition、pack要求節(jié)點(diǎn)帶value務(wù)必在布局前sum或count需要自定義順序時(shí)在其后sort且sort的 compare 比較的是節(jié)點(diǎn)而非 data。渲染時(shí)以root.descendants()取節(jié)點(diǎn)、root.links()取邊交互高亮用ancestors()邊緣捆綁用path()快速檢索用find()。遍歷策略按需選擇按層處理用eachBFS、自底向上聚合用eachAfter、自頂向下展開用eachBefore、最簡(jiǎn)寫法用for...of。同一數(shù)據(jù)嘗試多種布局時(shí)用copy()隔離坐標(biāo)副作用。進(jìn)一步閱讀d3-hierarchy 模塊總覽、stratify、tree、cluster、partition、pack、treemap?!久赓M(fèi)下載鏈接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:項(xiàng)目地址: https://gitcode.com/GitHub_Trending/d3/d3創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考