2014-12-30 45 views
0

'用m3繪製縮進的樹。我從Mike Rostock's code開始,並做了一些修改,以便:1)顯示除葉子外的向右/向下箭頭; 2)爲每一行添加一個複選框; 3)隱藏根節點。用d3繪製一個可縮進的縮進樹d3

代碼在下面,它接受任何數據,我使用兩個參數調用drawIntentedTree函數:根節點和樹在其中繪製的div ID。

enter image description here

隨着上的圖片可能會看到,也有代碼的一些問題,對於這幫助將不勝感激: 1根/啓動節點,而花費樹枝,導致重繪在重疊左和下箭頭時,請參閱SCL線。 2.使用複選框可以觀察到類似的問題,該複選框基本上是用白色透明的x隱藏的。我的第一個意圖是用筆畫顏色填充框,但必須弄清楚每一行的css顏色是什麼,因爲它改變了。除了解決這兩個問題之外,我還有意圖在節點之間繪製直線,但原始代碼會繪製捲曲線,並允許摺疊和消耗之間的中間狀態(部分摺疊),同時以45°旋轉的箭頭,只顯示分支中的選中框。此外,我希望分支在展開另一分支時摺疊或部分摺疊,以避免向下滾動。

Mike Bostock正在使用一種技巧來顯示/隱藏樹的一部分,他在_children中備份子項,然後將子項分配給null以隱藏摺疊的分支,但重繪始終始於根節點,並且我沒有設法:1)避免根節點重繪; 2)將預先存在的左側三角形旋轉90或90°。

在一篇文章中的許多問題,我會很感激任何部分的幫助。 jsfiddle link

d3js代碼:

function drawIndentedTree(root, wherein) { 

var width = 300, minHeight = 800; 
var barHeight = 20, barWidth = 50; 

var margin = { 
     top: -10, 
     bottom: 10, 
     left: 0, 
     right: 10 
    } 

var i = 0, duration = 200; 

var tree = d3.layout.tree() 
    .nodeSize([0, 20]); 

var diagonal = d3.svg.diagonal() 
    .projection(function(d) { return [d.y, d.x]; }); 

var svg = d3.select("#"+wherein).append("svg") 
    .attr("width", width + margin.left + margin.right) 
    .append("g") 
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

// set initial coordinates 
root.x0 = 0; 
root.y0 = 0; 

// collapse all nodes recusively, hence initiate the tree 
function collapse(d) { 
    d.Selected = false; 
    if (d.children) { 
     d.numOfChildren = d.children.length; 
     d._children = d.children; 
     d._children.forEach(collapse); 
     d.children = null; 
    } 
    else { 
     d.numOfChildren = 0; 
    } 
} 
root.children.forEach(collapse); 

update(root); 

function update(source) { 

    // Compute the flattened node list. TODO use d3.layout.hierarchy. 
    var nodes = tree.nodes(root); 

    height = Math.max(minHeight, nodes.length * barHeight + margin.top + margin.bottom); 

    d3.select("svg").transition() 
     .duration(duration) 
     .attr("height", height); 

    d3.select(self.frameElement).transition() 
     .duration(duration) 
     .style("height", height + "px"); 

    // Compute the "layout". 
    nodes.forEach(function(n, i) { 
      n.x = i * barHeight; 
     }); 

    // Update the nodes… 
    var node = svg.selectAll("g.node") 
     .data(nodes, function(d) { 
       return d.index || (d.index = ++i); }); 

    var nodeEnter = node.enter().append("g").filter(function(d) { return d.id != root.id }) 
     .attr("class", "node") 
     .style("opacity", 0.001) 
     .attr("transform", function(d) { 
       return "translate(" + source.y0 + "," + source.x0 + ")"; 
     }); 

    // Enter any new nodes at the parent's previous position. 
    nodeEnter.append("path").filter(function(d) { return d.numOfChildren > 0 && d.id != root.id }) 
     .attr("width", 9) 
     .attr("height", 9) 
     .attr("d", "M -3,-4, L -3,4, L 4,0 Z") 
     .attr("class", function(d) { return "node "+d.type; }) 
     .attr("transform", function(d) { 
       if (d.children) { 
       return "translate(-14, 0)rotate(90)"; 
       } 
       else { 
       return "translate(-14, 0)rotate(0)"; 
       } 
      }) 
     .on("click", click); 

    // Enter any new nodes at the parent's previous position. 
    nodeEnter.append("rect").filter(function(d) { return d.id != root.id }) 
     .attr("width", 11) 
     .attr("height", 11) 
     .attr("y", -5) 
     .attr("class", function(d) { return "node "+d.type; }); 

// check box filled with 'x' or '+' 
    nodeEnter.append("text") 
     .attr("dy", 4) 
     .attr("dx", 2) 
     .attr("class", function(d) { return "node "+d.type+" text"; }) 
     .text("x"); 

    nodeEnter.append("rect").filter(function(d) { return d.parent }) 
     .attr("width", 9) 
     .attr("height", 9) 
     .attr("x", 1) 
     .attr("y", -4) 
     .attr("class", "node select") 
     .attr("style", function(d) { return "fill: "+boxStyle(d) }) 
     .on("click", check); 

    nodeEnter.append("text") 
     .attr("dy", 5) 
     .attr("dx", 14) 
     .attr("class", function(d) { return "node "+d.type+" text"; }) 
     .text(function(d) { return d.Name; }); 

    // Transition nodes to their new position. 
    nodeEnter.transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) 
     .style("opacity", 1); 

    node.transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) 
     .style("opacity", 1) 
     .select("rect"); 

    // Transition exiting nodes to the parent's new position. 
    node.exit().transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) 
     .style("opacity", 1e-6) 
     .remove(); 

    // Stash the old positions for transition. 
    nodes.forEach(function(d) { 
      d.x0 = d.x; 
      d.y0 = d.y; 
     }); 
} 

