2013-10-14 33 views
2

我试图在页面上同时放置多个D3强制布局。强制布局的数量理想情况下是可变的,具体取决于从动态API返回的根的数量。我已经按照答案on this question regarding multiple force layouts,并成功地将每个布局放在一个单独的div,在一个单独的SVG。多个强制布局导致打勾函数冲突

然而,问题是双重的:

1)svgs似乎同时被吸入,从而导致在α冷却参数冲突(在每一图中的“滴答声”)。因此,唯一按照预期方式定位的布局是在页面上绘制的最后一个svg。滴答功能包含的代码可以形成与垂柳树相似的力布局,其中根节点位于顶部,子节点位于其下方。

2)设置一个循环来从API中遍历完整的结果列表会导致D3崩溃,并且出现一个错误“Uncaught TypeError:无法读取属性'textContent'为null。

我认为理想的解决方案是在不会导致alpha制冷参数(在“tick”上)冲突或者重载D3库的情况下,在先前成功渲染之后绘制每个力布局一次有太多的力布局实例。有人能够洞察这个问题吗?这里是我的代码:

/* ... GET THE RESULTS FROM THE API ...*/ 
function handleRequest2(json) { 
     allroots = json[1]['data']['children']; 
     (function() { 
      var index = 0; 
      function LoopThrough() { 
       currentRoot = allroots[index]; 
       if (index < allroots.length) { 
        /* DRAW THE GRAPH */ 
        draw_graphs(currentRoot, index); 
        ++index; 
        LoopThrough(); 
        }; 
      } 

     LoopThrough(); 
     })(); 

    } 
