Rozgonyi
Rozgonyi

Reputation: 1059

Is there a way to make a D3 force layout continually move?

Is there a way to may a d3 force layout continually move even after it has "cooled"? I have been using this but the movement is very minor:

svg.on('mousemove', function() {
   force.start();
});

Upvotes: 4

Views: 2679

Answers (4)

altocumulus
altocumulus

Reputation: 21578

As other answerers have noted the simulation's alpha parameter controls the amount of heat in the system. The rate of decay of that heat determines how fast the force layout cools down to a halt, which happens as soon as alpha reaches alphaMin.

For D3 v3 or lower the other answers are the way to go pumping energy into the simulation by manipulating alpha. As of D3 v4, however, you can use simulation.alphaDecay() to directly control the rate of decay of alpha. Setting the decay rate to 0 will keep the simulation running infinitely. That way, you can set a level of alpha at your discretion and keep it at the exact same level the entire time.

For a runnable demo have a look at the following snippet adapted from Mike Bostocks Force-Directed Tree notebook:

d3.json("https://raw.githubusercontent.com/d3/d3-hierarchy/v1.1.8/test/data/flare.json")
  .then(data => {
  const width = 400;
  const height = 400;
  const root = d3.hierarchy(data);
  const links = root.links();
  const nodes = root.descendants();

  const simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).id(d => d.id).distance(0).strength(1))
      .force("charge", d3.forceManyBody().strength(-50))
      .force("x", d3.forceX())
      .force("y", d3.forceY())
      .alphaDecay(0);

  const svg = d3.select("body")
    .append("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", [-width / 2, -height / 2, width, height]);

  const link = svg.append("g")
      .attr("stroke", "#999")
      .attr("stroke-opacity", 0.6)
    .selectAll("line")
    .data(links)
    .join("line");

  const node = svg.append("g")
      .attr("fill", "#fff")
      .attr("stroke", "#000")
      .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
      .attr("fill", d => d.children ? null : "#000")
      .attr("stroke", d => d.children ? null : "#fff")
      .attr("r", 3.5);

  node.append("title")
      .text(d => d.data.name);

  simulation.on("tick", () => {
    link
        .attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);

    node
        .attr("cx", d => d.x)
        .attr("cy", d => d.y);
  });
});
<script src="https://d3js.org/d3.v5.js"></script>

Upvotes: 2

Zee
Zee

Reputation: 874

Pass in a function instead of a value.

.alpha(() => 0.1)

Upvotes: 0

Rozgonyi
Rozgonyi

Reputation: 1059

I actually figured it out on my own with this:

setInterval(function(){force.alpha(0.1);},250);

That's probably not the most performant on a large layout but it provides a nice continual drift on my force layout of 20 nodes.

Upvotes: 4

Lars Kotthoff
Lars Kotthoff

Reputation: 109232

The cooling and amount of movement is controlled by the alpha parameter. If you want to keep the layout running continuously, reset alpha to be non-zero:

force.alpha(0.1);

Note that even though alpha may be greater than zero, there's not necessarily going to be any (significant) movement. At some point, the layout will settle into its equilibrium state and to get a significant change you'll have to e.g. move one of the nodes.

Upvotes: 1

Related Questions