user1934426 user1934426 - 3 months ago 12
jQuery Question

D3 Force Layout: Collapse Subset of Child Nodes (Opacity Change)

I am using a D3 force layout and trying to collapse child nodes whilst keeping the graph fixed, e.g. by simply changing them and their links' opacity. However, I am not trying to just collapse all nodes at once - I actually have an attribute in each node called "group", which can be 1,2 or 3.

When I click on a node, a tooltip appears with 3 buttons for each of these groups - upon clicking on the correct button, I would like the child node of that type to collapse, but all of its children (of all 3 groups) to collapse as well.

Here is the fiddle.

I have so far tried to create arrays of the node group ids and corresponding link IDs, and then use JQuery to hide those DOM objects but this didn't work:

function collapseNodes(node, collapseType, isCollapsed) {
var collapseData = minimise(node, collapseType);
var cNodes = collapseData.nodes,
cLinks = collapseData.links;
console.log(collapseData);
var newClass = isCollapsed ? "uncollapsed" : "collapsed";

cNodes.forEach(function(n) {
d3.select(n).style("opacity", 0);
});

cLinks.forEach(function(l) {
d3.select(l).style("opacity", 0);
});
}

function minimise(node, assetMinType = "") {
// Function to minimise a node
var minNodes = [];
var minLinks = [];

if (node.group == 'asset') {
node.children.forEach(function(child) {
if (child.group == assetMinType)
minimiseRec(child, node.id);
});
}
else {
minimiseRec(node, "");

// We want to keep the top node and link
minNodes.shift();
minLinks.shift();

}

function minimiseRec(node, parentID) {
minNodes.push("#" + node.id);
minLinks.push("#parent_" + parentID + "_child_" + node.address);

node.children.map(function(child) {
minimise(child, node.id);
});
}

return { nodes: minNodes, links: minLinks };
}


Does anyone know how best to do this?

Thanks.

Answer

If its only the opacity you wish to change this is fairly simple. I guess if you hide a nodes children you want to hide their childrens children and so on ?

First I set a variable d.hiddenNode. This is so I can toggle the opacity. I set it to false. And then on click function :

.on('click', function(d) {
    console.log(d);
    if(d.hiddenNode){
        hideChildren(d, false);
      d.hiddenNode = false;
    } else {
        hideChildren(d, true);
      d.hiddenNode = true;
    }

  })

Now because you want the childrens children etc to be hidden you need a recursive function like so. One that hides links and nodes ( I have commented to explain) :

function hideChildren(node, hide) {

  for (var i = 0; i < node.children.length; i++) {
    recurseChildren(node.children[i]); //loop through children to hide
  }

  function recurseChildren(node) {

    nodeEnter.each(function(d) { //go through all nodes to check for children
      if (d.index == node.index) { //if child is found
        d3.select(this).style('opacity', function(){ 
        return hide ? 0 : 1;        //toggle opacity depending on d.hiddenNode value
        }) //.remove(); 
      }
    })

    link.each(function(d) { //same with links
      if (d.source.index == node.index || d.target.index == node.index ) { //if source or target are hidden hide the link
        d3.select(this).style('opacity', function(){
        return hide ? 0 : 1;        
        }) //.remove(); 
      }
    })   

    if (node.children) { //if this node has children, call again but with their children
      for (var i = 0; i < node.children.length; i++) {
        recurseChildren(node.children[i]);
      }
    }
  }
}

Here is the updated fiddle : http://jsfiddle.net/thatOneGuy/3g4fqfa8/2/

EDIT

For the pop up i just created a div that contains the 3 groups, 1,2,3 :

<div id='groupChoice'>
<div id='group1' class = 'group' onclick='hideGroup(1)'>1</div>
<div id='group2' class = 'group' onclick='hideGroup(2)'>2</div>
<div id='group3' class = 'group' onclick='hideGroup(3)'>3</div>
</div>

Set this to hidden in CSS :

.group{
  width: 50px;
  height:50px;
  border : 1px solid black;
}

#groupChoice{
  visibility:hidden;
}

I have changed the logic slightly but this is the updated fiddle with commentary : http://jsfiddle.net/thatOneGuy/3g4fqfa8/3/

Theres some work that needs to be done with the links. I havent got enough time atm to finish it off, im sorry. But that should get you started.

Basically, when you click a node, pop up comes up, you chose a group to hide, they get hidden. Hit the same node again, chose a node to show. This was done super quick, so there's a lot wrong with it, but the basics are the so it should help :)

Comments