http://www.lai18.com/content/508828.html
思維導圖的節點具有層級關係和隸屬關係,很像枝葉從樹幹伸展開來的形狀。在前面講解佈局的時候,提到有五個佈局是由層級佈局擴展來的,其中的樹狀圖(tree layout)和集羣圖(cluster layout)佈局製作出來的圖具有“樹形”。因此,可以憑藉這兩種佈局來製作思維導圖。
1. 構造思路
樹狀圖佈局,將一個具有層級關係的對象root轉換成節點數組nodes時,情況如下。有一個root對象:- {
- name: "node1",
- children:
- [
- { name: "node2" },
- { name: "node3" }
- ]
- }
- [
- {
- name: "node1",
- children:
- [
- { name: "node2" },
- { name: "node3" }
- ]
- },
- { name: "node2" },
- { name: "node3" }
- ]
問題是:怎樣製作一個“開關”,使得點擊樹狀圖中的某個節點時,樹狀圖更新並顯示出被點擊節點的子節點。
我們知道,樹狀圖的層級關係是由每一個對象的children屬性決定的(當然,也可以通過tree.children()修改這一點),也就是說,如果某一個節點的children值爲空,則再次用佈局計算時,其子節點就不會進入節點數組nodes了。例如,將root改爲:
- {
- name: "node1",
- children: null
- }
- {
- name: "node1",
- children: null
- _children: /* 臨時變量 */
- [
- { name: "node2" },
- { name: "node3" }
- ]
- }
- //切換開關,d 爲被點擊的節點
- function toggle(d){
- if(d.children){
- //如果有子節點
- d._children = d.children; //將該子節點保存到 _children
- d.children = null; //將子節點設置爲null
- }else{
- //如果沒有子節點
- d.children = d._children; //從 _children 取回原來的子節點
- d._children = null; //將 _children 設置爲 null
- }
- }
- //重繪函數
- function redraw(source){
- //重新計算節點和連線
- var nodes = tree.nodes(root);
- var links = tree.links(nodes);
- //獲取節點的update部分
- var nodeUpdate = svg.selectAll(".node")
- .data(nodes, function(d){ return d.name; });
- //獲取節點的enter部分
- var nodeEnter = nodeUpdate.enter();
- //在給enter部分添加新的節點時,添加監聽器,應用開關切換函數
- nodeEnter.append("g")
- .on("click", function(d) {
- toggle(d);
- redraw(d);
- });
- /***************
- 省略
- ***************/
- }
2. 製作思維導圖
首先,要有一個具有層級關係的 JSON 文件,本文使用:learn.json其次,依次創建樹狀圖佈局、對角線生成器等,用於繪製樹狀圖。
然後,實現最關鍵的重繪函數,函數聲明如下:
- function redraw(source)
2.1 調用佈局,計算節點和連線數組
樹狀圖佈局的tree.nodes()返回節點數組,tree.links()返回連線數組。其中,對節點的y座標重新計算,使其只與節點的深度有關,由於後期繪製節點和連線時要將x和y座標對調,因此這裏重計算的實際上是水平方向的座標。- //應用佈局,計算節點和連線
- var nodes = tree.nodes(root);
- var links = tree.links(nodes);
- //重新計算節點的y座標
- nodes.forEach(function(d) { d.y = d.depth * 180; });
2.2 分別處理節點的update、enter、exit三部分
在svg裏選擇當前所有的節點,使其與節點數組nodes綁定,綁定時要設定一個鍵函數。鍵函數裏直接返回d.name,當節點數組發生更新時,新節點要與舊節點在名稱上相對應。- //獲取節點的update部分
- var nodeUpdate = svg.selectAll(".node")
- .data(nodes, function(d){ return d.name; });
- //獲取節點的enter部分
- var nodeEnter = nodeUpdate.enter();
- //獲取節點的exit部分
- var nodeExit = nodeUpdate.exit();
本例中,每一個新添加的節點都將緩慢地過渡到自己本身的位置,如此更具有友好性。因此,新節點的初始位置都設定在source節點處,確切的說是重回之前source節點的位置,該座標是保存在source.x0和source.y0裏的。另外,對於每一個新節點,設置的半徑爲0,設置爲完全透明,接下來在處理update部分的時候會將這些新節點過渡到正常狀態的。下圖展示了處理enter部分和update部分時如何節點的位置時如何確定和過渡的。
處理enter部分的代碼如下。
- //1. 節點的 Enter 部分的處理辦法
- var enterNodes = nodeEnter.append("g")
- .attr("class","node")
- .attr("transform", function(d) {
- return "translate(" + source.y0 + "," + source.x0 + ")";
- })
- .on("click", function(d) {
- toggle(d);
- redraw(d);
- });
- //省略添加圓和文字部分
- //2. 節點的 Update 部分的處理辦法
- var updateNodes = nodeUpdate.transition()
- .duration(500)
- .attr("transform", function(d) {
- return "translate(" + d.y + "," + d.x + ")";
- });
- //3. 節點的 Exit 部分的處理辦法
- var exitNodes = nodeExit.transition()
- .duration(500)
- .attr("transform", function(d) {
- return "translate(" + source.y + "," + source.x + ")";
- })
- .remove();
2.3 分別處理連線的update、enter、exit三部分
在svg中選擇所有的連線,綁定連線數組links,由此可獲得連線的update、enter、exit部分。- //獲取連線的update部分
- var linkUpdate = svg.selectAll(".link")
- .data(links, function(d){ return d.target.name; });
- //獲取連線的enter部分
- var linkEnter = linkUpdate.enter();
- //獲取連線的exit部分
- var linkExit = linkUpdate.exit();
對於連線的update部分,將所有的連線的位置(對角線的起點和終點)更新到新的位置,即目前綁定的數組links裏保存的位置。
對於連線的exit部分,令其緩緩過渡到當前的source點,再移除。
- //1. 連線的 Enter 部分的處理辦法
- linkEnter.insert("path",".node")
- .attr("class", "link")
- .attr("d", function(d) {
- var o = {x: source.x0, y: source.y0};
- return diagonal({source: o, target: o});
- })
- .transition()
- .duration(500)
- .attr("d", diagonal);
- //2. 連線的 Update 部分的處理辦法
- linkUpdate.transition()
- .duration(500)
- .attr("d", diagonal);
- //3. 連線的 Exit 部分的處理辦法
- linkExit.transition()
- .duration(500)
- .attr("d", function(d) {
- var o = {x: source.x, y: source.y};
- return diagonal({source: o, target: o});
- })
- .remove();
2.4 保存當前的節點座標
當用戶點擊節點後,數據發生更新,即每個節點的座標要發生更新。但是,在對節點和連線進行過渡操作的時候,需要使用到更新前的數據(source.x0和source.y0)。因此,每一次調用重繪函數,都要將當前節點的位置保存下來。- nodes.forEach(function(d) {
- d.x0 = d.x;
- d.y0 = d.y;
- });
3. 結果
結果如下圖所示,點擊節點可以展開子節點。源代碼請單擊以下鏈接,郵件查看源代碼:
http://www.ourd3js.com/demo/G-10.0/mind.html謝謝閱讀。
文檔信息
版權聲明:署名(BY)-非商業性(NC)-禁止演繹(ND)發表日期:2015 年 6 月 27 日
更多內容:OUR D3.JS - 數據可視化專題站 和 CSDN個人博客
備註:本文發表於 OUR D3.JS ,轉載請註明出處,謝謝