Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force-directed Graph (DAG mode) Support Cycle #199

Closed
Arya-Aghaei opened this issue Jun 30, 2020 · 21 comments
Closed

Force-directed Graph (DAG mode) Support Cycle #199

Arya-Aghaei opened this issue Jun 30, 2020 · 21 comments

Comments

@Arya-Aghaei
Copy link

Hi, I've been asked to provide dag mode support for graphs with or without cycles, there are 2 issues:

  • When the graph has cycles, it will throw an error.
  • When the graph has diverse directions, dag mode does not work.

I would be appreciated if you suggest me how to handle these demands or any hints about how to modify the source code to achieve this goal. thanks in advance.

image

  • This sample is just to show the direction of the links as an example
@Arya-Aghaei
Copy link
Author

I replaced throw in the traverse function with "continue;" in force-graph-module in my node modules, so I won't get any error and nodes become visible circular, now how can I put the root node in the centre and simulate the dag mode? :D

function traverse(nodes, nodeStack = []) { const currentDepth = nodeStack.length; for (var i=0, l=nodes.length; i<l; i++) { const node = nodes[i]; if (nodeStack.indexOf(node) !== -1) { const loop = [...nodeStack.slice(nodeStack.indexOf(node)), node].map(d => idAccessor(d.data)); throwInvalid DAG structure! Found cycle in node path: ${loop.join(' -> ')}.; } if (currentDepth > node.depth) { // Don't unnecessarily revisit chunks of the graph node.depth = currentDepth; traverse(node.out, [...nodeStack, node]); } } }

#https://github.com/vasturiano/force-graph/blob/master/src/dagDepths.js

@vasturiano
Copy link
Owner

@arrow891 thanks for reaching out.

I've added a onDagError prop that can be used as an escape hatch for this. If unset it will throw an exception but you can choose to override what happens when a cycle is encountered.
If you set it to:

onDagError={loopNodeIds => {}}

you're essentially allowing the DAG processing to continue after encountering a cycle. This is not recommended as graph directionality cannot be guaranteed and the result will be a best effort.

But it can help a case in which the graph structure is mostly a dag except for some small negligible loop segments.

TL;DR use with caution. 😄

@Arya-Aghaei
Copy link
Author

@vasturiano I do appreciate for the time you have assessed to save my job, you saved 50% of my job, yet I have to save the rest 50% 😂, because of the confidential data I'm not able to share anything of my code or etc. But so far the thing that happens is similar to this :

Screenshot_20200708_005402

As you can see, the root node is also placed in the circle position, while with the dagMode its expected to be something like this:

Screenshot_20200708_005349

Sorry for my bad drawing :)) but I meant the root node should be placed in the center, I can provide group property in my data so that it can distinguish which one is the root or something else, I hope you can suggest me something before I run out of time 😅 thank you so much in advance

@vasturiano
Copy link
Owner

@arrow891 maybe you can try using a radialOut DAG layout, as selectable in this example:
Screen Shot 2020-07-07 at 1 57 01 PM

dagMode="radialOut"

@Arya-Aghaei
Copy link
Author

Arya-Aghaei commented Jul 7, 2020

@vasturiano The example pictures that I shared was the results of using radialout or radialin, I also tried to trace your code to find out it's mechanism, but having the lack of time intercepted me, I will try to provide an example data if possible but I'm not sure if I can

@vasturiano
Copy link
Owner

@arrow891 if you wish to fix the root node at the center of the canvas, you can do so by adding fx: 0, fy: 0 attributes to it. Perhaps in that case you don't need the DAG processing any longer. Those cycles in the data is what's causing the non-optimal results.

@Arya-Aghaei
Copy link
Author

Arya-Aghaei commented Jul 13, 2020

@vasturiano I've tried to set fx and fy in nodeCanvasObject or even in the onDagError, and did not work, specially when we have a mesh graph, I think the reverse vertices are causing the issues, I will try to simulate the situation in a codesandbox or provide the data ASAP

@Arya-Aghaei
Copy link
Author

@vasturiano I tried my best but I failed, here is my approach tu use grouping nodes and position them circular around the root node, I used d3Force to position them with forceX and forceY, or forceRadial, but this is too much beyond my knowledge, do you have any suggestions how to achieve this?

Screenshot_20200721_133317

@vasturiano
Copy link
Owner

@arrow891 the graph you've drawn does not appear to have any cycles in it (not sure because it doesn't include link directionality), so the radialOut should get you close to that.
The problems only start when you have cycles in your data.

If you want to make radialOut work with cycles can you draw what that would look like?

@Arya-Aghaei
Copy link
Author

