0

I am using d3's forceSimulation to calculate (x,y) coordinates for each node given the forces (starting without nodes or links):

const simulation = d3.forceSimulation()
      .force('charge', d3.forceManyBody().strength(-1000))
      .force('link', d3.forceLink().id(d => d?.id).distance(200))
      .force('collide', d3.forceCollide().radius(150))
      .stop()
      .tick(300)

and then listen to simulation's 'tick' event to update graph (utilising react-flow-renderer setNodes and setSetEdges see the docs):

simulation.on('tick', () => {
    setEdges([...simulation.force('link').links()].map(setEdge));
    setNodes([...simulation.nodes()].map(setNode));
})

React part is supposed to restart the simulation while applying actual nodes and edges to the simulation:

useEffect(() => {
    simulation.nodes(nodes);
    simulation.force('link').links(edges);
    simulation.alpha(0.1).restart();

    return () => simulation.stop();
}, [nodes, edges])

Now, the nodes and edges might get updated as there is a possibility to extend node's relations. The thing is - every time we extend a node relationships, we get a new array of nodes and edges, each containing old values and the new ones:

Dummy example:

old nodes : [{ id: 1 }]

new nodes: [{ id: 1 }, { id: 2}, { id: 3}]

The 'simulation' restarts with new values and recalculates the graph starting from scratch so the new graph is nothing like old one. Is there an option so the graph would keep old nodes' (x,y) coordinates and simply 'add' new ones to existing graph?

user1346765
  • 172
  • 1
  • 15

1 Answers1

0

In case anyone faces this kind of problem:

Keep a record of previous nodes before you update the nodes' array:

const prev = usePrev(new Map(nodes.map((node) => [node.id, node])));

and then, right before passing the new nodes to simulation, make a new copy, merging previously existing elements like so:

useEffect(() => {
    nodes = nodes.map((node) => Object.assign(node, prev?.get(node.id)));

    simulation.nodes(nodes);
    (...) // rest of the code

This way when the simulation starts with new nodes, some of them might already have (x,y) coordinates set.

user1346765
  • 172
  • 1
  • 15
  • Do you mind providing a sample codepen? :) I'm trying this but I get an infinite animation, I'm sure it's my lack of knowledge of using ReactFlow – 123 456 789 0 Jul 03 '23 at 02:26
  • Sorry, the code is long gone now. Are you sure the animation is infinite? It might take it a longer while to cool down if forces are set up incorrectly. Start with smaller values, not like I did (forceManyBody -1000, forceLink 200) – user1346765 Jul 04 '23 at 07:14