2016-09-29 22 views
0

私はこの質問に似た何かをしようとしていますd3.js how to dynamically add nodes to a tree。しかし、私はd3.jsのv4で動作するソリューションを得るのは本当に難しいと思っています。d3.js v4動的にツリーを整理木に追加する

私はいくつかのプリロードされたjsonからツリーを構築しています。そして、ユーザーがノードをクリックしたときに子ノードをツリーに追加したいと考えています。子ノードを追加するプロセスには、RESTサービスを呼び出す必要があります.RESTサービスはjsonを返します。私のコードの大部分は、v3を使用して展開/折りたたみ可能な整理木であるhttp://bl.ocks.org/mbostock/4339083と、v4を使用して拡張/折りたたみ不可能な整頓木であるhttps://bl.ocks.org/mbostock/9d0899acb5d3b8d839d9d613a9e1fe04に大きく基づいています。

私のコードはhttps://jsfiddle.net/rkian/3e73Lz6d/です。私はツリーを問題なく描くことができますが、ツリーにノードを追加する方法は実際には問題があります。私はこれを疑う:

function click(d) { 
    if (!d.children && !d._children) { 
     var jsonChildren = $.parseJSON('...'); 
     d.data.children = jsonChildren; 
     d.data._children = jsonChildren; 
     updateTree(d); 
    } 

私はちょうどそれを実装する方法を練ることができません。

+0

http://stackoverflow.com/questions/43140325/add-node-to-d3-tree-v4/43368677:私のコードのすべてがここにある、私はそれが誰かの役に立てば幸い#43368677 –

答えて

0

私は自分のツリーを構築しようとしていた系統変数(階層構造に変換されるフラットアレイ)に奇妙なことがありました。ツリーに変換すると、ツリー内のオブジェクトが変更されました。私はこの系統の深いコピーを作ってこれを解決しました。

var lineage = $.parseJSON('[{"id":739,"name":"Life","parent":null,"rank":9,"child_count":1},{"id":1,"name":"Eukaryota","parent":739,"rank":1,"child_count":3},{"id":43,"name":"Animalia","parent":1,"rank":2,"child_count":9},{"id":2,"name":"Archaeplastida","parent":1,"rank":2,"child_count":1},{"id":740,"name":"Plantae","parent":1,"rank":2,"child_count":0},{"id":417,"name":"Annelida","parent":43,"rank":3,"child_count":1},{"id":336,"name":"Arthropoda","parent":43,"rank":3,"child_count":6},{"id":228,"name":"Chordata","parent":43,"rank":3,"child_count":3},{"id":222,"name":"Cnidaria","parent":43,"rank":3,"child_count":4},{"id":328,"name":"Mollusca","parent":43,"rank":3,"child_count":1},{"id":548,"name":"Myzozoa","parent":43,"rank":3,"child_count":1},{"id":604,"name":"Nematoda","parent":43,"rank":3,"child_count":1},{"id":467,"name":"Platyhelminthes","parent":43,"rank":3,"child_count":2},{"id":275,"name":"Porifera","parent":43,"rank":3,"child_count":2},{"id":418,"name":"Polychaeta","parent":417,"rank":4,"child_count":3},{"id":419,"name":"Spionida","parent":418,"rank":5,"child_count":1},{"id":452,"name":"Sabellida","parent":418,"rank":5,"child_count":1},{"id":448,"name":"Terebellida","parent":418,"rank":5,"child_count":1},{"id":420,"name":"Spionidae","parent":419,"rank":6,"child_count":2},{"id":699,"name":"Boccardia","parent":420,"rank":7,"child_count":1},{"id":421,"name":"Polydora","parent":420,"rank":7,"child_count":1},{"id":700,"name":"Boccardia proboscidea","parent":699,"rank":8,"child_count":0}]'); 
 

 
$(document).ready(function() { 
 
    // WTF js. 
 
    var lineage_flat; 
 
    
 
\t // Color manipulation functions and settings for the tree 
 
\t function shadeColor2(color, percent) { 
 
\t \t var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; 
 
\t \t return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1); 
 
\t } 
 
\t function blendColors(c0, c1, p) { 
 
\t \t var f=parseInt(c0.slice(1),16),t=parseInt(c1.slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF,R2=t>>16,G2=t>>8&0x00FF,B2=t&0x0000FF; 
 
\t \t return "#"+(0x1000000+(Math.round((R2-R1)*p)+R1)*0x10000+(Math.round((G2-G1)*p)+G1)*0x100+(Math.round((B2-B1)*p)+B1)).toString(16).slice(1); 
 
\t } 
 
    
 
    // Color variables 
 
\t var main_color = '#FFFFFF'; 
 
\t var secondary_color = '#FFFFFF'; 
 
\t var line_color = '#FFF793'; 
 
\t var max_rank = lineage[lineage.length - 1]['rank']; 
 

 
\t // Used to assign colors to ranks 
 
\t function get_rank_colour(d) { 
 
\t \t rank = d.data.data.rank; 
 
\t \t if(rank == 9) { 
 
\t \t \t return main_color; 
 
\t \t } 
 
\t \t else { 
 
\t \t \t return blendColors(main_color, secondary_color, (d.data.rank/max_rank)) 
 
\t \t } 
 
\t } 
 
    
 
    // Draws a curve between two points 
 
    function connector(d) { 
 
    return 'M' + d.x + ',' + (d.y - 18) + 
 
     "C" + (d.x + d.parent.x)/2 + "," + (d.y - 25) + 
 
     " " + (d.x + d.parent.x)/2 + "," + (d.parent.y + 25) + 
 
     " " + d.parent.x + "," + (d.parent.y + 17); 
 
    }; 
 

 
\t // Transition vars 
 
\t var duration = 500; 
 

 
    // Get the width of the container element for the tree 
 
\t width = $('#svgcontainer').width(); 
 

 
\t // Calculate the height required based on the number of node levels on the ancestry tree 
 
\t height = lineage[lineage.length - 1]['rank']* 150; 
 

 
\t // Set the svg element's height 
 
\t $('#lifetree').attr('height', height + 'px'); 
 

 
    // Select the svg element in the DOM 
 
    var svg = d3.select("svg") 
 

 
    // Insert a group container and move it 40 px to the right (to pad the tree contents in the svg container) 
 
    var g = svg.append("g").attr("transform", "translate(40,40)"); 
 

 
    // Create and return a d3 tree object of the correct width and height, run the root hierarchy element through it 
 
    var tree = d3.tree().size([width-200, height - 160]); 
 

 

 
    var root = getTreeData(lineage); 
 
    updateTree(root); 
 

 
    // Get the data for the tree 
 
    function getTreeData(json) {  
 
    // Save the flat lineage, we have to do this weird parse thing to make a deep copy 
 
    lineage_flat = JSON.parse(JSON.stringify(json)); 
 
    
 
    // This seems to unflatten arrays of objects with parentIds and parents. Wish I'd known about it sooner. 
 
    var dataTree = d3.stratify() 
 
     .id(function(d){ return d.id; }) 
 
     .parentId(function(d){ return d.parent; }) 
 
     (JSON.parse(JSON.stringify(json))); 
 

 
    // D3 requires a hierarchy object which then gets made into a tree 
 
    var root = d3.hierarchy(dataTree); 
 
    tree(root); 
 
    
 
    // Normalize for fixed-depth, also we do some fancy transitions so save a copy of original xys 
 
    root.each(function(d) { d.y = d.depth * 100; d.x0 = d.x; d.y0 = d.y; }); 
 
    return root; 
 
    } 
 

 
    function drawElements(node) { 
 
    // Add circles above each node 
 
    node.append("circle") 
 
     .attr("r", 2) 
 
     .attr("transform", function(d) { return "translate(0,-18)"; }) 
 
     .attr("class", "upper-circle") 
 
     .style("stroke", get_rank_colour) 
 
     .style("fill", get_rank_colour); 
 

 
    // Add the circles below each node 
 
    node.append("circle") 
 
     .attr("r", 4) 
 
     .attr("transform", function(d) { return "translate(0,16)"; }) 
 
     .attr("class", "lower-circle") 
 
     .style("stroke", "#000000") 
 
     .style("fill", function(d) { 
 
     return d.data.data.child_count > 0 ? "#FFFFFF" : "#000000"; 
 
     }); 
 

 
    // Add text 
 
    node.append("text") 
 
     .attr("dy", 3) 
 
     .style("fill", '#FFFFFF') 
 
     .style("text-anchor", "middle") 
 
     .text(function(d) { 
 
     return d.data.data.name; 
 
     if(d.data.rank == max_rank || d.data.name == "Life") { 
 
      return d.data.name; 
 
     } 
 
     else if(d.children) { 
 
      return d.data.name + ' (' + d.children.length + ")"; 
 
     } 
 
     else { 
 
      return d.data.name + ' (' + d.data.count + ")"; 
 
     } 
 
     }) 
 
     .each(function(d) { 
 
     d.textwidth = this.getBBox().width; 
 
     d.textheight = this.getBBox().height; 
 
     }); 
 

 
    // Add clickable background rectangle so it looks nicer 
 
    node.insert("rect",":first-child") 
 
     .style("fill", '#000000') 
 
     .style("fill-opacity", function(d) { 
 
      if(d.children || d.data.data.rank == max_rank) { return 0.5; } 
 
      else { return 0.2; } 
 
     } 
 
    ) 
 
     .attr('height', function(d) { return d.textheight + 10; }) 
 
     .attr('width', function(d) { return d.textwidth + 10; }) 
 
     .attr("transform", function(d) { 
 
     if(d.data.data.rank == 9) { 
 
      return "translate(-" + ((d.textwidth + 10)/2) + ",-" + ((d.textheight + 30)/2) + ")"; 
 
     } 
 
     return "translate(-" + ((d.textwidth + 10)/2) + ",-" + ((d.textheight + 15)/2) + ")"; 
 
     }) 
 
     .attr('rx', 10) 
 
     .attr('ry', 10); 
 
    } 
 

 
    function updateTree(source, shallowestDepth = 0) { 
 
    /* 
 
    * Nodes 
 
    */ 
 
    // Data join with source data, keeping ids so it knows about the same nodes 
 
    var node = g.selectAll(".node") 
 
     .data(source.descendants() , function(d) { return d.data.id; }); 
 
    
 
    // Data enter, this starts doing things to all the new nodes 
 
    var nodeEnter = node.enter() 
 
     .append("g") 
 
     .attr("class", function(d) { return "rank-" + d.data.data.rank + " node" + (d.children ? " node--internal" : " node--leaf"); }) 
 
     .attr("transform", function(d) { 
 
     if(d.parent != null) { 
 
      return "translate(" + d.parent.x + "," + d.parent.y + ")"; 
 
     } 
 
     return "translate(" + d.x + "," + d.y + ")"; 
 
     }) 
 
     .on("click", click); 
 
    
 
    // Add text + bg + circles to the nodes 
 
    drawElements(nodeEnter); 
 
    
 
    // Add pretty hover class for each taxon node 
 
    $('g').hover(function() { 
 
     $(this).children('rect').addClass('recthover'); 
 
    }, function() { 
 
     $(this).children('rect').removeClass('recthover'); 
 
    }); 
 
    
 
    // Transition nodes to their new position. 
 
    var nodeMerge = node.merge(nodeEnter).transition() 
 
     .duration(duration) 
 
     .attr('transform', function (d) { 
 
     return 'translate(' + d.x + ',' + d.y + ')'; 
 
     }); 
 
    nodeMerge.select('rect', ':first-child').style("fill-opacity", function(d) { 
 
     if(d.children || d.data.data.rank == max_rank) { return 0.5; } 
 
     else { return 0.2; } 
 
    }); 
 
     
 
    // Get the old elements for removal 
 
    var oldNode = node.exit(); 
 
    
 
    // Find the shallowest depth in the old element, that's the parent 
 
    oldNode.each(function(d) { 
 
     var shallowestParent = d; 
 
     do { shallowestParent = shallowestParent.parent; } 
 
     while(shallowestParent.depth > shallowestDepth); 
 
     d.shallowestParentX = shallowestParent.x; 
 
     d.shallowestParentY = shallowestParent.y; 
 
    }); 
 
    
 
    // Transition the old nodes out 
 
    var transitionedNodes = oldNode.transition() 
 
     .duration(duration) 
 
     .attr("transform", function(d) { 
 
     return "translate(" + d.shallowestParentX + "," + d.shallowestParentY + ")"; 
 
     }); 
 
    oldNode.selectAll('rect').transition() 
 
     .style("fill-opacity", 0) 
 
     .duration(duration/2) 
 
    oldNode.selectAll('text').transition() 
 
     .style("fill-opacity", 0) 
 
     .duration(duration/2) 
 
    oldNode.selectAll('circle').transition() 
 
     .style("fill-opacity", 0) 
 
     .duration(duration/3) 
 
    transitionedNodes.remove(); 
 
    
 
    /* 
 
    * Links 
 
    */ 
 
    var link = g.selectAll(".link") 
 
     .data(source.descendants().slice(1).reverse(), function(d) { return d.data.id; }) 
 
    
 
    // Draw the links between nodes 
 
    var linkEnter = link.enter() 
 
     .insert("path",":first-child") 
 
     .attr("class", "link") 
 
     .style("stroke", function(d) { 
 
     if(d.children) { 
 
      return line_color; 
 
     } 
 
     return blendColors(main_color, secondary_color, (d.data.data.rank/max_rank)) 
 
     }) 
 
     .attr("d", function (d) { 
 
     var o = {x: d.parent.x0, y: d.parent.y0, parent: {x: d.parent.x0, y: d.parent.y0}}; 
 
     return connector(o); 
 
     }); 
 
     
 
    // Transition links to their new position. 
 
    var linkMerge = link.merge(linkEnter).transition() 
 
     .duration(0) 
 
     .attr('d', connector); 
 
     
 
    // Style the links 
 
    linkMerge.style("stroke", function(d) { 
 
     if(d.children) { 
 
     return line_color; 
 
     } 
 
     return blendColors(main_color, secondary_color, (d.data.data.rank/max_rank)) 
 
    }) 
 
    
 
    // Transition the old links out 
 
    var oldLink = link.exit(); 
 
    oldLink.transition() 
 
     .duration(duration/2) 
 
     .attr("d", function(d) { 
 
     var o = {x: d.x, y: d.y, parent: {x: d.x, y: d.y}}; 
 
     return connector(o); 
 
     }) 
 
     .remove(); 
 
    } 
 
    
 
    // Toggle children on click. 
 
    function click(d) { 
 
    // If the node does not have any pre-loaded children 
 
    if (!d.children && !d._children) { 
 
     var jsonPath = '/taxa/api/children/' + d.data.id; 
 
     
 
     // Get the JSON lineage for it 
 
     d3.json(jsonPath, function(error, json) { 
 
     // Get the children 
 
     children = json['children']; 
 
     
 
     // Make a new lineage array, can't use the one previously stored because 
 
     // of javascript variables mutability being weird 
 
     new_lineage = []; 
 
     lineage_flat.forEach(function(node, i) { 
 
      recalcIndex = lineage.indexOf(node); 
 
      // The node.rank 9 is in there because Life for some reason has rank 9 
 
      // Basically we want to exclude all nodes of a lower rank than the one clicked 
 
      if(node.rank > d.data.data.rank && node.rank != 9) {   
 
      // console.log(node); - we don't want these nodes 
 
      } 
 
      else { new_lineage.push(node); } 
 
     }); 
 
     
 
     // Append the children to the new lineage 
 
     children.forEach(function(child) { 
 
      new_lineage.push(child); 
 
     }); 
 
     
 
     // Javascript is weird. We need a deep copy of new_lineage 
 
     temp = JSON.parse(JSON.stringify(new_lineage)); 
 
     new_lineage = JSON.parse(JSON.stringify(temp)); 
 
     
 
     // Turn it into a tree and update our svg 
 
     root = getTreeData(new_lineage); 
 
     updateTree(root, d.depth); 
 
     }); 
 
    } 
 
    } 
 
});
.link { 
 
    fill: none; 
 
    stroke: orange; 
 
    stroke-width: 1.5px; 
 
\t /* Transition. */ 
 
\t -o-transition:.5s; 
 
\t -ms-transition:.5s; 
 
\t -moz-transition:.5s; 
 
\t -webkit-transition:.5s; 
 
\t /* ...and now for the proper property */ 
 
\t transition:.5s; 
 
} 
 
.link:hover { 
 
    fill: none; 
 
    stroke: orange; 
 
    stroke-width: 3px; 
 
} 
 
.link.warning{ 
 
    stroke: orange; 
 
} 
 
.lower-circle, .upper-circle { 
 
    z-index: 1; 
 
    stroke-width: 2px; 
 
} 
 
path.link { 
 
    stroke-width: 3px; 
 
} 
 
.rank-9 .upper-circle { 
 
    display: none; 
 
} 
 
.rank-9 text { 
 
    font-size: 3em; 
 
    font-weight: bold; 
 
} 
 
.rank-1 { 
 
    font-size: 1.5em; 
 
    font-weight: bold; 
 
} 
 

 
.rank-2 { 
 
    font-size: 1em; 
 
    font-weight: bold; 
 
    margin-right: 20px; 
 
    margin-left: 20px; 
 
} 
 
svg text { 
 

 
} 
 
.rank-3 { 
 
    font-size: 1em; 
 
} 
 
#triangles { 
 
    /*position: relative; 
 
    top: -20px; 
 
    margin-top: -50px; 
 
    padding-top: 80px; 
 

 
-webkit-transform: rotate(180deg); 
 
-moz-transform: rotate(180deg); 
 
-ms-transform: rotate(180deg); 
 
-o-transform: rotate(180deg); 
 
transform: rotate(180deg);*/ 
 
} 
 