//Force Layout Code 
function draw_graphs(root, id) { 
var root_id = "map-" + id.toString(); 
var force; 
var vis; 
var link; 
var node; 
var w = 980; 
var h = 1000; 
var k = 0; 
// Create a separate div to house each SVG graph 
    div = document.createElement("div"); 
    div.style.width = "980px"; 
    div.style.height = "1000px"; 
    div.style.cssFloat="left"; 
    div.id = root_id; 
    $(div).addClass("chattermap-map"); 
    // Append the div to the chart container 
    $('#chart').append(div); 

force = d3.layout.force() 
    .size([w, h]) 
    .charge(-250) 
    .gravity(0) 
    .on("tick", tick); 
    // Create the SVG and append it to the created div 
vis = d3.select("#"+root_id) 
    .append("svg:svg") 
    .attr("width", w) 
    .attr("height", h) 
    .attr("id",root_id); 


    // Put the Reddit JSON in the correct format for the Force Layout 
nodes = flatten(root), 
links = optimize(d3.layout.tree().links(nodes)); 
// Calculations for the sizing of the nodes 
avgNetPositive = getAvgNetPositive(); 
maxNetPositive = d3.max(netPositiveArray); 
minNetPositive = d3.min(netPositiveArray); 
// Create a logarithmic scale that sizes the nodes 
radius = d3.scale.pow().exponent(.3).domain([minNetPositive,maxNetPositive]).range([5,30]); 
// Fix the root node to the top of the svg 
root.data.fixed = true; 
root.data.x = w/2; 
root.data.y = 50; 
    // Start the force layout. 
force 
    .nodes(nodes) 
    .links(links) 
    .start(); 
    // Update the links 
    link = vis.selectAll("line.link") 
    .data(links, function(d) { return d.target.id; }); 
    // Enter any new links. 
    link.enter().insert("svg:line", ".node") 
    .attr("class", "link") 
    .attr("x1", function(d) { return d.source.x; }) 
    .attr("y1", function(d) { return d.source.y; }) 
    .attr("x2", function(d) { return d.target.x; }) 
    .attr("y2", function(d) { return d.target.y; }); 
    // Exit any old links. 
    link.exit().remove(); 
    // Update the nodes 
    node = vis.selectAll("circle.node") 
    .data(nodes, function(d) {return d.id; }) 
    .style("fill", function(d) { 
     return '#2960b5'; 
    }); 
    // Enter any new nodes. 
    node.enter().append("svg:circle") 
    .attr("class", "node") 
    .attr("cx", function(d) {return d.x; }) 
    .attr("cy", function(d) {return d.y; }) 
    .attr("r", function(d) { 
     //Get the net positive reaction 
     var netPositive = d.ups - d.downs; 
     var relativePositivity = netPositive/avgNetPositive; 
     //Scale the radii based on the logarithmic scale defined earlier 
     return radius(netPositive); 
    }) 
    .style("fill", function(d) { 
     return '#2960b5'; 
    }) 
    // Allow dragging on click 
    .call(force.drag); 
    // Exit any old nodes. 
    node.exit().remove(); 
    //This will add the name of the author to the node HTML 
    node.append("author").text(function(d) {return d.author}); 
    //Add the body of the comment to the node 
    node.append("comment").text(function(d) {return Encoder.htmlDecode(d.body_html)}); 
    //Add the UNIX timestamp to the node 
    node.append("timestamp").text(function(d) {return moment.unix(d.created_utc).fromNow();}) 
    //On load, assign the root node to the tooltip 
    numberOfNodes = node[0].length; 
    rootNode = d3.select(node[0][parseInt(numberOfNodes) - 1]); 
    rootNodeComment = rootNode.select("comment").text(); 
    rootNodeAuthor = rootNode.select("author").text(); 
    rootNodeTimestamp = rootNode.select("timestamp").text(); 

    // Create the tooltip div for the comments 
tooltip_div = d3.select("#"+root_id).append("div") 
    .attr("class", "tooltip")    
     .style("opacity", 1); 
//Add the HTML to the tooltip for the root 
    tooltip_div .html("<span class='commentAuthor'>" + rootNodeAuthor + "</span><span class='bulletTimeAgo'>&bull;</span><span class='timestamp'>" + rootNodeTimestamp + "</span><br>" + rootNodeComment) 
    //Position the tooltip based on the position of the current node, and it's size 
    .style("left", (rootNode.attr("cx") - (-rootNode.attr("r")) - (-9)) + "px") 
    .style("top", (rootNode.attr("cy") - 15) + "px");  

    node.on("mouseover", function() { 
    currentNode = d3.select(this); 
    currentTitle = currentNode.select("comment").text(); 
    currentAuthor = currentNode.select("author").text(); 
    currentTimestamp = currentNode.select("timestamp").text(); 
    tooltip_div.transition()   
    .duration(200)  
    .style("opacity", 1); 
    // Add the HTML for all other tooltips on mouseover 
    tooltip_div .html("<span class='commentAuthor'>" + currentAuthor + "</span><span class='bulletTimeAgo'>&bull;</span><span class='timestamp'>" + currentTimestamp + "</span><br>" + currentTitle) 
       //Position the tooltip based on the position of the current node, and it's size 
       .style("left", (currentNode.attr("cx") - (-currentNode.attr("r")) - (-9)) + "px") 
       .style("top", (currentNode.attr("cy") - 15) + "px");  
    }); 

    // Fade out the tooltip on mouseout 
    node.on("mouseout", function(d) {  
     tooltip_div.transition()   
      .duration(500)  
      .style("opacity", 1); 
    }); 
    // Optimize the JSON output of Reddit for D3 
    function flatten(root) { 

    var nodes = [], i = 0, j = 0; 
    function recurse(node) { 

     if (node['data']['replies'] != "" && node['kind'] != "more") { 
      node['data']['replies']['data']['children'].forEach(recurse); 
     } 
     if (node['kind'] !="more") { 
      //Add an ID value to the node starting at 1 
      node.data.id = ++i; 
      node.data.name = node.data.body; 
      //Put the replies in the key 'children' to work with the tree layout 
      if (node.data.replies != "") { 

       node.data.children = node.data.replies.data.children; 
       //Remove the extra 'data' layer for each child 
       for (j=0; j < node.data.children.length; j++) { 
        node.data.children[j] = node.data.children[j].data; 
       } 

      } else { 
       node.data.children = ""; 
      } 
      var comment = node.data; 
      nodes.push(comment); 
     } 
    } 
    recurse(root); 
    return nodes; 
    } 
    // Optimize the JSON for use with Links 
    function optimize(linkArray) { 
     optimizedArray = []; 
     for (k=0; k < linkArray.length; k++) { 
      if(typeof linkArray[k].target.count == 'undefined') { 
       optimizedArray.push(linkArray[k]); 
      } 
     } 
     return optimizedArray; 
    } 
    // Get the average net positive upvotes for use in sizing 
    function getAvgNetPositive() { 
    var sum = 0; 
    netPositiveArray = [] 
    //Select all the nodes 
    var allNodes = d3.selectAll(nodes)[0]; 
    //For each node, get the net positive votes and add it to the sum 
    for (i=0; i < allNodes.length; i++) { 
     var netPositiveEach = allNodes[i]["ups"] - allNodes[i]["downs"]; 
     sum += netPositiveEach; 
     netPositiveArray.push(netPositiveEach); 
    } 
    var avgNetPositive = sum/allNodes.length; 
    return avgNetPositive; 
    } 
    function tick(e) { 
    var kx = .4 * e.alpha, ky = 1.4 * e.alpha; 
    links.forEach(function(d, i) { 
     d.target.x += (d.source.x - d.target.x) * kx; 
     d.target.y += (d.source.y + 80 - d.target.y) * ky; 
    }); 
    link.attr("x1", function(d) { return d.source.x; }) 
     .attr("y1", function(d) { return d.source.y; }) 
     .attr("x2", function(d) { return d.target.x; }) 
     .attr("y2", function(d) { return d.target.y; }); 

    node.attr("cx", function(d) { return d.x; }) 
     .attr("cy", function(d) { return d.y; }); 
    } 
    // // Remove the animation effect of the force layout 
    // while ((force.alpha() > 1e-2) && (k < 150)) { 
    //  force.tick(), 
    //  k = k + 1; 
    // } 
} 

在此先感谢!

回答

1

如果将它们封装在自己的命名空间中,您应该能够同时使多个强制布局工作,例如,通过单独的功能。但是,您也可以通过听end事件来做你想做的事情 - 请参阅the documentation。这样,您可以“链接”布局,一旦前一个完成后就开始布局。

关于另一个错误,它看起来像这将是由不完整/错误的数据造成的。

+0

将每个布局封装在自己的名称空间/函数中的Lars正是我在这里发布的代码所完成的目标。每个布局都放在它自己的SVG中,并放在一个独立的ID中。您在代码中看到的任何暗示名称间距/分隔不足的情况? –

+0

我想问题是,强制布局不允许你为'tick'事件指定一个名称空间。你可以只用一个'tick'处理程序来更新所有图形。 –

+0

谢谢拉尔斯。你能否指出我朝着写这样的功能的正确方向? –