前面有稍微介紹一下d3.js的特性,這邊直接以主題開場,在做複雜資料視覺化的時候,資料常常都是巢狀的,這邊分享一下如何用d3.js處理巢狀資料。
何謂巢狀資料呢?
const data=[{ name: '1', subdata: [1,2,3] },{ name: '2', subdata: [1,2,3] }]
而我們想要根據在data中的array來添加視覺化的element.
要解決這類問題,要先理解D3一些基本運作原理,其中以d3.select的運作和其如何跟data鏈結的最為重要,建議可以閱讀下面的文章:
How Selection Work
D3開發者mike bostock對於selection的解釋
D3 – 深入了解Selection與Data綁定
這篇寫的頗棒,奠基在mike bostrock的說明文章,添加自己的想法。
以及下面這篇stackover的回答:
loop through array of arrays in d3
第一步:
d3核心觀念就是data-driven document,必須先有巢狀的文件結構,來對應資料(你如何設計你的資料結構,會影響到最終的視覺化呈現)。這邊以SVG為例,除了svg,g以外,像是rect,circle,path 都無法直接在其內包含其他要件。
第二步
對應上面的資料範例,可以先建構或試想所對應的文件結構,如下:
第三步
使用d3.selection和d3.collection來做對應資料和文件的關係,最好直接閱讀d3文檔,因為在不同版本間,d3有許多的改變,如d3v3和d3v4兩個版本間的selection, data join, index以及data manipulation就有很大的差異,如今在2018/02又發布了d3v5,可以想見d3開發和修改的速度有多快。
這邊一步步來用代碼說明解決方式。
首先,先建立一個基於svg要件的select object。
const svg = d3.select('body') .append('svg') .attr('width', 1000) .attr('height', 1000);
如此,我們便取得了svg物件,其為d3.select物件
const layer1 = svg.selectAll('g.layer1') .data(data) .enter() .append('g') .classed('layer1', true);
由上面的代碼,我們取得layer1物件,其基於在前面建立的svg物件上,使用selectAll來建立第一層的選擇,皆則把資料用data()放進去,append g 要件,根據目前data內的物件數,會產生兩個。
const layer2 = layer1.selectAll('g.layer2') .data((d,i,j)=>{ let num = d3.entries(j)[i].key return d.data.map((d)=> [d,num]) }) .enter() .append('g') .classed('layer2', true);
我們取得layer2物件,在layer1上接者chain使用selectAll,來將data物件中的subdata陣列暴露出來,並使用d3.entries將parent index往下遞出。
layer2.append('rect') .attr('x', (d,i)=> i*60) .attr('y', (d,i)=> d[1]*60) .attr('width', 50) .attr('height', 50) .attr('fill', 'blue');
如此我們便能在layer2物件中,根據subdata來對應相對的視覺化要件(element)如rect,同時,保有每個資料element相對於data內的index和自己陣列中的index,如此便能做出具有巢狀結構的視覺化。
最後就能創造出如下的效果
這邊是放在observable的範例,歡迎一起切磋巢狀資料的處理!