【问题标题】:d3 v4 adding new nodes to force directed graphd3 v4 添加新节点以强制有向图
【发布时间】:2017-08-12 03:04:53
【问题描述】:

我是 d3.js 的新手,目前遇到一个问题。我正在使用力导向图来显示我的数据的关系。这应该允许用户将新节点添加到现有图表并在 2 个或更多节点之间绘制关系链接。我需要注意的是,我的数据是从一个 ajax 调用中填充的,我将其分配给一个变量并将其传递给生成图形的函数。数据的初始加载效果很好,一切都正确显示。我的问题是当用户单击按钮添加新节点时。在该操作中,我正在进行 ajax 调用以检索新的未链接关系以添加到图表中。我将新检索到的数据添加到节点数组并尝试重新绘制整个图形。但是,我收到关于将 x 和 y 属性设置为 NaN 的错误。我相信这与 forceSimulation 如何分配这些值有关。我确实尝试过使用simulation.reset(),但没有成功。

这是我的一些代码;

检索所有现有关系的初始调用。

function getGraphData(){
 $.ajax({
   url: [link to rest uri],
   type: 'GET',
   contentType: 'application/json'
 }).done(function(response){
   drawGraph(response);
 })
};

这是我第二次调用来检索新的未链接关系

function getNewRelationshipData(){
  $.ajax({
    url: [link to second rest uri],
    type: 'GET'
    contentType: 'application/json'
  }).done(function(response){
    var newNode = response.nodes;
    updateGraph();
    //---same as getGraphData()
    $.ajax({
       url: [link to rest uri],
       type: 'GET',
       contentType: 'application/json'
    }).done(function(response){
       var graphData = response;
       graphData.nodes[graphData.nodes.length] = newNode[0]
       //assigned relationship data to graphData and appended the newNode value
       drawGraph(graphData);
    })
  });
};

function updateGraph(){
 // clears out old graph
 d3.selectAll("svg > *").remove();
};

这就是我设置图表的方式。

function drawGraph(relationships){
 var svg = d3.select("svg"),
     w = +svg.attr("width"),
     h = +svg.attr("height);
 var g = svg.append("g");
 var color = d3.scaleOrdinal(d3.schemeCategory20);

 var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(60))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(w / 2, h / 2))
    .force("attraceForce",d3.forceManyBody().strength(-900));

 var opacity = 0.05;
 var transitionPeriod = 500;
 var graph = relationships;
 var link = g.selectAll("line")
 .data(graph.links)
    .enter().append("line")
    .style("stroke-width", function(d) { return d.value; })
    .style("stroke", "#999" )
    .style("opacity", "1")
    .attr("group",function(d) {return d.group; })
    .on("click", function(d) {
        // This is to toggle visibility - need to do it on the nodes, links & text
        d3.selectAll("line:not([group='"+d.group+"'])")
        .transition().duration(transitionPeriod).style("opacity", function() {
            var currentDisplay = d3.select(this).style("opacity");
            currentDisplay = currentDisplay == "1" ? opacity : "1";
            return currentDisplay;
        });
        d3.selectAll("circle:not([group='"+d.group+"'])")
        .transition().duration(transitionPeriod).style("opacity",function() {
            var currentDisplay = d3.select(this).style("opacity");
            currentDisplay = currentDisplay == "1" ? opacity : "1";
            return currentDisplay;
        });
        d3.selectAll("text:not([group='"+d.group+"'])")
        .transition().duration(transitionPeriod).style("opacity",function() {
            var currentDisplay = d3.select(this).style("opacity");
            currentDisplay = currentDisplay == "1" ? opacity : "1";
            return currentDisplay;
        });

    })

   var node = g
     .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("r", 14)
    .attr("fill", function(d) { return color(d.group); })
    .call(d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended))

  var images = g.selectAll("image")
    .data(graph.nodes)
    .enter().append("image")
    .attr("xlink:href",function(d){
      var type = d.type,
          typeIcon = "",
       switch(type){
       //assigns an image based on the subject type person, address, phone, ect.
       }
       return typeIcon;
    })
  // This is the label for each node
    var text = g.selectAll("text")
        .data(graph.nodes)
        .enter().append("text")
        .attr("dx",12)
        .attr("dy",".35m")
        .text(function(d) { return d.id;})
        .attr("text-anchor", "middle")
      .attr("group",function(d) {return d.group;} ) ;

  node.append("title")
      .text(function(d) { return d.id; });

  simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(graph.links);

  function ticked() {
    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; });
    text
         .attr("x", function(d) { return d.x; })
         .attr("y", function(d) { return d.y; });
  }
});