@vasturiano Thanks for replying so fast, I provided a sandbox to show an example data with cycle and radialOut option enabled if you look at the data, I added a group property to my nodes object, as you can see in my drawing, I would like to place nodes with the same group id, around the root node. (each group represent a level like circles in the drawing)

@vasturiano
Copy link
Owner

@arrow891 for that purpose I would suggest to not use the dagMode but instead apply a radial force whose radius is determined by the node group.

You can add a new force to the system using the d3Force component method (not prop).

Something like:

myGraphRef.current.d3Force('myRadialForce', 
  forceRadial()
    .radius(node => node.group * LEVEL_GAP)
    .strength(1)
);

You can import forceRadial from https://github.com/d3/d3-force

@Arya-Aghaei
Copy link
Author

@vasturiano I have updated my sandbox , somehow it works but there 2 questions:
1- how can I reset the force I applied to my graph (I want to make it optional so the user can enable or disable it)?
2- how to place nodes with the same level (group) on the circles I have drawn?

@vasturiano
Copy link
Owner

  1. You can remove a force from the system by assigning it to null:
myGraphRef.current.d3Force('myRadialForce', null);

Or alternatively, you can just set its intensity to 0:

myGraphRef.current.d3Force('myRadialForce').strength(0);
  1. Just set the radius of the circle according to the group, f.e.:
myGraphRef.current.d3Force('myRadialForce').radius(node => node.group === 'a' ? 30 : 60);

@Arya-Aghaei
Copy link
Author

Arya-Aghaei commented Aug 9, 2020

@vasturiano it's me again :D I needed more codes and I found some, but now I am facing another issue, please check this sandbox to see what happens to disjoint nodes :D

The disjoint nodes overlapping on each other while they should be separated as much as the lines around them, if I change the forceCenter, then mapping of the nodes will be interrupt.

@vasturiano
Copy link
Owner

@arrow891 from that example is not overly clear what the intent/issue is. Can you explain what is not behaving as you would expect?

@Arya-Aghaei
Copy link
Author

@vasturiano Ok, please consider this scenario, we have a disjoint graph with 3 root nodes, each other node is in the specific group base on data, we want to place each node around its root base on the group index, so we have a 3 organized graph, when you open the example, roots and their childrens are not organized, when you click on the button, each node will move to its position around the root, but roots overlaps on each other !

The issue : after applying forces, each root node and its childrens should be visible correctly, but in the demo, all root nodes go to the 0,0 position, I couldn't separate centers without affecting the categorization.
The demand view is something similar to the after in below image.

Screenshot_20200811_222605.jpg

@vasturiano
Copy link
Owner

@arrow891 perhaps I'm missing something but if you'd like to separate the various disjointed clusters, why not give them different radial centers, using the x, y properties of forceRadial?

https://github.com/d3/d3-force#radial_x

@Arya-Aghaei
Copy link
Author

Arya-Aghaei commented Aug 14, 2020

@vasturiano probably I'm missing something, first of all:
1- how to set different centers with ForceRadial? (while it does not affect subNodes grouping) you mean I should write multiple forceRadial functions? If I change x, y of different roots (maybe in nodeCanvasObject) then the grouping of subNodes will be affected, and forceRadial x, y can not be dynamic because it receives a numeric value, I tried to set it using forceX but no result achieved :/ I've tried whatever my knowledge and experience supported me :/

2- how to avoid collision between the outer circles around each disjoint root? Something like the last circle repel other outer circles of other roots.

@vasturiano
Copy link
Owner

1 - Yes, unfortunately the x and y properties of forceRadial do not accept an accessor method, so your remaining option is to have multiple radial forces, where each of them only has an effect (strength > 0) on its corresponding cluster. It's a bit more complicated to maintain but it's possible.

2 - Since you're in control of the radial centers, that's the only thing you can really manipulate to prevent collisions. Now, what you could do is set a collision force between all the root nodes, with the collision radius as large as the outer ring of that cluster. Then, you dynamically set the radial force center of each cluster (at every tick) to the coordinates of its root node (that's been affected by the collision force). It will probably take a bit of calibration to get it just right, but it could be done.

@Arya-Aghaei
Copy link
Author

@vasturiano

1- I tried multiple forceRadial but the same results, would you please help me with the sandbox or more practical examples?

2- setting forceCollide for each root node, not only affects on distance between roots, but also affects the distance between root and its dependent nodes which is not a good solution :(

@Arya-Aghaei
Copy link
Author

@vasturiano well, finally I solved the problem, it was something beyond the library support and I had to modify the library itself in order to achieve this goal, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants