d3 v4添加新节点以强制有向图

时间:2017-08-03 22:08:26

标签: javascript d3.js

我是d3.js的新手,我目前遇到了问题。我正在使用强制有向图来显示我的数据的关系。这应该允许用户将新节点添加到现有图形并在2个或更多节点之间绘制关系链接。我的警告是我的数据是从ajax调用填充的,我将其分配给变量并将其传递给生成图形的函数。数据的初始加载效果很好,一切都正常显示。我的问题是当用户单击按钮添加新节点时。在该操作上,我正在进行ajax调用以检索新的未链接关系以添加到图形中。我将新检索的数据添加到nodes数组并尝试重新绘制整个图形。但是我在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经验的人能指出我正确的方向。

1 个答案:

答案 0 :(得分:0)

我发布此信息以防其他人遇到同样的问题。我通过将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;
}