// Toggle children on click. 
function click(d) { 
    if (d.children) { 
     d3.select(this).attr("translate(-14, 0)rotate(90)"); 
     d._children = d.children; 
     d.children = null; 
    } else if (d._children) { 
     d.children = d._children; 
     d._children = null; 
    } 
    update(d); 
} 

// Toggle check box on click. 
function check(d) { 
    d.Selected = !d.Selected; 
    d3.select(this).style("fill", boxStyle(d)); 
} 

function boxStyle(d) { 
    return d.Selected ? "transparent" : "white"; 
} 
} 

var wherein = "chart"; 
var root = { 
"name": "AUT-1", 
"children": [ 
    { 
     "name": "PUB-1","children": [ 
      {"name": "AUT-11","children": [ 
       {"name": "AFF-111"}, 
       {"name": "AFF-112"} 
      ]}, 
      {"name": "AUT-12","children": [ 
       {"name": "AFF-121"} 
      ]}, 
      {"name": "AUT-13","children": [ 
       {"name": "AFF-131"}, 
       {"name": "AFF-132"} 
      ]}, 
      {"name": "AUT-14","children": [ 
       {"name": "AFF-141"} 
      ]} 
     ] 
    }, 
    { 
     "name": "PUB-2","children": [ 
      {"name": "AUT-21"}, 
      {"name": "AUT-22"}, 
      {"name": "AUT-23"}, 
      {"name": "AUT-24"}, 
      {"name": "AUT-25"}, 
      {"name": "AUT-26"}, 
      {"name": "AUT-27"}, 
      {"name": "AUT-28","children":[ 
       {"name": "AFF-281"}, 
       {"name": "AFF-282"}, 
       {"name": "AFF-283"}, 
       {"name": "AFF-284"}, 
       {"name": "AFF-285"}, 
       {"name": "AFF-286"} 
      ]} 
     ] 
    }, 
    {"name": "PUB-3"}, 
    { 
     "name": "PUB-4","children": [ 
      {"name": "AUT-41"}, 
      {"name": "AUT-42"}, 
      {"name": "AUT-43","children": [ 
       {"name": "AFF-431"}, 
       {"name": "AFF-432"}, 
       {"name": "AFF-433"}, 
       {"name": "AFF-434","children":[ 
        {"name": "ADD-4341"}, 
        {"name": "ADD-4342"}, 
       ]} 
      ]}, 
      {"name": "AUT-44"} 
     ] 
    } 
] 
}; 

CSS:

.node { 
font: 12px sans-serif; 
fill: #ccebc5; 
stroke: #7c9b75; 
stroke-width: 1px; 
} 

.node circle { 
fill: #fff; 
stroke: steelblue; 
stroke-width: 1.5px; 
cursor: pointer; 
} 

.node rect { 
width: 11px; 
height: 11px; 
cursor: pointer; 
} 

