D3.JS force layout graph dynamic update

2,083 views
Skip to first unread message

Siva Kumar A.P

unread,
Apr 8, 2016, 10:28:43 AM4/8/16
to d3-js

Requirement is to filter the nodes and links based on the Button click event. Initial loading process works fine, but button click rendering process is failing.

Created a separate method for rendering process. Initial data is loaded in Json file (data.json).


<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
  stroke: #B8B0B4;  
}
</style>
<body>
<div style="font-size:12px;font-family:Comic Sans MS"><input type="Button" value="1958" onclick="Update_Node()"></input></div></center>
<script>

var width = 800,
    height = 700,
    radius = 6;

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-240)
    .gravity(7)
    .linkDistance(30)
    .size([width, height]);     

var svg = d3.select("div.graph").append("svg")
    .attr("width", width)
    .attr("height", height);

var edges = [];
var nodes = [];
var Updatededges = [];
var Updatednodes = [];

function Update_Node()
{
    console.log("Inside");
    edges=[];       
    nodes.forEach(function(e) { 
    if(e.PersonId > 1000)   
        Updatednodes.push(e)    
    });
    console.log(Updatednodes.length);
    force.stop()
    render(nodes,edges)
    force
      .nodes(nodes)
      .links(edges)
      .start();
}

d3.json("data.json", function(error, graph) {
  if (error) throw error;
    graph.links.forEach(function(e) { 
    var sourceNode = graph.nodes.filter(function(n) { return n.PersonId === e.source; })[0],
    targetNode = graph.nodes.filter(function(n) { return n.PersonId === e.target; })[0];            
    edges.push({source: sourceNode, target: targetNode, weight : e.weight});
    });   
    nodes = graph.nodes;
    console.log(nodes.length);
    force.stop()
    render(graph.nodes,edges)
    force
      .nodes(graph.nodes)
      .links(edges)
      .start();

});
function render(nodelist,links) {

  var link = svg.selectAll(".link")
      .data(links)
      .enter().append("line")
      .attr("class", "link")
      .style("stroke-opacity",  0.5)
      .style("stroke-width", function(d) { return 1})

  var node = svg.selectAll("g.node")
      .data(nodelist)
      .enter().append("g")
      .classed('node', true);

  node.append("circle")
      .attr("class", "node")
      .attr("r", function(d) { return d.Weight; })    
      .style("fill",  "#889CB0")
      .style("stroke", "#382E44")
      .style("stroke-width", 0.05)
      .call(force.drag);         

  node.append("title")
      .text(function(d) { return d.Forename+" "+d.Surname+" ("+d.Weight+")"; });

  node.append("text")
      .attr("dx", 12)
      .attr("dy", ".35em")
      .style("font-weight", "bold")
      .style("fill", d3.rgb(29,23,23))
      .style("fill", d3.rgb(29,23,23))
      .text(function(d) { return d.Name})

  force.on("tick", function() {
    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] + ')'; 
    });
  });
}
</script>
</body>

Curran

unread,
Apr 9, 2016, 10:36:02 AM4/9/16
to d3-js
Hello,

Your code is only handling the enter() case, but needs to also handle update and exit(). The enter() case is only triggered when DOM elements get added corresponding to data elements the first time. You need to modify your code to use enter(), exit(), and update phases to deal with data elements coming and going. You'll also need to pass the new set of nodes and edges into the force layout. Here's some example code that does these things:

force
  .nodes(graph.nodes)
  .links(graph.links)
  .start();

link = linkG.selectAll('.link').data(graph.links);
link.enter().append('line').attr('class', 'link');
link.style('stroke-width', function(d) { return Math.sqrt(d.value); });
link.exit().remove();

node = nodeG.selectAll('.node').data(graph.nodes);
node.enter().append('circle')
  .attr('class', 'node')
  .attr('r', 5)
  .append('title');
node
  .style('fill', function(d) { return color(d.group); })
  .call(force.drag);
node.exit().remove();
node.select('title').text(function(d) { return d.name; });



Hope this is helpful to you.

All the best,
Curran


...
Message has been deleted

Siva Kumar A.P

unread,
Apr 12, 2016, 6:24:12 PM4/12/16
to d3-js
Thanks Curran, It works like a charm.
stuck with the next issue, I am not getting the node text in graph. It would be great, if you could help.

