Saving and Restoring New Node/Edge Positions

Hi everyone,

I’ve been reading through other posts to try and make sure I’m not asking a duplicate question, but I didn’t find anything quite on point.

I’m trying to support a use case where users will be able to decide to take control of layout on a given diagram and manually reposition nodes and edges. I want to be able to save those new node and edge positions and restore the custom-laid out graph as needed. I also want to be able to add nodes to the graph in a way that does not place those nodes on previously manually positioned nodes.

I thought it might be clever to generate a diagram using dot and then when a user moves a node, to capture the new position, and then regenerate the graph using neato, which supports node positions via pos. However, the translation does not seem to be perfect and I’m wondering why. As a simple test, if I take a dot graph and output to dot (which includes pos for each node / edge) and then use that as input for neato, it does not come out looking the same.

Here’s the input dot:

digraph {
bgcolor="transparent"
nodesep="0.5"
ranksep="0.75"
fontname="Helvetica"
node [fontsize="10" fontname="Helvetica" fillcolor="#111827" color="#111827" penwidth="0.75"]
edge [fontsize="10" fontname="Helvetica" color="#111827" penwidth="0.75"]
"1679216963259" [id="1679216963259" shape="oval" fontcolor="#111827" color="#111827" margin="0,0.05"  width="3" height="1" fixedsize=true color="#111827" fillcolor="#FFFFFF" style="filled" label=<<table border="0" cellborder="0" cellspacing="0" cellpadding="0"><tr><td><font point-size="13"><b>Bob</b></font></td></tr></table>>]
"1679216654435" [id="1679216654435" shape="rectangle" fontcolor="#111827" color="#111827" margin="0.12,0.11" width="3" height="1" fixedsize=true color="#111827" fillcolor="#FFFFFF" style="filled" label=<<table border="0" cellborder="0" cellspacing="0" cellpadding="0"><tr><td><font point-size="13"><b>ABC Inc.</b></font></td></tr><tr><td></td></tr><tr><td></td></tr></table>>]
"1679216963259"->"1679216654435" [arrowsize=0 label=<100%>]
}

Here’s the output dot to input into neato:

digraph {
	graph [bb="0,0,216,210",
		bgcolor=transparent,
		fontname=Helvetica,
		nodesep=0.5,
		ranksep=0.75
	];
	node [color="#111827",
		fillcolor="#111827",
		fontname=Helvetica,
		fontsize=10,
		label="\N",
		penwidth=0.75
	];
	edge [color="#111827",
		fontname=Helvetica,
		fontsize=10,
		penwidth=0.75
	];
	1679216963259	[fillcolor="#FFFFFF",
		fixedsize=true,
		fontcolor="#111827",
		height=1,
		id=1679216963259,
		label=<<table border="0" cellborder="0" cellspacing="0" cellpadding="0"><tr><td><font point-size="13"><b>Bob</b></font></td></tr></table>>,
		margin="0,0.05",
		pos="108,174",
		shape=oval,
		style=filled,
		width=3];
	1679216654435	[fillcolor="#FFFFFF",
		fixedsize=true,
		fontcolor="#111827",
		height=1,
		id=1679216654435,
		label=<<table border="0" cellborder="0" cellspacing="0" cellpadding="0"><tr><td><font point-size="13"><b>ABC Inc.</b></font></td></tr><tr><td></td></tr><tr><td></td></tr></table>>,
		margin="0.12,0.11",
		pos="108,36",
		shape=rectangle,
		style=filled,
		width=3];
	1679216963259 -> 1679216654435	[arrowsize=0,
		label=<100%>,
		lp="120.79,105",
		pos="e,108,71.607 108,138.33 108,118.03 108,92.461 108,72.087"];
}

Why doesn’t this work as I expected? I tried playing with inputscale but that didn’t seem to do much. Also, if anyone has any better suggestions on how to achieve this, it would be much appreciated.

Thanks for your time,

Brett

1 Like

I believe the only thing “missing” is the -n option on the neato command line. Like so: neato -n -Tpng myfile.gv>myfile.png (see FAQ | Graphviz)
neato will use the (new) node positions & generate appropriate (pretty much) edges.
Explicitly generating new edges is more of a challenge (see Fun with edges!)
Also note that neato is OK with (oblivious to) overlapping nodes, and the results can be fun (or not). (see Stupid Dot tricks)

1 Like