.node.select { 
width: 9px; 
height: 9px; 
cursor: pointer; 
fill: red; 
stroke-width: 0px; 
} 

.node path { 
width: 11px; 
height: 11px; 
cursor: pointer; 
} 

.node text Panel { 
stroke: #08519c; 
stroke-width: 0.5px; 
} 

.node text Cell { 
stroke: #a50f15; 
stroke-width: 0.5px; 
} 

.node.Root { 
fill: #f7f7f7; 
stroke: #505050; 
stroke-width: 1.0px; 
} 

.node.Root.text { 
fill: #505050; 
stroke-width: 0px; 
font-size: 10px; 
font-family: sans-serif; 
} 

.node.Panel { 
fill: #eff3ff; 
stroke: #08519c; 
stroke-width: 1.0px; 
} 

.node.Panel.text { 
fill: #08519c; 
stroke-width: 0px; 
font-size: 12px; 
font-family: sans-serif; 
} 

.node.Cell { 
fill: #fee5d9; 
stroke: #a50f15; 
stroke-width: 1.0px; 
} 

.node.Cell.text { 
fill: #a50f15; 
stroke-width: 0px; 
font-size: 12px; 
font-family: sans-serif; 
} 
+0

你能和你的'styles'更新您的問題,您的數據爲例:修改後的代碼 enter image description here

塊? – Mark

+0

我認爲這個說法不太正確,「但重繪始終始於根節點」。輸入節點最初是在根的位置開始的,但僅適用於根節點的子節點。後來每個節點都在其父母位置啓動(因爲'click'功能中的update(d)')。還值得注意的是,更新節點的html不會重新生成。所以他們開始他們的位置,然後轉換(翻譯)到他們的新職位(參見塊註釋爲//轉移節點到他們的新位置)。 – mehmet

回答

1

,我通過你的問題的工作我會更新我的答案。

  1. 根/啓動節點被重新繪製而花費樹枝,造成左重疊和向下箭頭,見SCL線。

這是d3的enter/update/exit的一個典型例子。你有nodeEnter變量 - 在輸入數據時要畫什麼 - 這是最初繪製的元素。然後你有node變量 - 這是所有已經繪製的東西。當您切換箭頭時,您正在採取行動nodeEnter,因此您將重新添加一個新的path導致重疊。取而代之的是,只需更新現有path並更改變換:

node.select("path").attr("transform", function(d) { 
    if (d.children) { 
     return "translate(-14, 0) rotate(90)"; 
    } else { 
     return "translate(-14, 0) rotate(0)"; 
    } 
}); 

here

0

在Mark的大力幫助下,問題現在已解決,並且正確的代碼如下所示。我用複選框替換了路徑中的x文本。

進一步的改進 - 對我來說 - 將箭頭旋轉結合到節點運動,然後允許如上所述的部分摺疊和自動摺疊,並且可能是在節點之間添加直線,可能會很難看。

enter image description here

function drawIndentedTree(root, wherein) { 

var width = 300, minHeight = 800; 
var barHeight = 20, barWidth = 50; 

var margin = { 
     top: -10, 
     bottom: 10, 
     left: 0, 
     right: 10 
    } 

var i = 0, duration = 200; 

var tree = d3.layout.tree() 
    .nodeSize([0, 20]); 

var diagonal = d3.svg.diagonal() 
    .projection(function(d) { return [d.y, d.x]; }); 

var svg = d3.select("#"+wherein).append("svg") 
    .attr("width", width + margin.left + margin.right) 
    .append("g") 
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

// set initial coordinates 
root.x0 = 0; 
root.y0 = 0; 

// collapse all nodes recusively, hence initiate the tree 
function collapse(d) { 
    d.Selected = false; 
    if (d.children) { 
     d.numOfChildren = d.children.length; 
     d._children = d.children; 
     d._children.forEach(collapse); 
     d.children = null; 
    } 
    else { 
     d.numOfChildren = 0; 
    } 
} 
root.children.forEach(collapse); 

update(root); 

function update(source) { 

    // Compute the flattened node list. TODO use d3.layout.hierarchy. 
    var nodes = tree.nodes(root); 

    height = Math.max(minHeight, nodes.length * barHeight + margin.top + margin.bottom); 

    d3.select("svg").transition() 
     .duration(duration) 
     .attr("height", height); 

    // Compute the "layout". 
    nodes.forEach(function(n, i) { 
      n.x = i * barHeight; 
     }); 

    // Update the nodes… 
    var node = svg.selectAll("g.node") 
     .data(nodes, function(d) { 
       return d.index || (d.index = ++i); }); 

    var nodeEnter = node.enter().append("g") 
     .attr("class", "node") 
     .style("opacity", 0.001) 
     .attr("transform", function(d) { 
       return "translate(" + source.y0 + "," + source.x0 + ")"; 
     }); 

    // Enter any new nodes at the parent's previous position. 
    nodeEnter.append("path").filter(function(d) { return d.numOfChildren > 0 && d.id != root.id }) 
     .attr("width", 9) 
     .attr("height", 9) 
     .attr("d", "M -3,-4, L -3,4, L 4,0 Z") 
     .attr("class", function(d) { return "node "+d.type; }) 
     .attr("transform", "translate(-14, 0)") 
     .on("click", click); 

    node.select("path").attr("transform", function(d) { 
      if (d.children) { 
       return "translate(-14, 0)rotate(90)"; 
      } 
      else { 
       return "translate(-14, 0)rotate(0)"; 
      } 
     }); 

    // Enter any new nodes at the parent's previous position. 
    nodeEnter.append("rect").filter(function(d) { return d.id != root.id }) 
     .attr("width", 11) 
     .attr("height", 11) 
     .attr("y", -5) 
     .attr("class", function(d) { return "node "+d.type; }); 

    nodeEnter.append("path").filter(function(d) { return d.parent }) 
     .attr("width", 9) 
     .attr("height", 9) 
     .attr("d", "M -5,-5, L -5,6, L 6,6, L 6,-5 Z M -5,-5, L 6,6, M -5,6 L 6,-5") 
     .attr("class", function(d) { return "node "+d.type; }) 
     .attr("style", function(d) { return "opacity: "+boxStyle(d) }) 
     .attr("transform", "translate(5, 0)") 
     .on("click", check); 

    nodeEnter.append("text") 
     .attr("dy", 5) 
     .attr("dx", 14) 
     .attr("class", function(d) { return "node "+d.type+" text"; }) 
     .text(function(d) { return d.Name; }); 

    // Transition nodes to their new position. 
    nodeEnter.transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) 
     .style("opacity", 1); 

    node.transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) 
     .style("opacity", 1) 
     .select("rect"); 

    // Transition exiting nodes to the parent's new position. 
    node.exit().transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) 
     .style("opacity", 1e-6) 
     .remove(); 

    // Stash the old positions for transition. 
    nodes.forEach(function(d) { 
      d.x0 = d.x; 
      d.y0 = d.y; 
     }); 
} 

