日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

D3.js畫思維導(dǎo)圖(轉(zhuǎn))

 ooAAMM 2019-03-23

思維導(dǎo)圖的節(jié)點(diǎn)具有層級關(guān)系和隸屬關(guān)系,很像枝葉從樹干伸展開來的形狀。在前面講解布局的時(shí)候,提到有五個(gè)布局是由層級布局?jǐn)U展來的,其中的樹狀圖(tree layout)和集群圖(cluster layout)布局制作出來的圖具有“樹形”。因此,可以憑借這兩種布局來制作思維導(dǎo)圖。

1. 構(gòu)造思路

樹狀圖布局,將一個(gè)具有層級關(guān)系的對象root轉(zhuǎn)換成節(jié)點(diǎn)數(shù)組nodes時(shí),情況如下。有一個(gè)root對象:

{  
    name: "node1",  
    children:   
[  
            { name: "node2" },  
            { name: "node3" }  
    ]  
}  

經(jīng)樹狀圖布局轉(zhuǎn)換后,得到的節(jié)點(diǎn)數(shù)組nodes如下:

[  
{  
        name: "node1",  
        children:   
        [  
            { name: "node2" },  
            { name: "node3" }  
        ]  
    },  
    { name: "node2" },  
    { name: "node3" }  
]  

下圖是上述節(jié)點(diǎn)數(shù)組的示意圖。由于 node1 具有子節(jié)點(diǎn),可作為開關(guān)使用,點(diǎn)擊 node1 才會(huì)展現(xiàn) node2 和 node3。

問題是:怎樣制作一個(gè)“開關(guān)”,使得點(diǎn)擊樹狀圖中的某個(gè)節(jié)點(diǎn)時(shí),樹狀圖更新并顯示出被點(diǎn)擊節(jié)點(diǎn)的子節(jié)點(diǎn)。

我們知道,樹狀圖的層級關(guān)系是由每一個(gè)對象的children屬性決定的(當(dāng)然,也可以通過tree.children()修改這一點(diǎn)),也就是說,如果某一個(gè)節(jié)點(diǎn)的children值為空,則再次用布局計(jì)算時(shí),其子節(jié)點(diǎn)就不會(huì)進(jìn)入節(jié)點(diǎn)數(shù)組nodes了。例如,將root改為:

{  
    name: "node1",  
    children: null  
}  

則得到的節(jié)點(diǎn)數(shù)組nodes里將沒有node2和node3節(jié)點(diǎn)。也就是說,“開關(guān)”只要將被點(diǎn)擊節(jié)點(diǎn)的children設(shè)置為null即可。但是,由于將來可能還要用到children節(jié)點(diǎn),可設(shè)一臨時(shí)變量_children保存此值,例如:

{  
    name: "node1",  
    children: null  
       _children:       /* 臨時(shí)變量 */  
       [  
            { name: "node2" },  
            { name: "node3" }  
    ]  
  
}  

樹狀圖布局不會(huì)認(rèn)為_children是保存子節(jié)點(diǎn)的變量,只把它看做是一般的變量而保存下來,因此節(jié)點(diǎn)數(shù)組nodes里只有一個(gè)節(jié)點(diǎn)。根據(jù)上面的思路,寫一個(gè)開關(guān)切換函數(shù)如下。

//切換開關(guān),d 為被點(diǎn)擊的節(jié)點(diǎn)  
function toggle(d){  
  
if(d.children){  
//如果有子節(jié)點(diǎn)  
        d._children = d.children; //將該子節(jié)點(diǎn)保存到 _children  
        d.children = null;  //將子節(jié)點(diǎn)設(shè)置為null  
      
}else{  
//如果沒有子節(jié)點(diǎn)  
        d.children = d._children; //從 _children 取回原來的子節(jié)點(diǎn)   
        d._children = null; //將 _children 設(shè)置為 null  
    }  
}  

每次開關(guān)狀態(tài)切換時(shí),都要重新調(diào)用布局重新計(jì)算節(jié)點(diǎn)的位置,也就是說,要有一個(gè)重繪函數(shù)能夠處理數(shù)據(jù)發(fā)生更新的情況。這就又要用到【選擇集與數(shù)據(jù) - 第 5 章】的處理模板,重繪函數(shù)的部分代碼如下,尤其要注意開關(guān)函數(shù)是如何被使用的。

//重繪函數(shù)  
 function redraw(source){  
  
    //重新計(jì)算節(jié)點(diǎn)和連線  
    var nodes = tree.nodes(root);  
    var links = tree.links(nodes);  
  
//獲取節(jié)點(diǎn)的update部分  
    var nodeUpdate = svg.selectAll(".node")  
                        .data(nodes, function(d){ return d.name; });  
  
    //獲取節(jié)點(diǎn)的enter部分  
    var nodeEnter = nodeUpdate.enter();  
  
//在給enter部分添加新的節(jié)點(diǎn)時(shí),添加監(jiān)聽器,應(yīng)用開關(guān)切換函數(shù)  
nodeEnter.append("g")  
           .on("click", function(d) {   
toggle(d);   
redraw(d);   
});  
  
/*************** 
        省略 
***************/  
}  

