almende / vis

⚠️ This project is not maintained anymore! Please go to https://github.com/visjs
7.84k stars 1.48k forks source link

Ordering / interaction for large directed networks #3204

Open viksit opened 7 years ago

viksit commented 7 years ago

I have a large directed graph that represents a flow. For instance, there's a start node and an end node.

A naive version of it looks like this: start -> a -> b -> c -> d -> end a->c # an edge from a to c b->d # an edge from b to d

As a result, I have a requirement that some sort of directed layout be used to represent this graph (left to right).

I'm running into a few issues that I would love feedback on,

image

image

Notice that the edges towards the bottom of the screenshot are actually overlapping on top of each other because they are forced to be on the same level. So just by looking at the graph, its hard to say what connects where.

Ideally, I would like a mechanism where the graph is rendered left to right, but is not constrained by the concept of "levels" - so the nodes towards the right are rendered "narrowowy" rather than being spread really wide apart. What would be the best way to do this? (I see #2151 and #528 have some information but that seems to involve changing the visjs source. Is there a way to add on a custom sort function?)

Thanks!

wimrijnders commented 7 years ago

First, I have to ask: is this network truly hierarchical? This, I define as: every node has at most one parent.

In this case, the Network module does a sincere attempt at minimizing the area used and the total edge length. But it doesn't seem to be doing a good job here....this rearrangement is controlled by the options:

These should be on by default; if you haven't changed them, it's time to consider that something is going wrong.

As a note: If the network is not truly hierarchical, then all bets are off. The layout rearrangement is known to fail so badly in this case that it's been explicitly disabled.


Is there a way to add on a custom sort function?

You're not the first to ask this question; some initiative has been made to make this available, but it's currently marked as a TODO somewhere in the code. But your request appears to be slightly different in that it affects nodes according to the population of a level.

Would you mind elaborating on how your think this sorting would work? This is for clarification on my behalf, to so see how it can fit in a more generalized sorting strategy.


In and out nodes:

A strategy I've seen used, is to use a group definition for the 'critical path'. This would change some display characteristic so that this path is visible. So, to define this path, it is sufficient to add a group field to the nodes in this path.

As to determining this path, if this network is truly hierarchical and you know the out-node, it is trivial: just make a list of successive parents to the top. This is entirely doable.

If by storing dependencies you mean registering the relationships between nodes, you don't really need to do that. The Network instance already knows this and makes the info available through method getConnectedNodes().

Hiding nodes can be achieved through clustering. Please have a look at this jsbin demo, in which branches can be shown/hidden. I can imagine this being combined with finding a critical path as above; anything not on it is hidden by clustering.


HTH.

viksit commented 7 years ago

Thanks for the feedback! Replies in order,

image

or

image


image


wimrijnders commented 7 years ago

Your thoughts, essentials redacted:

  1. As LR directed graph, but introduce nodes into new levels based on the number of in-edges into them.

    • one connection, node goes to the next level (k+1)
    • > 1 connection: it goes into (k+2)
  2. Add a constant that controls the spacing of each level,

  3. Use a different CCW or CW curve constant to introduce more visible connections between these nodes.

My feedback tacked on to these:

  1. Number of in-nodes given a node id nodeId:
// Note: 2nd parameter available in next release
var numInNodes = network.getConnectedEdges(nodeId, 'from');

So, now you need to adjust the level. I'm trying the helicopter view here, to see how it will fit will other sort function request. This is the current best I can think of:

/**
 * Present 2 nodes to compare for sorting.
 * Params levelA and levelB are current or initial levels
 */
function customSort(nodeA, nodeB, levelA, levelB) {
  var compare;  // < 0, 0, > 0 significant
  // Do your sorting magic here
  return [ compare, newLevelA, newLevelB];  // ignore levels with 'return compare;'
}

Something like this will give you explicit control over which level is assigned. I'll be honest and say I'm not thrilled by this, but it's the best I can think of now. Feedback is welcome.

  1. Ooh tough one. The thing here is that in physics there is only one option for the entire network controlling the spacing: layout.hierarchical.nodeSpacing.

How about the following idea: set a negative gravity constant and control the spacing using the mass of the nodes. Mass is node-specific, and since movement is restricted to the 'orthogonal' dimension, the effect on nodes on other levels will not be severe. This does require enabling physics, though.

  1. This is what edges.smooth.type: dynamic is good at. Otherwise, you need to figure out CW and CCW yourself.

Thusfar. HTH.

wimrijnders commented 7 years ago

Personal Note: Keep this issue as a feature request. There's good stuff in it.