title: How to make d3js force directed graph less shaky when a node is dragged?
body: <p>I want my force directed graph to stay calm when I drag one node to another point. At the moment, dragging a small node far from the center can cause the whole graph to shake uncontrollably. Which could be a mess since the nodes are plenty (over 100)...</p>
  
  <p>Here is a short video depicting the problem: <a href="https://gfycat.com/GleamingMellowHypacrosaurus" rel="nofollow noreferrer">https://gfycat.com/GleamingMellowHypacrosaurus</a></p>
  
  <p>I know about the method of <a href="https://stackoverflow.com/questions/13463053/calm-down-initial-tick-of-a-force-layout">setting the coordinate of all the nodes</a>, but it's not possible since the nodes are too many and may increase in later times.</p>
  
  <p>Here is my code:</p>
  
  <pre class="lang-js prettyprint-override"><code>function getNeighbors(node) {
      return links.reduce(function(neighbors, link) {
          if (link.target.id === node.id) {
              neighbors.push(link.source.id)
          } else if (link.source.id === node.id) {
              neighbors.push(link.target.id)
          }
          return neighbors
      }, [node.id])
  }
  
  function isNeighborLink(node, link) {
      return link.target.id === node.id || link.source.id === node.id
  }
  
  
  function getNodeColor(node, neighbors) {
      // If is neighbor
      if (Array.isArray(neighbors) &amp;&amp; neighbors.indexOf(node.id) &gt; -1) {
          return 'rgba(251, 130, 30, 1)'
          // return node.level === 1 ? '#9C4A9C' : 'rgba(251, 130, 30, 1)'
      } else {
          // Check the node level
          if (node.level === 0) {
              return '#E72148'
          } else if (node.level === 1) {
              return '#9C4A9C'
          } else {
              return '#D8ABD8'
          }
      }
      //return node.level === 0 ? '#91007B' : '#D8ABD8'
  }
  
  function getLinkColor(node, link) {
      return isNeighborLink(node, link) ? 'rgba(251, 130, 30, .85)' : 'rgba(251, 130, 30, 0.25)'
  }
  
  function getTextColor(node, neighbors) {
      return Array.isArray(neighbors) &amp;&amp; neighbors.indexOf(node.id) &gt; -1 ? '#333' : '#bbb'
  }
  
  function getLabelColor(node, link) {
      return isNeighborLink(node, link) ? 'rgba(51, 51, 51, .9)' : 'rgba(51, 51, 51, 0)'    // #333
  }
  
  var width = window.innerWidth
  var height = window.innerHeight
  
  var svg = d3.select('svg')
  // svg.attr('width', width).attr('height', height)
  svg.attr("width", '100%')
      .attr("height", '500px')
      .attr('viewBox', '250 0 800 600')
      //.attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height))
      .attr('preserveAspectRatio', 'xMidYMid')
      .append("g")
      .attr("transform", "translate(" + Math.min(width, height) / 2 + "," + Math.min(width, height) / 2 + ")");
  
  //add zoom capabilities
  var zoom_handler = d3.zoom()
      .scaleExtent([1 / 2, 8])
      .on("zoom", zoom_actions);
  
  zoom_handler(svg);
  
  function zoom_actions() {
      g.attr("transform", d3.event.transform)
  }
  
  function button_zoom_in() {
      zoom_handler.scaleBy(svg, 2);
  }
  
  function button_zoom_out() {
      zoom_handler.scaleBy(svg, 0.5);
  }
  
  // simulation setup with all forces
  var linkForce = d3
      .forceLink()
      .id(function(link) {
          return link.id
      })
      // Alternative: using the distance from the data "strength"
      //.distance(50).strength(function (link) { return link.strength })
      // If don't want to use this, use default here:
      .distance(50).strength(.7)
  
  var simulation = d3
      .forceSimulation()
      .force('link', linkForce)
      .force('charge', d3.forceManyBody().strength(-1500))
      .force('radial', d3.forceRadial(function(d) {
          return d.level * 50
      }, width / 2, height / 2))
      .force('center', d3.forceCenter(width / 2, height / 2))
  
  var dragDrop = d3.drag().on('start', function(node) {
      node.fx = node.x
      node.fy = node.y
  }).on('drag', function(node) {
      simulation.alphaTarget(0.7).restart()
      node.fx = d3.event.x
      node.fy = d3.event.y
  }).on('end', function(node) {
      if (!d3.event.active) {
          simulation.alphaTarget(0)
      }
      node.fx = null
      node.fy = null
  })
  
  function selectNode(selectedNode) {
      var neighbors = getNeighbors(selectedNode)
  
      // we modify the styles to highlight selected nodes
      nodeElements.attr('fill', function(node) {
          return getNodeColor(node, neighbors)
      })
      textElements.attr('fill', function(node) {
          return getTextColor(node, neighbors)
      })
      linkElements.attr('stroke', function(link) {
          return getLinkColor(selectedNode, link)
      })
      labelElements.attr('fill', function(link) {
          return getLabelColor(selectedNode, link)
      }).attr("style", "-webkit-text-stroke: 1px rgba(255, 255, 255, 0.75); text-shadow: -1px -1px 0 rgba(255, 255, 255, 0.75), 1px -1px 0 rgba(255, 255, 255, 0.75), -1px 1px 0 rgba(255, 255, 255, 0.75), 1px 1px 0 rgba(255, 255, 255, 0.75)")
  
  }
  
  // Format the numbers to dots e.g. 100000 =&gt; 100.000
  function commafy( num ) {
      var str = num.toString().split('.');
      if (str[0].length &gt;= 5) {
          str[0] = str[0].replace(/(\d)(?=(\d{3})+$)/g, '$1.');
      }
      if (str[1] &amp;&amp; str[1].length &gt;= 5) {
          str[1] = str[1].replace(/(\d{3})/g, '$1 ');
      }
      return str.join('.');
  }
  
  
  // Enables zooming
  var g = svg.append("g")
      .attr("class", "everything");
  // Enables zooming end
  
  // Create circling orbit
  var circles = g.selectAll(null) // use g.selectAll instead of svg.selectAll to enable zoom
      .data([200, 350]) // sets the circle radius
      .enter()
      .append("circle")
      .attr("cx", width / 2)
      .attr("cy", height / 2)
      .attr("r", d =&gt; d)
      .style("fill", "none")
      .style("stroke", "#ddd");
  
  var linkElements = g.append("g") // use g.append instead of svg.append to enable zoom
      .attr("class", "links")
      .selectAll("line")
      .data(links)
      .enter().append("path")
      .attr("id", function(d, i) {
          return "linkId_" + i;
      })
      .attr("stroke-width", function(link) {
          var linkValueNormalize = link.value / 100;
          var linkValueNormalize = Math.ceil(linkValueNormalize);
          if (linkValueNormalize &gt;= 201) {
              return 26;
          } else if (linkValueNormalize &gt;= 101 &amp;&amp; linkValueNormalize &lt;= 200) {
              return 20;
          } else if (linkValueNormalize &gt;= 71 &amp;&amp; linkValueNormalize &lt;= 100) {
              return 16;
          } else if (linkValueNormalize &gt;= 41 &amp;&amp; linkValueNormalize &lt;= 70) {
              return 12;
          } else if (linkValueNormalize &gt;= 21 &amp;&amp; linkValueNormalize &lt;= 40) {
              return 8;
          } else if (linkValueNormalize &gt;= 11 &amp;&amp; linkValueNormalize &lt;= 20) {
              return 12;
          } else if (linkValueNormalize &gt;= 7 &amp;&amp; linkValueNormalize &lt;= 10) {
              return 8;
          } else if (linkValueNormalize &gt;= 3 &amp;&amp; linkValueNormalize &lt;= 6) {
              return 4;
          } else {
              return 2;
          }
          // return linkValueNormalize;
      })
      .attr("stroke", "rgba(251, 130, 30, 0.5)")
  
  var labelElements = g.append("g")
      .attr("class", "label")
      .selectAll("text")
      .data(links)
      .enter().append("text")
      .attr("font-size", 10)
      .attr("font-family", "sans-serif")
      .attr("fill", "rgba(51, 51, 51, 0)")  // #333
      .attr("x", "70")
      .attr("y", "-20")
      .attr("text-anchor", "start")
      .append("textPath")
      .attr("xlink:href", function(d, i) {
          return "#linkId_" + i;
      })
      .text(function(link) {
            var linkValueNormalize = link.value; 
            var linkValueNormalize = commafy(linkValueNormalize);
  
            return "Rp "+ linkValueNormalize +" M";
  
      })
  
  var nodeElements = g.append("g") // use g.append instead of svg.append to enable zoom
      .attr("class", "nodes")
      .selectAll("circle")
      .data(nodes)
      .enter().append('a')    // Append a first, then circle
      .attr("xlink:href", function(node){return node.url;})
      .attr("target", "_blank")
      .append("circle")
      .attr("r", function(dat, index, n) {
          var linkItem = links.find(function(link) {
              return link.target == dat.id;
          });
  
          var radius = 26;
          var linkValueNormalize = (linkItem &amp;&amp; linkItem.value) / 100; // in milyar
          var linkValueNormalize = Math.ceil(linkValueNormalize);
  
          if (linkValueNormalize &gt;= 201) {
              radius = 24;
          } else if (linkValueNormalize &gt;= 101 &amp;&amp; linkValueNormalize &lt;= 200) {
              radius = 22;
          } else if (linkValueNormalize &gt;= 71 &amp;&amp; linkValueNormalize &lt;= 100) {
              radius = 18;
          } else if (linkValueNormalize &gt;= 41 &amp;&amp; linkValueNormalize &lt;= 70) {
              radius = 14;
          } else if (linkValueNormalize &gt;= 21 &amp;&amp; linkValueNormalize &lt;= 40) {
              radius = 10;
          } else if (linkValueNormalize &gt;= 11 &amp;&amp; linkValueNormalize &lt;= 20) {
              radius = 14;
          } else if (linkValueNormalize &gt;= 7 &amp;&amp; linkValueNormalize &lt;= 10) {
              radius = 10;
          } else if (linkValueNormalize &lt;= 6) {
              radius = 6;
          }
  
          if (dat.level === 0) {
              radius = 26;
          }
  
          return radius;
      })
      .attr("fill", getNodeColor)
      .attr("stroke", "#fff")
      .attr('stroke-width', 2)
      .call(dragDrop)
      .on('mouseover', selectNode)
  
  var textElements = g.append("g") // use g.append instead of svg.append to enable zoom
      .attr("class", "texts")
      .selectAll("text")
      .data(nodes)
      .enter().append("text")
      .text(function(node) {
          return node.label
      })
      .attr("font-size", 10)
      .attr("font-family", "sans-serif")
      .attr("text-anchor", "middle")
      .attr("fill", "#333")
      .attr("style", "font-weight:bold; -webkit-text-stroke: 1px #fff; text-shadow: 3px 3px 0 #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff")
      .attr("dx", 0)
      .attr("dy", 20)
  
  
  
  simulation.nodes(nodes).on('tick', () =&gt; {
      nodeElements
          .attr('cx', function(node) {
              return node.x
          })
          .attr('cy', function(node) {
              return node.y
          })
      textElements
          .attr('x', function(node) {
              return node.x
          })
          .attr('y', function(node) {
              return node.y
          })
      linkElements.attr("d", function(link) {
          return "M" + link.source.x + "," + link.source.y + " L" + link.target.x + "," + link.target.y;
      });
      labelElements
          .attr('x', function(link) {
              return link.target.x
          })
          .attr('y', function(link) {
              return link.target.y
          })
  })
  
  simulation.force("link").links(links)
  </code></pre>