每一個(gè)被新添加的節(jié)點(diǎn),都會(huì)響應(yīng)click事件。當(dāng)某個(gè)節(jié)點(diǎn)被點(diǎn)擊時(shí),如果它具有子節(jié)點(diǎn),則在開關(guān)切換函數(shù)的作用下,root對象被修改了,然后調(diào)用重繪函數(shù)后,新的樹狀圖將被繪制。如此一來,樹狀圖具有開關(guān)功能,也就可以當(dāng)做思維導(dǎo)圖使用了。

2. 制作思維導(dǎo)圖

首先,要有一個(gè)具有層級關(guān)系的 JSON 文件,本文使用:learn.json

{
"name":"如何學(xué)習(xí)D3",
"children":
[
    { 
      "name":"預(yù)備知識(shí)" , 
        "children":
        [
                {"name":"HTML & CSS" },
                {"name":"JavaScript" },
                {"name":"DOM" },
                {"name":"SVG" }
        ] 
      },
      
    { 
        "name":"安裝" , 
        "children":
        [
            {
                "name":"記事本軟件",
                "children":
                [
                    {"name":"Notepad++"},
                    {"name":"EditPlus"},
                    {"name":"Sublime Text"}
                ]
            },
            {
                "name":"服務(wù)器軟件",
                "children":
                [
                    {"name":"Apache Http Server"},
                    {"name":"Tomcat"}
                ]
            },
            {"name":"下載D3.js"}
        ] 
    },
    
    { 
        "name":"入門",
        "children":
        [
            {
                "name":"選擇集",
                "children":
                [
                    {"name":"select"},
                    {"name":"selectAll"}
                ]
            },
            {
                "name":"綁定數(shù)據(jù)",
                "children":
                [
                    {"name":"datum"},
                    {"name":"data"}
                ]
            },
            {"name":"添加刪除元素"},
            {
                "name":"簡單圖形",
                "children":
                [
                    {"name":"柱形圖"},
                    {"name":"折線圖"},
                    {"name":"散點(diǎn)圖"}
                ]
            },
            {"name":"比例尺"},
            {"name":"生成器"},
            {"name":"過渡"}
        ] 
    },
    
    { 
        "name":"進(jìn)階" , 
        "children":
        [
            {
                "name":"布局的應(yīng)用",
                "children":
                [
                    {"name":"餅狀圖"},
                    {"name":"樹狀圖"},
                    {"name":"矩陣樹圖"}
                ]
            },
            {"name":"地圖"}
        ]
    }
]
}

其次,依次創(chuàng)建樹狀圖布局、對角線生成器等,用于繪制樹狀圖。

然后,實(shí)現(xiàn)最關(guān)鍵的重繪函數(shù),函數(shù)聲明如下:

function redraw(source)

只有一個(gè)參數(shù)source,這是被點(diǎn)擊的節(jié)點(diǎn),如果該節(jié)點(diǎn)原來為閉合狀態(tài),點(diǎn)擊后其子節(jié)點(diǎn)將顯現(xiàn),如果原來為打開狀態(tài),點(diǎn)擊后其子節(jié)點(diǎn)將隱藏。函數(shù)體的實(shí)現(xiàn),分為四個(gè)步驟:

2.1 調(diào)用布局,計(jì)算節(jié)點(diǎn)和連線數(shù)組

樹狀圖布局的tree.nodes()返回節(jié)點(diǎn)數(shù)組,tree.links()返回連線數(shù)組。其中,對節(jié)點(diǎn)的y坐標(biāo)重新計(jì)算,使其只與節(jié)點(diǎn)的深度有關(guān),由于后期繪制節(jié)點(diǎn)和連線時(shí)要將x和y坐標(biāo)對調(diào),因此這里重計(jì)算的實(shí)際上是水平方向的坐標(biāo)。

//應(yīng)用布局,計(jì)算節(jié)點(diǎn)和連線  
var nodes = tree.nodes(root);  
var links = tree.links(nodes);  
  
//重新計(jì)算節(jié)點(diǎn)的y坐標(biāo)  
nodes.forEach(function(d) { d.y = d.depth * 180; });

之所以重新計(jì)算y坐標(biāo),是為了當(dāng)數(shù)據(jù)更新(用于點(diǎn)擊節(jié)點(diǎn))時(shí),保證樹狀圖的結(jié)構(gòu)不要發(fā)生太大的變化,如此看起來比較自然。

2.2 分別處理節(jié)點(diǎn)的update、enter、exit三部分

