Slate8
Slate8

Reputation: 139

Update existing nodes in a d3 Force Layout

I have created a force layout using d3 and it works well. My initial data is loaded from a json file and the chart is drawn with techniques that are similar to this d3.js example:

enter image description here

Now that the chart is on the screen I need to add, update and remove nodes on the fly from data I receive over a web-socket. I have add and remove methods working, but I can't find the correct way to update an existing node's properties.

From the reading I have undertaken I gather the correct technique is to change the data source, then update the chart by using the enter() method.

To update a node I am doing the following:

function updateNode(id, word, size, trend, parent_id){    
  var updateNode = nodes.filter(function(d, i) { return d.id == id ? this : null; });
  if(updateNode[0]){   
    updateNode.size = Number(size);
    updateNode.trend = trend;
    nodes[updateNode.index] = updateNode;      
    update();
  }
}

The update function then updates the nodes with:

function update(){
  node = vis.selectAll('.node')
  .data(nodes, function(d) {
    return d.id;
  })

  createNewNodes(node.enter());
  node.exit().remove();
  force.start();
}

function createNewNodes(selection){    
  var slct = selection.append('g')
  .attr('class', 'node')
  .call(force.drag);

  slct.append('circle')
    .transition()
    .duration(500)
    .attr('r', function(d) {
      if(d.size){
        return Math.sqrt(sizeScale(d.size)*40);
      }
   })
}

Am I taking the right approach to this? When I try this code the node I get as the datum when trying to set the radius attribute on the circle is the last node in the nodes array. I.e. the one that contains the hierarchical node data rather than a single node object.

Any pointers would be greatly appreciated, I've spent too much time on this :)

Upvotes: 11

Views: 5690

Answers (1)

Christopher Chiche
Christopher Chiche

Reputation: 15325

There are multiple points that you need. What I get from your questions is: 'how do I use the reusable pattern'

The simple answer to this question, would be to tell you to read this excellent tutorial from Mike Bostock: towards reusable charts

If you want to have more information, this selection of documents can be interesting:

Now, here is a draft of the implementations I would do for your particular issue:

function ForceGraph(selector,data) {
   // This is the class that will create a graph

   var _data = data  
   // Local variable representing the forceGraph data 

   svg = d3.select(selector)
      .append('svg')
   // Create the initial svg element and the utilities you will need. 
   // These are the actions that won't be necessary on update. 
   // For example creating the axis objects, your variables, or the svg container

   this.data = function(value) {
      if(!arguments.length) {
         // accessor
         return _data;
      }
      _data = value;
      return this;
      // setter, returns the forceGraph object
   }

   this.draw = function() {
      // draw the force graph using data

      // the method here is to use selections in order to do: 
      // selection.enter().attr(...) // insert new data
      // selection.exit().attr(...) // remove data that is not used anymore
      // selection.attr(...) // 

   }
}

var selector = "#graphId";
var data = {nodes: [...],links: [...]};
myGraph = new ForceGraph(selector,data);
// Create the graph object
// Having myGraph in the global scope makes it easier to call it from a json function or anywhere in the code (even other js files). 
myGraph.draw();
// Draw the graph for the first time 

// wait for something or for x seconds  

var newData = {nodes: [...],links: [...]};
// Get a new data object via json, user input or whatever
myGraph.data(newData);
// Set the graph data to be `newData`
myGraph.draw();
// redraw the graph with `newData`

As you can probably see, the goal is not to have a function to add a new node. The goal is to redraw the entire force directed graph by only updating or removing existing nodes and adding the new nodes. This way the drawing code is written only once, then only the data changes.

For further reading, this question was my goldmine when I first tackled this issue: Updating links on a force directed graph from dynamic json data

Upvotes: 8

Related Questions