vasturiano / 3d-force-graph

3D force-directed graph component using ThreeJS/WebGL
https://vasturiano.github.io/3d-force-graph/example/large-graph/
MIT License
4.67k stars 825 forks source link

Forces re-calculating when doing incremental updates with no changes. #645

Closed Blitzy closed 1 year ago

Blitzy commented 1 year ago

Im working on a graph where there are regular updates to the graph data - meaning the graph is very dynamic and responsive to changes in node data as its updated by the server.

The documented way to perform incremental updates is to essentially reassign the nodes and links to the graph like so:

var graphData = { nodes: [], links: [] }

let updatedGraphData = myAPI.updateGraphData({...graphData});
graph.graphData(updatedGraphData)

And this works pretty well and has been tested to make sure that the objects for the nodes and links are actually staying consistent between updates and not being constantly recreated if the data doesnt change between updates.

The problem is that with each update the entire simulation reheats and all the forces seem to become active again even if none of the nodes have actually changed. This results in the graph constantly moving and twitching and it never comes to a stop. I noticed that this occurs even with the Dynamic Data Changes example if you modify it to always update the graphData with the same data.

Am I using the graph in a way it was not intended to be used?

Example

Here's a JSFiddle running a modified version of the Dynamic Data Changes example that just re-assigns the graph's initial data every 250ms without making any changes to the data:

https://jsfiddle.net/Lsbtrkfm/2/

vasturiano commented 1 year ago

@Blitzy thanks for reaching out.

The force simulation reheating is necessary in situations where the graph topology actually changed, so that it has the chance to find the new equilibrium in the nodes positions. But if you find the iterative motion of the nodes too distracting you can have them jump immediately to their final position by doing warmupTicks(50) (adjust to taste) and cooldownTicks(0).

What's not clear in your example is why do you need to update the data structure if there's no actual topology changes in your graph? In other words, if nothing changed in the data, why call graphData at all? This is causing the simulation to get reheated needlessly.

Blitzy commented 1 year ago

That's a valid point and why I asked if I was using the feature in a way that was unintended. My plan moving forward is to treat it as you described, topographical changes.

Im storing a lot of state in the graph node and link data so updating it even if the topography didn't change was helpful. For instance, I'm changing the visual appearance of links based on custom properties I'm assigning to the link objects and updating the graph data seemed to be the most direct way to get the graph to update them.

vasturiano commented 1 year ago

If you just need to do a cosmetic change, you don't need to reinvoke graphData but just the part that you updating, f.e. linkColor.

Blitzy commented 1 year ago

Alright I refactored my own code to only reassign graph data when there are topographical changes. For cosmetic line update I re-assign the link property accessors like linkColor, linkWidth, etc so that the graph will rerun those without reheating the simulation. Thanks for the help @vasturiano !