Reputation: 1059
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
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
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
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