function render(nodelist,links,gravity) {  
  force
      .nodes(nodelist)
      .links(links)
      .start();
  var link = svg.selectAll(".link").data(links);

  link.enter().append("line").attr("class", "link");

  link.style("stroke-opacity", 0.5)
      .style("stroke-width", function(d) { return 1})
  .style("stroke", function(d) { if (d.target.PersonId==10001) return "#BFA915";});  

  link.exit().remove();

  var node = svg.selectAll(".node").data(nodelist);

  node.enter().append("circle")     
  .attr("class", "node")
  .append('title');
node
      .attr("r", function(d) { return d.Weight; });

node   
      .style("fill",  function(d) {if (d.Type=="2") return d.NodeColor; if (d.Gender == "Male") return "#889CB0"; else if (d.Gender == "Female") return "#851B1B"; else return "#BFA915"})
  .style("stroke", "#382E44")
  .style("stroke-width", 0.05)
  .call(force.drag);           

  node.select("title")
      .text(function(d) { return d.Forename+" "+d.Surname+" ("+d.Weight+")"; });  

  node.append("text")
      .attr("dx", 12)
      .attr("dy", ".35em")
  .style("font-weight", "bold")
  .style("fill", d3.rgb(29,23,23))
  .style("fill", d3.rgb(29,23,23))
      .text(function(d) { return d.Name});
  node.exit().remove();
  
  force.on("tick", function() {
    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] + ')'; 
    });
  });
}


Regards,
Siva

Curran

unread,
Apr 14, 2016, 2:26:49 AM4/14/16
to d3-js
Great! You are very close. I think what you need to do now is use the same pattern that you're using for "title" with "text". Meaning, the append needs to be moved to inside enter(), and the update needs to use "select" to access the appended element.

  // Here's the difference:
  // Only append the text inside enter().
  // This may or may not actually append elements, depending on if data was added since the previous invocation.
  // Set the values here that do not change.
  node.enter().append("text")
      .attr("dx", 12)
      .attr("dy", ".35em")
      .style("font-weight", "bold")
   .style("fill", d3.rgb(29,23,23))
     .style("fill", d3.rgb(29,23,23));

  // Access the text element that was appended previously.
  // Set the values here that change with the data.
  node.select("text")
      .text(function(d) { return d.Name});

I haven't tested this code. If you can post your full example on bl.ocks.org (maybe using Blockbuilder) or CodePen, I'm happy to take a look and make sure it really works.

Regards,
Curran

Siva Kumar A.P

unread,
Apr 14, 2016, 9:52:51 PM4/14/16
to d3-js
No luck Curran., Still not getting the text. Also edges are rendering on top of the node. How to avoid that situation?
 
Webpage has two buttons - 1964 and 1965 (ON/OFF buttons). If you click"1964" twice, edges will render on top of the node. (PFB screenshot)
 
Uploaded my code here.
 




 

Curran

unread,
Apr 15, 2016, 9:24:18 AM4/15/16
to d3-js
Hello,

To avoid having edges on top of the nodes, you can use two <g> elements, one for containing the nodes and one for containing the edges. If you add the group for the edges first, then the group for the nodes, then the nodes will always appear on top of the edges. Currently, your code is adding <circle> and <line> elements directly to the SVG, so the ordering of them determines which is drawn on top.

For adding text to the nodes, please have a look at this example https://bl.ocks.org/mbostock/950642 . You'll need to use <g> elements for each node, then append <circle> and <text> elements to them.

By the way, if you rename "POC.html" to "index.html" in your Gist, then the program will display correctly at http://bl.ocks.org/csesivakumar/31e23b875f260622f8f7da3ae380958c

Regards,
Curran

Siva Kumar A.P

unread,
Apr 17, 2016, 9:07:56 PM4/17/16
to d3-js
Thanks Curran

d3.selectAll("svg > *").remove();  -- Resolved the edges and nodes override issue, Removing all the DOM elements from SVG and creating the complete graph once again


Added Circle in group to create nodes with text. 

var node = svg.selectAll("g.node").data(nodelist);
node.enter().append("g")     
 .attr("class", "node")  
 .attr('id', function(d){ return d.PersonId; })
 .call(force.drag);   
   
node.append("circle")  
      .attr("r", function(d) { return d.Weight; })
      .style("fill",  function(d) {if (d.Type=="2") return d.NodeColor; if (d.Gender == "Male") return "#889CB0"; else if (d.Gender == "Female") return "#851B1B"; else return "#BFA915"})
 .style("stroke", "#382E44")
 .style("stroke-width", 0.05);          
  
node.append("text")
      .attr("dx", 12)
      .attr("dy", ".35em")   
Reply all
Reply to author
Forward
0 new messages