在svg里選擇當(dāng)前所有的節(jié)點(diǎn),使其與節(jié)點(diǎn)數(shù)組nodes綁定,綁定時(shí)要設(shè)定一個(gè)鍵函數(shù)。鍵函數(shù)里直接返回d.name,當(dāng)節(jié)點(diǎn)數(shù)組發(fā)生更新時(shí),新節(jié)點(diǎn)要與舊節(jié)點(diǎn)在名稱上相對應(yīng)。

//獲取節(jié)點(diǎn)的update部分  
var nodeUpdate = svg.selectAll(".node")  
               .data(nodes, function(d){ return d.name; });  
  
//獲取節(jié)點(diǎn)的enter部分  
var nodeEnter = nodeUpdate.enter();  
  
//獲取節(jié)點(diǎn)的exit部分  
var nodeExit = nodeUpdate.exit(); 

先處理enter部分,即添加節(jié)點(diǎn)。節(jié)點(diǎn)的構(gòu)成為:分組元素里有一個(gè)圓表示節(jié)點(diǎn),還有一個(gè)文字元素表示節(jié)點(diǎn)的名稱。元素結(jié)構(gòu)如下:

本例中,每一個(gè)新添加的節(jié)點(diǎn)都將緩慢地過渡到自己本身的位置,如此更具有友好性。因此,新節(jié)點(diǎn)的初始位置都設(shè)定在source節(jié)點(diǎn)處,確切的說是重回之前source節(jié)點(diǎn)的位置,該坐標(biāo)是保存在source.x0和source.y0里的。另外,對于每一個(gè)新節(jié)點(diǎn),設(shè)置的半徑為0,設(shè)置為完全透明,接下來在處理update部分的時(shí)候會(huì)將這些新節(jié)點(diǎn)過渡到正常狀態(tài)的。下圖展示了處理enter部分和update部分時(shí)如何節(jié)點(diǎn)的位置時(shí)如何確定和過渡的。

處理enter部分的代碼如下。

//1. 節(jié)點(diǎn)的 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);   
});  
  
//省略添加圓和文字部分  

然后處理update部分,將所有節(jié)點(diǎn)(包括在enter部分新添加的節(jié)點(diǎn))都緩緩過渡到新的位置。由于新的節(jié)點(diǎn)數(shù)組是與節(jié)點(diǎn)選擇集綁定在一起的,因此d.x和d.y里保存的就是新的坐標(biāo)值。

//2. 節(jié)點(diǎn)的 Update 部分的處理辦法  
var updateNodes = nodeUpdate.transition()  
               .duration(500)  
               .attr("transform", function(d) {   
return "translate(" + d.y + "," + d.x + ")";   
}); 

最后處理exit部分,需要?jiǎng)h除的節(jié)點(diǎn)的位置緩緩過渡到其父節(jié)點(diǎn)處。

//3. 節(jié)點(diǎn)的 Exit 部分的處理辦法  
var exitNodes = nodeExit.transition()  
 .duration(500)  
.attr("transform", function(d) {   
return "translate(" + source.y + "," + source.x + ")";   
})  
 .remove();  

2.3 分別處理連線的update、enter、exit三部分

在svg中選擇所有的連線,綁定連線數(shù)組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(); 

對于連線的enter部分,是插入路徑元素path,路徑由對角線生成器獲取,對角線的起點(diǎn)和終點(diǎn)坐標(biāo)都是(source.x0, source.y0)。

對于連線的update部分,將所有的連線的位置(對角線的起點(diǎn)和終點(diǎn))更新到新的位置,即目前綁定的數(shù)組links里保存的位置。

對于連線的exit部分,令其緩緩過渡到當(dāng)前的source點(diǎn),再移除。

//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 保存當(dāng)前的節(jié)點(diǎn)坐標(biāo)

當(dāng)用戶點(diǎn)擊節(jié)點(diǎn)后,數(shù)據(jù)發(fā)生更新,即每個(gè)節(jié)點(diǎn)的坐標(biāo)要發(fā)生更新。但是,在對節(jié)點(diǎn)和連線進(jìn)行過渡操作的時(shí)候,需要使用到更新前的數(shù)據(jù)(source.x0和source.y0)。因此,每一次調(diào)用重繪函數(shù),都要將當(dāng)前節(jié)點(diǎn)的位置保存下來。

nodes.forEach(function(d) {  
      d.x0 = d.x;  
      d.y0 = d.y;  
}); 

x和y坐標(biāo)分別保存在x0和y0中,在調(diào)用redraw(source)時(shí),被點(diǎn)擊的節(jié)點(diǎn)被作為參數(shù)傳到了重繪函數(shù)里,因此source.x0和source.y0里保存的是被點(diǎn)擊之前節(jié)點(diǎn)的坐標(biāo)。

3. 結(jié)果

結(jié)果如下圖所示,點(diǎn)擊節(jié)點(diǎn)可以展開子節(jié)點(diǎn)。

源代碼請單擊以下鏈接,郵件查看源代碼:

http://www./demo/G-10.0/mind.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多