Search code examples
javascriptd3.jsthree.jsd3-force-directed

How do I influence link distance in 3d-force-graph?


I am trying out the 3d Force Graph in this package and I am looking for ways to influence bond strength between nodes. Either link width or length would be good, but I don't see anything in API that allows me to control or influence either. What is the correct field of Graph to pass in link strenghts, one for each link?


Solution

  • Here is a modified version of this example which applies a custom distance per link:

    const N = 300;
    const gData = {
      nodes: [...Array(N).keys()].map(i => ({ id: i })),
      links: [...Array(N).keys()]
        .filter(id => id)
        .map(id => {
    
          var distance = (Math.random() < 0.2) ? 75 : 250;
          var width = (distance == 75) ? 4 : 0.2;
    
          return ({
            source: id,
            target: Math.round(Math.random() * (id-1)),
            width: width,
            distance: distance
          })
        })
    };
    
    const Graph = ForceGraph3D()
      (document.getElementById('3d-graph'))
        .graphData(gData)
        .linkWidth('width')
        .cameraPosition({ z: 600 })
        .d3Force("link", d3.forceLink().distance(d => d.distance))
        .d3Force("charge", d3.forceManyBody().theta(0.5).strength(-1));
    <head>
      <style> body { margin: 0; } </style>
    
      <script src="//unpkg.com/3d-force-graph"></script>
      <script src="https://d3js.org/d3.v4.min.js"></script>
    </head>
    
    <body>
      <div id="3d-graph"></div>
    </body>


    In fact, to allow users to modify links parameters, the library simply uses the d3 force API. The only difference is the name of the accessor in the 3d-force-graph library which is .d3Force() (instead of .force() in d3).

    For instance, to modify the distance per link, we can add a distance attribute to each link data point (in addition to source and target) and then modify this distance as follow in the force layout (using the d3Force accessor):

    .d3Force("link", d3.forceLink().distance(d => d.distance))
    

    whereas in a d3 force layout we would have used:

    .force("link", d3.forceLink().distance(d => d.distance))
    

    which gives us:

    const Graph = ForceGraph3D()
      (document.getElementById('3d-graph'))
        .graphData(gData)
        .linkWidth('width')
        // The link distance modification:
        .d3Force("link", d3.forceLink().distance(d => d.distance))
        // To make it prettier:
        .d3Force("charge", d3.forceManyBody().theta(0.5).strength(-1));
    

    The strength can be adjusted the same way, using:

    .d3Force("link", d3.forceLink().strength(d => d.strength))
    

    In the example, I have given each link a distance which is randomly either 75 or 250. And to make it clear, I have given a bigger width to links with a smaller distance. The distribution of nodes in my demo is not perfect, and might need additional tuning.