Aaron Bramson
Aaron Bramson

Reputation: 1288

How can I make convex hulls of multiple group network nodes in D3.js

I am trying to make network diagram in which the nodes are grouped by a property, and in which they are bundled by group with both location (force layout) and using a convex hull. Like this but without the click to collapse/expand. That model/code is a bit too complicated for a beginner to dissect and learn from.

My question can be simply put: is there example code of the network+grouped capability that includes the possibility of nodes in multiple groups?

Sometimes there will be only one node in a group, and the basic convex hull doesn't work for less than three. The above example has a fix for that (that I don't understand), and so does this one that creates invisible nodes and may be easier.

The groups can be nested at multiple layers. This tutorial provides all the info necessary to use d3.nest() to get data in a hierarchy. But it may not be a hierarchy, it could be that a node is in multiple groups and needs to be in the correct hulls and location. Some nodes will not be in any group, but would still be connected in the network.

I know that the forces in force layouts are additive, so it shouldn't be a problem to have both the groups and the links effecting the node locations in a simple way, but I haven't seen a simple example of this. My approach is to add network connections to the multi-foci example and see if I can get that to work.

As bonuses, the nodes shouldn't overlap and for the hierarchical elements the base layer nodes should be put around a circle so that the nodes that are in multiple groups are in between those groups like this. I mentioned these now because the desire for these features may effect the best solution to the above desiderata.

I will be working on this building up from this JSFiddle; I will continue to update and refine this question as I make progress on the code myself. I would appreciate any pointers to code that's closer than the collapsible one to what I want, caveats for the limitations/problems of D3 (like needing a work-around for convex hulls of 1-2 nodes), and code that provides one of these capabilities in an understandable and modular way toward the final goal. Really, any help is appreciated.

Upvotes: 4

Views: 1285

Answers (2)

Fede
Fede

Reputation: 41

This is a great example, many network libraries including cytoscape.js do not allow this kind of complex grouping. I have 2 questions:

  1. With reference to: " All that's left for this capability is to get the groupings to effect the force layout appropriately" would this highly impact larger networks ? This is basically allowing the layout algorithm to take into consideration groups ?

  2. I 'm trying to migrate your approach to d3 v4 and was just wondering if you had already tried to do that ?

Upvotes: 0

Aaron Bramson
Aaron Bramson

Reputation: 1288

Although this question didn't attract much attention over the past week, hopefully the answer will be useful to some people. I managed to use a function for the linkDistance that starts off large and shrinks depending on (1) whether the source and target are in the same collection, and (2) the level of that collection -- i.e., groups or subgroups or subsubgroups.

The basic solution idea came from the only example I could find of this capability. The actual linkDistance function I used is a bit of a simplistic approach, but one which does the job well and can be easily adapted to lots of other purposes for lots of other people (I hope). Depending on the groupings you have you may want to adjust other features of the links (like color) via functions like the ones I used here.

force
    .nodes(graph.nodes)
    .links(graph.links)
    .linkDistance(function(thisLink) { 
        var myLength = 100, theSource = thisLink.source, theTarget = thisLink.target;
        groupNodes.forEach(function(groupList) { 
            if (groupList.indexOf(theSource) >= 0 && groupList.indexOf(theTarget) >= 0) {
                myLength = myLength * 0.7;
            }
        });
        subgroupNodes.forEach(function(groupList) { 
            if (groupList.indexOf(theSource) >= 0 && groupList.indexOf(theTarget) >= 0) {
                myLength = myLength * 0.4;
            }
        });
        subsubgroupNodes.forEach(function(groupList) { 
            if (groupList.indexOf(theSource) >= 0 && groupList.indexOf(theTarget) >= 0) {
                myLength = myLength * 0.2;
            }
        });
        return myLength; } )
    .linkStrength(function(l, i) { return 1; } )
    .gravity(0.05)   
    .charge(-600)   
    .friction(0.5)  
    .start();

A version of this solution, along with other improvements, can be found at this JSFiddle.

Although this answers my question by adjusting the link forces by the group membership, it doesn't combine forces of links + groups in the way that I intended originally. I would still like the ability to give the subsubgroups the "desire" to move away from each other into corners, but constrained by the edges that connect them (rather then forcing them to be on a circle layout).

Note that my groups are defined exogenously as lists of lists. If the group membership in your case is a node property, then you can use the d3.nest() function to produce groups of nodes that I had to create with a map function, but I don't think that will work if the groups have overlapping members.

Upvotes: 4

Related Questions