Fine-tune note positions in force-directed placement

Hi all

I want to visualize connections in a network. The general structure is constant, but single nodes are added and removed quite frequently. I have tried fdp, sfdp and neato. In all cases, I can try different seeds with start until I eventually find a layout that looks okay. I would like to find a more reliable approach.

The documentation suggests that there is a pos attribute. I hope to use it to feed an initial position (either fixed or of a previous run) into the algorithm. I could not get it to work, so I read the source. There is a getPos function. The position data is forwarded to the layout function. I found these lines in spring_electrical.c:

  grid = Multilevel_get_coarsest(grid0);
  if (Multilevel_is_finest(grid)){
    xc = x;
  } else {
    xc = gv_calloc(grid->n * dim, sizeof(double));
  }

x allegedly holds the user-supplied positions. We get the coarsest grid, then we check if it is the finest grid. This never seems to be the case. Always, the new memory is allocated. The user-supplied information seems to be disregarded. Unfortunately, there is no documentation regarding multilevel_spring_electrical_embedding and x is not the most intuitive parameter name, so I could be completely mistaken. Anyhow, I could not figure out how to make this work. Is this a bug or am I using it wrong?

Kind Regards
Hermann

Yes, pos values can be supplied on startup. Please provide an example input graph, with desired pos values.

Thank you for the response. Here is the anonymized data:

graph Network {
label="Connections"
overlap=false
node [style=filled, fillcolor=white, color=grey]
edge [color=silver]

Anon01 [color=violet]
Anon78 [color=lime]
Anon03 [color=violet, pos="0, 0"]
Anon04 [color=violet, pos="-10, 0"]
Anon05 [color=green]
Anon06 [color=violet]
Anon07 [color=lime]
Anon08 [color=violet]
Anon09 [color=violet, pos="10, 0"]
Anon10 [color=lime]
Anon11 [color=lime]
Anon12 [color=lime]
Anon13 [color=green]
Anon14 [color=green]
Anon15 [color=silver, pos="10, -10"]
Anon16 [color=lime]
Anon17 [color=lime]
Anon18 [color=lime]
Anon19 [color=silver]
Anon20 [color=lime, pos="10, 5"]
Anon21 [color=lime]
Anon22 [color=violet]
Anon23 [color=violet]
Anon24 [color=violet]
Anon25 [color=green]
Anon26 [color=green]
Anon27 [color=green]
Anon28 [color=lime]
Anon29 [color=violet]
Anon30 [color=silver]
Anon31 [color=silver]
Anon32 [color=lime]
Anon33 [color=green]
Anon34 [color=lime]
Anon35 [color=green]
Anon36 [color=lime]
Anon37 [color=lime]
Anon38 [color=lime]
Anon39 [color=lime]
Anon40 [color=lime]
Anon41 [color=lime]
Anon42 [color=lime]
Anon43 [color=lime]
Anon44 [color=green]
Anon45 [color=lime]
Anon46 [color=lime]
Anon47 [color=green]
Anon48 [color=lime]
Anon49 [color=lime]
Anon50 [color=lime]
Anon51 [color=green]
Anon52 [color=green]
Anon53 [color=green]

Anon01 -- Anon03
//Anon01 -- Anon05
Anon01 -- Anon54
Anon54 -- Anon78
Anon01 -- Anon06
Anon06 -- Anon07
Anon07 -- Anon08
Anon03 -- Anon13
Anon03 -- Anon57
Anon03 -- Anon58
Anon03 -- Anon55
Anon03 -- Anon44
Anon55 -- Anon09
Anon09 -- Anon73
Anon09 -- Anon77
Anon09 -- Anon76
Anon09 -- Anon75
Anon09 -- Anon30
Anon30 -- Anon29
Anon30 -- Anon20 [color="darkred"]
Anon20 -- Anon21
Anon20 -- Anon27 [color="darkred"]
Anon20 -- Anon22 [color="blue"]
Anon73 -- Anon74
Anon73 -- Anon15
Anon15 -- Anon72
Anon15 -- Anon05
Anon15 -- Anon10
Anon10 -- Anon11
Anon10 -- Anon28
Anon10 -- Anon71
Anon10 -- Anon69
Anon13 -- Anon14
Anon13 -- Anon70
Anon13 -- Anon36
Anon36 -- Anon40
Anon13 -- Anon35
Anon36 -- Anon37
Anon78 -- Anon04
Anon78 -- Anon68
Anon78 -- Anon32
Anon78 -- Anon19
Anon68 -- Anon19
Anon32 -- Anon41
Anon04 -- Anon16
Anon04 -- Anon49
Anon16 -- Anon34
Anon04 -- Anon31
Anon04 -- Anon12
Anon19 -- Anon12
Anon12 -- Anon18
Anon12 -- Anon67
Anon12 -- Anon17
Anon12 -- Anon66
Anon12 -- Anon25
Anon12 -- Anon45
Anon03 -- Anon43
Anon03 -- Anon65 -- Anon42
Anon45 -- Anon47
Anon12 -- Anon46
Anon45 -- Anon24
Anon45 -- Anon39
Anon07 -- Anon23
Anon20 -- Anon33
Anon21 -- Anon33
Anon21 -- Anon64
Anon21 -- Anon50
Anon20 -- Anon50
Anon20 -- Anon52
Anon20 -- Anon59
Anon20 -- Anon60
Anon20 -- Anon53
Anon50 -- Anon63
Anon11 -- Anon51
Anon45 -- Anon48
Anon45 -- Anon61
Anon45 -- Anon62
Anon23 -- Anon24
//Anon05 -- Anon27
Anon25 -- Anon26
Anon42 -- Anon43
Anon21 -- Anon38
}

