force-graph icon indicating copy to clipboard operation
force-graph copied to clipboard

graph rotates when adding new nodes and using charge and link forces

Open klaraseitz opened this issue 3 years ago • 6 comments

Hi!

I am working on a tool which requires data to be layouted as a non-overlapping graph (neither nodes nor links). According to the node type (given by data) the links to and from it have to be of a certain length. Or at least of a certain ratio compared to other links of other lengths. I already have a working solution but this visualization also needs to be interactive and let the user add(or remove) nodes also. Since I am setting charge and link forces the whole layout rotates when a new node is added. when I remove these force values then it doesn't (at least not that much)

Here is how I add the forces. graph .d3Force('charge') .strength(-50)

graph.d3Force('link') .strength((link) => link.type === 'insert' ? strengths[link.source.type] : 1)

Also you can see my issue in this codepen. It load an initial typical dataset and lets you add nodes when clicking on one: https://codepen.io/Clarina/pen/QWpKygL In this example the data results in a circular layout. Not all my data leads to this. But also when the graph is more of a rectangular mesh layout there is rotation when adding new nodes.

Here you can also watch the rotation: rotatingGraphWhenAddingNodes

I believe I require the charge force because I cannot have any links or node overlap. (I know that my data always allows an overlap free visualisation). And I need the link force to specify how long the links should be. I already tried using link.distance instead of link.force but the result was worse. I could not get links to be as short as i needed unless I made the biggest distance really big. Which still may keep the ratio but resulted in an overall larger graph (in area not in amount of nodes/edges).

I know how to fix the position of nodes, but I do not want all nodes to be fixed when I add a new one because some additions might actually require a rearrangement (ex.: starting a line of node - edge - node - edge - node .. and then connecting the first to the last -> this should be rearranged into a circle)

I'm grateful for any tips of how to resolve this. Thanks!

klaraseitz avatar May 17 '21 16:05 klaraseitz

Thanks for reaching out @klaraseitz.

Tbh, I'm not sure why the graph rotates when adding new nodes. Sometimes it's just the adaptive nature of the set of forces active in the system that causes nodes to move in a particular manner.

I noticed one of your radial links had a different color/type (black instead of orange), not sure if that's intended nor if it has any effect on this motion.

This could also be a result of the fact that the current implementation of link force doesn't guarantee conservation of energy, which may result in some of these artifacts.

vasturiano avatar May 17 '21 19:05 vasturiano

Thanks for your quick reply @vasturiano

Yes, some of the radial links are black. That is on purpose. All black links represent the order of the nodes, by following the black links you can traverse the whole graph. This is one of the properties of my data. They are actually mostly shorter than the other (orange) radial links. Maybe this also leads to a stronger rotation but I do need different lengths of edges, also within one round.

It seems that the new nodes' charge and link force push the neighboring nodes which results in this rotation. Do you know of any way to circumvent this? I was thinking of fixing all nodes until the new node is positioned and then releasing the fixed nodes again. Is there some way to trigger a function once the warmup ticks are done?

Are there any other approaches that I could use instead to arrive to the same layout, and using different edge lengths?

klaraseitz avatar May 18 '21 16:05 klaraseitz

@klaraseitz you could try fixing all the existing nodes on .onEngineStop. That way when the new node gets added it's the only one that is allowed to move freely while the rest of the structure is fixed.

vasturiano avatar May 18 '21 21:05 vasturiano

Thanks @vasturiano I gave it a try with the .onEngineStop but i must be doing something wrong still. Here is the edited codepen

I added the following:

graph
[...]
.cooldownTime(100)
.warmupTicks(1500)
.onEngineStop(() => {
  const nodes = graph.graphData().nodes
  nodes.forEach((node, index) => {
    if(index !== nodes.length -1) {
      node.fx = node.x
      node.fy = node.y
    }
   graph.d3ReheatSimulation()
  })
})

The idea was to fix every node but the most recent. Since I generally only want to directly render the final layout I set the cooldown time to 0 and let all the work be done in the warm up phase. I observed that all nodes where fixed. The new nodes as well (which get created when clicking on an existing node) So I added the simulation reheat call. Now the new node repositions itself while i drag other nodes around. So i can see that it is actually not fixed but it seems it does not autoposition it self like it used to when all nodes where not fixed. So I thought it might need more time and set the cooldown time up to 150. This allowed the node to position itself. But once i add more nodes the previous ones change their position and get stuck there..?

I suppose I am making some mistake somewhere. Maybe you can point me to it?

Thanks for your help!!

klaraseitz avatar May 25 '21 11:05 klaraseitz

@klaraseitz if you'd like the currently non-fixed nodes to not move when adding a new one, perhaps you can do the node fixing on onNodeClick instead of onEngineStop. That would ensure that in the follow-up engine iteration due to the data changes they are already pre-fixed into position and thus not affected.

Let me know if that gets you closer to the results you expect.

Also, I don't think you need to reheat the simulation manually. That is done automatically by the framework whenever you modify the data structure.

vasturiano avatar May 25 '21 12:05 vasturiano

Additionally, you may want to disable the centering force as that can have unexpected effects if the majority of your graph is fixed. Because the force will try to use the few loose nodes to move the whole center of gravity of the graph towards the center, which causes an exaggerated and unintuitive motion on those nodes.

You can disable it like this:

myGraph.d3Force('center', null)

vasturiano avatar May 25 '21 12:05 vasturiano