Gene Golovchinsky
Gene Golovchinsky

Reputation: 6131

Constraining d3 force layout graphs based on node degree

I have a force layout with potentially a very large number of nodes, too large for the graph to render responsively. I was thinking that one way to improve the performance of the system was to prune the graph by eliminating nodes based on in- and out-degree when the number of nodes gets too large.

Recomputing the node and link lists is a bit of a nuisance because links are related to indexes in the node array, and so all the links would need to be re-built.

It seems more elegant to be able to mark individual nodes for exclusion (analogously to the way some nodes are fixed) and have the layout algorithm skip those nodes. This would allow me to dynamically select subsets of the graph to show, while preserving as much state for each node (e.g., position) as practical.

Has anyone implemented something like this?

UPDATE:

I tried to implement the filter suggestion, but ran into an interesting error. It appears that the filter method returns an object that does not implement enter:

qChart apply limit:2
NODES BEF: [Array[218], enter: function, exit: function, select: function, selectAll: function, attr: function…]
NODES AFT: [Array[210], select: function, selectAll: function, attr: function, classed: function, style: function…]
Uncaught TypeError: Object [object Array] has no method 'enter' 

The following code is run to get from BEF to AFT:

nodeSubset = nodeSubset.filter(function(n) { return (n.sentCount() <= limit); });   

UPDATE 2:

I created a jsfiddle to isolate the problem. This example implements my interpretation of ChrisJamesC's answer. When I tried to implement his suggestion directly (putting the filter after the data), the subsequent call to enter failed because the object returned by filter did not have enter defined.

The goal is to make the layout select only those nodes that have active == true, so in this example, this means that node b should be excluded.

Upvotes: 2

Views: 3813

Answers (1)

Christopher Chiche
Christopher Chiche

Reputation: 15345

You can use the selection.filter() option combined with the node.weight attribute.

What you would normally do is:

var node = svg.selectAll(".node")
    .data(graph.nodes)
    .enter().append("circle")

Here you can do:

var node = svg.selectAll(".node")
    .data(graph.nodes)
    .filter(function(d){return d.weight>3})
    .enter();

You might also have to remove from drawing the links going to these nodes using the same method.

EDIT You should just filter the data you provide if you want to mark nodes as active directly in the data array (and do the same for links)

var node = svg.selectAll(".node")
    .data(force.nodes().filter(function(d) { return d.active; }));

var link = svg.selectAll(".link")
      .data(force.links().filter(function(d) { 
          var show =  d.source.active && d.target.active;
          if (show)
              console.log("kept", d);
          else
              console.log("excluded", d);
          return show;
      }) );

Fiddle

If you want to do this by computing the weight of each node, I would still recommend you to do this before passing the nodes and links to the graph and mark nodes as active or not following a specific criteria, then filter the links according to active nodes. Otherwise you would have to load the whole force directed layout only to get the weight to then filter the data to re-load the force directed graph.

Upvotes: 2

Related Questions