In this sample, I tried to give some hints for a horizontal layout. neato -s does something, but I cannot say I feel in control. When I set pos="-12, 0" for Anon49, hoping it will end up left of Anon04 to eliminate the unsightly crossed edges, I end up with a completely different graph. sfdp seems to ignore the pos attribute completely.

  • To “nail down” a node (pin in Graphviz language), either add a bang (!) to the end of the pos (e.g. pos=“2,4!”) or add the attribute pin=true. (see pos | Graphviz and point | Graphviz)
  • note that input pos units are inches, but output units are points (inch/72)

Below is a resulting graph after adding “!” to each pos, running neato with no other attributes set.

I hoped for something “softer” than “pinning”. Thank you for the hint not to use neato's -s switch. :slight_smile: I ended up pinning all the nodes, effectively doing the layout manually. Not ideal, but okay for now. I hope to find a true automatic layout mechanism. graphviz does not seem to be the right tool for the job.

Try halfviz. Their force directed layouts let you drag nodes around a bit.

FWIW, like Hermann I find it pretty strange that the multilevel_spring_electrical_embedding ignores the user-supplied positions.

It’s intrinsic to the multilevel layout algorithm, because it starts with a coarsened (simplified) representation of the network, then expands it until the full node set is reached. It is not clear how to cope with combining multiple nodes that have potentially very different layout coordinates. Not saying it couldn’t be done or this problem hasn’t been solved elsewhere but it would take some effort. For similar reasons, multilevel layout doesn’t cope with variable (user-specified) input edge lengths.

There could be some good (student? hacker?) projects based on this.

In graph drawing, make N layouts in parallel and automatically choose the best one(s).

In interactive editing, support fine tuning with a scalable constraint-based graphics editor. Allow all kinds of constraints: separation, alignment, compaction. Tim Dwyer made a lot of progress on this with WebCoLa but it’s not maintained any more. This might be an example of how better technology doesn’t necessarily win over popularity, flexibility/portability, and some level of community support.

I remember several times starting to fix this before remembering that it wasn’t that easy. The obvious approach is to give a coarse node some average position of its component nodes, but this sounds like it could cause problems. Interestingly, even allowing pinned nodes as in neato and fdp can cause problems with the layout, iirc.