Thanks for the quick reply! That addresses the issue I raised, and apparently I should have read the FAQ a bit closer.

It seems that having fixed positions is an all or nothing thing? I.e. it is not possible to have fixed positions for nodes X and Y but not for Z and expect neato to put Z somewhere reasonable?

1 Like

It all depends. The neato and fdp layout engines will let you use the pin attribute (pin | Graphviz) to fix the location of nodes (not edges). Note the comment in the pos documentation about units are points, not inches.
But neato will not do a “good” job of adding nodes to a graph that originated from one of the other layout engines (e.g. dot).

Out of curiosity, why might your users want to add nodes and not re-layout?

1 Like

Thanks for the additional explanation and the pin suggestion.

The app I’m working on calculates new states based on changes in underlying data. But the data is sequentially dependent, kind of like in Excel how if you have cell A1 + cell A2 with the total in A3, if A1 changes, A3 will recalculate. In my app, the user can change A1, at which point the recalculation might add nodes to A3, at which point the user will likely want to re-layout themselves if not relying on the auto-layout. I would just want the new nodes to appear somewhere reasonable for the user to then re-layout (and perhaps not on top of other nodes).

Regarding the units being points, that was a bit of a gotcha for me this morning. I started playing with perhaps using another library to handle the movement of nodes (something that would provide more interactivity features out-of-the-box). The first surprise was that it seems like the positions of pos are relative to the bottom right corner, is that correct? I inferred that because the lower node in the example above has a ‘y’ pos of 36, while the node above it has a ‘y’ pos of 174. When I rendered those coordinates in another diagramming library which uses pixels (so after multiplying by 96/72), the lower node was on top. So I ended up using the bounding box size to calculate when the ‘y’ position would be if using the top left corner as the reference point.

The pos also being the center of the node required calculating the top left corner, which seemed doable since I just had the width and height. I also had to multiple the font size by 96/72.

So far I was able to reproduce a simple chart using the graphviz coordinates, just two nodes with one edge between them.

Any other tips are much appreciated, thanks!

1 Like

bbx999 I am in need of a similar approach. I consider a button to do a full re-layout, and otherwise let the user do gradual changes, based on the previous locations. neato allows to draw all the edges and the locations are kept. New nodes will then be needed to be placed manually.

Problem for me is a “location memory” in a shared session. People keep locations of information in their heads and it’s difficult to keep track. The standard graphviz algorithm is moving things around a lot and this is confusing for others that are not in charge of the session’s network graph.

It can be quite confusing & annoying when a “small” change is made to a a network definition (one “tiny” node or edge is added or deleted) but he resulting layout is unrecognizably different.
Possibly the layers feature (FAQ | Graphviz) can help.
Layers can definitely help preserve consistency in alternative networks (“what if”) and can also help when presenting changes going “backward” in time.
Here links to layers usage:

@steveroush Do layers just achieve the ability to more easily hide / show groups of nodes and edges by associating them with a specific layer (and without triggering a new layout calculation)? Or does putting nodes and edges into different layers somehow also impact the layer calculation?

What do you mean by shared session here? What’s your use case?

Layer layout is performed once, based on the (super)set of all nodes, edges, and clusters that appear in any of the individual layers. All nodes, edges, and clusters have their positions calculated. Each node, edge, cluster can “appear” in any number of layers (1, 2, n, all).
Then, each layer can be drawn based on the (sub)set of nodes, edges, and clusters that it includes.
If you add a new layer, there is a good chance that the entire graph (all the layers) will change, but each of the newly positioned layers will be consistent.
A set of pictures is worth 10,000 words in this case. I suggest playing with the example in the FAQ - then add some new (or delete existing) nodes and/or edges.
Note that there are some problems with the implementation. My comments about layerselect should help.
Maybe layers will work for you and maybe not.

Several people in front of one screen.

Thanks for the clarification.

Understood @Vithanco. I’ve definitely experienced people having trouble following when presenting and making changes to a graph where things get reshuffled. Perhaps an initial automatic layout followed by, as you said, manual node positions would work better.

If you give neato the positions of nodes, can/will it route the edges nicely between them?

neato -n
will take explicitly positioned nodes (and clusters) and then position your edges (FAQ | Graphviz). The edge positioning is not always identical to dot’s, but very similar to it.
If you want splined edges, you must explicitly set splines=true because neato defaults to splines=false

1 Like

Thanks!