//Used to drag the graph round the screen
function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

// This is the zoom handler
var zoom_handler = d3.zoom()
  .scaleExtent([1/4, 4])
  .on("zoom", zoom_actions);

//specify what to do when zoom event listener is triggered
function zoom_actions(){
    g.attr("transform", d3.event.transform);
}

// initial scaling on the svg container - this means everything in it is scaled as well
svg.call(zoom_handler)
.call(zoom_handler.transform, d3.zoomIdentity.scale(0.9,0.9))
;

zoom_handler(svg);
};

我的 ajax 数据看起来像这样

{
 "nodes":[
  {"id": "1", "group": "1", "type": "person", "name":"Jon Doe"},
  {"id": "2", "group": "1", "type": "person", "name":"Jane Doe"}
  //ect list of ~50
 ],
 "links":[
  {"source": "1", "target":"2"},
  //ect list of ~50
 ]
}

我希望有更多 d3.js 经验的人可以为我指明正确的方向。

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    我把这个贴在那里以防其他人遇到同样的问题。我通过将 drawGraph 函数分解为更小的小部件来解决我的问题。

    我将以下内容移至父范围。

    var svg = d3.select("svg"),
          w = +svg.attr("width"),
          h = +svg.attr("height),
          node,
          link;
    
    var simulation = d3.forceSimulation(nodes)
        .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(60))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(w / 2, h / 2))
        .force("attraceForce",d3.forceManyBody().strength(-900));
    
    var color = d3.scaleOrdinal(d3.schemeCategory20);
    

    然后在 drawGraph 函数中我进行了以下更改。

    function drawGraph(nodes,links){
       var g = svg.append("g");
       link = g.selectAll(".link").data(links,function(d){ return d.target.id; })
       link = link.enter()
            .append("line")
            .attr("class","link")
            .style("stroke-width", function(d) { return d.value; })
            .style("stroke", "#999")
    
       node = g.selectAll(".node").data(nodes,function(d){ return d.id; })
       node = node.enter()
            .append("g")
            .attr("class","node")
            .call(d3.drag()
              .on("start", dragstarted)
              .on("drag", dragged)
              .on("end", dragended))
       node.append("circle").attr("r", 14).attr("fill",function(d){return color(d.group);})
       node.append("text").attr("dy", -15)
           .text(function(d){ return d.id; })
           .attr("text-anchor","middle")
           .style("fill", "#555");
    
       node.append("image")
           .attr("xlink:href",function(d){
              var type = d.type,
              typeIcon = "",
              switch(type){
                //assigns an image based on the subject type person, address, phone, ect.
               }
           return typeIcon;
          })
          .attr("x", -8)
          .attr("y", -8)
          .attr("height", 16)
          .attr("width", 16);
    
     simulation.nodes(nodes).on("tick", ticked);
     simulation.force("link").links(links);
    
     function zoom_actions(){
        g.attr("transform", d3.event.transform);
     };
    
     var zoom_handler = d3.zoom()
      .scaleExtent([1/4, 4])
      .on("zoom", zoom_actions);
    
     svg.call(zoom_handler).call(zoom_handler.transform, d3.zoomIdentity.scale(0.9,0.9));
    
    zoom_handler(svg);
    
    };
    
    function ticked() {
     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("transform", function(d) { 
               return "translate("+ d.x + ", " + d.y + ")"; 
          });
    };
    
     function dragstarted(d) {
      if (!d3.event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }
    
    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }
    
    function dragended(d) {
      if (!d3.event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }
    

    然后我添加了以下用于设置数据和绘制图形的函数。

    function formatGraphData(relationships){
     nodes = relationships.nodes;
     links = relationships.links;
     simulation.alpha(0.5).restart(); //<- needed to restart simulation and position nodes
     drawGraph(nodes,links);
    }
    

    然后,ajax 调用被更新为使用 formatGraphData 而不是 drawGraph。

    我将以下内容添加到我的 css 文件中

    .links line{
     stroke: #999;
    }
    .nodes circle{
     stroke: #fff;
     stroke-width: 1.5px;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-01
      • 2017-11-12
      • 1970-01-01
      • 2018-02-18
      • 2013-08-16
      相关资源
      最近更新 更多