4

I'm using d3's force-layout and would like to move nodes on an event trigger.

I've created a a minimal block here showing the desired functionality.

As you can see, when the button is clicked, the nodes move rapidly to their new position. I'd like to slow this down. I've played with combinations of (what I thought to be) relevant attributes specified in the documentation (e.g. alpha, alphaDecay, velocityDecay), but to no avail.

To recap: how do I make the nodes move slower on position updates?

Thanks!

Jared Wilber
  • 6,038
  • 1
  • 32
  • 35

1 Answers1

8

You probably want to use a high velocity decay. A value of 0.9 will slow the ticks to 0.1 of their velocity, "after the application of any forces during a tick, each node’s velocity is multiplied by 1 - [velocity]decay.(docs)". This will certainly slow down movement. A value of 0.9 probably is overkill in most situations.

However, in conjunction with this, we need to ensure alpha decay is low: if alpha decay is too high, the simulation will have cooled prior to the nodes reaching their endpoint. I used 0.0005 in this example below,

Finally, to address the jumpiness of the simulation on recentering, I have lowered the alpha to reduce the appearance of jittering, of course the lower you move alpha, the lower alphaDecay must be to support a simulation cooling off period of the same duration

Used below:

  • alhpaDecay: 0.005,
  • velocityDecay: 0.6,
  • restart alpha: 0.1

const width = 500
    const height = 500
    const svg = d3.select("svg")

    const trtCenter = width / 5
 const cntrlCenter = width / 1.5
    
    let sampleData = d3.range(24).map((d,i) => ({r: 40 - i * 0.5}))
    
    // define force
    let force = d3.forceSimulation()
     .force('charge', d3.forceManyBody().strength(1))
     force.force('x', d3.forceX().strength(.3).x( width / 2))
  force.force('y', d3.forceY().strength(.3).y(height / 3.5))
     .force('collision', d3.forceCollide(d => 12))     
     .nodes(sampleData)
     .on('tick', changeNetwork)

    let dots = svg.selectAll('.dot')
     .data(sampleData)
     .enter()
     .append('g')
     .attr('class', 'dot')
     .attr('group', (d,i) => i % 2 == 0 ? 'trt' : 'ctrl')
     .append('circle')
     .attr('r', 10)
     .attr('fill', (d,i) => i % 2 == 0 ? 'pink' : 'olive')
     .attr('stroke', 'black')
     .attr('stroke-width', .4)

    function nodeTreatmentPos(d) {
   return d.index % 2 == 0 ? trtCenter : cntrlCenter;
 }


    function changeNetwork() {
      d3.selectAll('g.dot')
       .attr('transform', d=> `translate(${d.x}, ${d.y})`)
    }
  
    // 
 function moveNodes() {
   force.force('center', null)
  .force('collision', d3.forceCollide(d => 12))
  .alphaDecay(.0005)
  .velocityDecay(0.6)
  force.force('x', d3.forceX().strength(1).x(nodeTreatmentPos))
  force.force('y', d3.forceY().strength(1).y(height / 3.5))
        force.alpha(.1).restart();
 }

 // force for center
 function moveCenter() {
 force//.force('center', null)
  .force('collision', d3.forceCollide(d => 12))
  .alphaDecay(.0005)
  .velocityDecay(0.6)
  force.force('x', d3.forceX().strength(1).x( width / 2))
  force.force('y', d3.forceY().strength(1).y(height / 3.5))
  force.alpha(.1).restart();
  }

 // resolve locations of node on cliks
 let toCenter = true;

 d3.select('#clickMe')
  .on('click', function() {
   toCenter === true ? moveNodes() : moveCenter()
   toCenter = !toCenter
  })
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js'></script>
<button id="clickMe" type="button">Move Nodes</button>
<svg id="svg" width="1200" height="500"></svg>
Andrew Reid
  • 37,021
  • 7
  • 64
  • 83