#svgcontainer { 
 
    position: relative; 
 
    padding-top: 80px; 
 
/*-webkit-transform: rotate(180deg); 
 
-moz-transform: rotate(180deg); 
 
-ms-transform: rotate(180deg); 
 
-o-transform: rotate(180deg); 
 
transform: rotate(180deg);*/ 
 
} 
 
#svgparent-disablethis { 
 
    background: transparent url('static/img/carousel/1.jpg' 0 0 no-repeat); 
 

 
    background: -moz-linear-gradient(top, rgba(255,255,255,0) 100%, rgba(130,91,0,1) 0%); /* FF3.6+ */ 
 
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0)), color-stop(100%,rgba(130,91,0,1))); /* Chrome,Safari4+ */ 
 
    background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* Chrome10+,Safari5.1+ */ 
 
    background: -o-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* Opera 11.10+ */ 
 
    background: -ms-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* IE10+ */ 
 
    background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* W3C */ 
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#825b00',GradientType=0); /* IE6-9 */ 
 
} 
 

 
svg { width: 100%; height: auto; } 
 

 

 
rect { 
 
\t /* Transition. */ 
 
\t -o-transition:.2s; 
 
\t -ms-transition:.2s; 
 
\t -moz-transition:.2s; 
 
\t -webkit-transition:.2s; 
 
\t /* ...and now for the proper property */ 
 
\t transition:.2s; 
 

 

 
} 
 
text { 
 
    cursor: pointer; 
 
} 
 

 
.lower-circle { 
 
\t cursor: pointer; 
 
} 
 
.lower-circle:hover { 
 
\t fill: #FFF !important; 
 
} 
 
.recthover { 
 
\t fill: #FFEC1C !important; 
 
}
<script src="https://d3js.org/d3.v4.min.js"></script> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 

 
    <div id="svgparent"> 
 
    <div id="svgcontainer"> 
 
     <svg id='lifetree'></svg> 
 
    </div> 
 
    </div>

関連する問題