// Toggle children on click. 
function click(d) { 
    if (d.children) { 
     d3.select(this).attr("translate(-14, 0)rotate(90)"); 
     d._children = d.children; 
     d.children = null; 
    } else if (d._children) { 
     d.children = d._children; 
     d._children = null; 
    } 
    update(d); 
} 

// Toggle check box on click. 
function check(d) { 
    d.Selected = !d.Selected; 
    d3.select(this).style("opacity", boxStyle(d)); 
} 

function boxStyle(d) { 
    return d.Selected ? 1 : 0; 
} 
} 
0

從上面的代碼開始,可以進行以下更改設計一個部分可摺疊樹。它從一個摺疊的樹開始,一個箭頭點擊一個分支,點擊複選框選擇項目。在箭頭上單擊新的部分摺疊樹,選定的項目保持可見,新的點擊將其全部摺疊。在摺疊/消費時保留選擇。

// rotate the arrow up, down and third way down on expensing/collapsing 
    node.select("path").attr("transform", function(d) { 
      if (d.children) { 
       if (!d._children) { 
        return "translate(-14, 0)rotate(90)"; 
       } 
       else { 
        return "translate(-14, 0)rotate(30)"; 
       } 
      } 
      else { 
       return "translate(-14, 0)rotate(0)"; 
      } 
     }); 

// toggle between the three states 
function click(d) { 
    if (d.children) { 
     if (!d._children) { 
      // backup children 
      d._children = d.children; 
      // restrick to selected items 
      d.children = marked = d.children.filter(function(d) { return d.Selected }); 
     } 
     else { 
      // partly collapsed -> collapse all 
      d.children = null; 
     } 
    } else if (d._children) { 
     d.children = d._children; 
     d._children = null; 
    } 
    update(d); 
}