Neato ignoring label positions when control points not defined for edges

Graphviz (using neato) ignores label positions when control points are not defined for edges.

I can brute force my way to positioning the labels, but I’m doing this in ignorance of the real problem.

Is there a way to stop graphviz overwriting lp, xlp in edges that do not have the curve control points pre-defined?

Why isn’t lp working on nodes?

I can position edge labels only if I position nodes and edges, and define edge curves

# manual_edge.dot
digraph G {

  A [label = "A"  xlabel="a" pos="0,100" lp="200,100!" xlp="300,100!" ]
  B [label = "B"  xlabel="b" pos="100,0" lp="200,0!" xlp="300,0!" ]

  A -> B  
    [pos="0,100 0,0 0,0 100,0" ] 
    [label = "A->B"  xlabel="a->b" lp="200,50!" xlp="300,50!" ]   
}

EDIT : could somebody with more edit privileges please uncomment this?

![label positioning when edge curve is defined|465x192]
(upload://b7Mm4tA2ZtJ3XFhHxlnKx8EvB0t.png)

Defining positions is easy enough, defining curves is a bit painful.
If I don’t do it - the edge label positions don’t work:

# edge.dot
digraph G {

  A [label = "A"  xlabel="a" pos="0,100" lp="200,100!" xlp="300,100!" ]
  B [label = "B"  xlabel="b" pos="100,0" lp="200,0!" xlp="300,0!" ]

  A -> B  
    [label = "A->B"  xlabel="a->b" lp="200,50!" xlp="300,50!" ]   
}

label positioning doesnt work when edge curve is not defined

When I export the dot output I can see that when I don’t define the edge curves, my label positions are being overwritten

$ dot -Tdot -Kneato -n2 -o ./output.dot ./edge.dot
	A -> B	 [label="A->B",
		lp="30.532,60.968",
		pos="e,84.997,15.003 15.188,84.812 31.877,68.123 58.839,41.161 77.877,22.123",
		xlabel="a->b",
		xlp="33.032,45.968"];

to work around this behaviour, I’m putting my values in nonsense attributes that survive processing and using regex to clean up the file before passing it back to neato:

...
[label = "A->B"  xlabel="a->b" zzzzl="200,50!" zzzzxl="300,50!" ]   
...
...
	A -> B	 [label="A->B",
		lp="30.532,60.968",
		pos="e,84.997,15.003 15.188,84.812 31.877,68.123 58.839,41.161 77.877,22.123",
		xlabel="a->b",
		xlp="33.032,45.968",
		zzzzl="200,50!",
		zzzzxl="300,50!"];
...

After converting zzzzl to lp, and zzzzzxl to xlp diagram works.
This is because when attributes are set multiple time the last value is used


dot -Tpng -Kneato -n2 -O ./output.dot

This gets me what I want, but it’s a bit longwinded.
Is there a way to stop graphviz overwriting lp, xlp in edges?
Why isn’t lp working on nodes?

question also posted on stackoverflow at:
…stackoverflow.com/questions/69491641/graphviz-edge-label-position-graphviz-ignoring-label-positions-when-control-po

I’m not allowed to say thank you on S.O, so I’ll say it here, thanks for taking the time to look at my question.

Neato and fdp are the only Graphviz engines that will use any pos value provided as input (pos | Graphviz). I believe fdp only uses node pos values.
(See also pin | Graphviz)
lp only applies to edges, graphs, and clusters (not nodes) (see lp | Graphviz)
edge xlp and lp pos values are only used by neato -n and neato -n2.
See also FAQ | Graphviz

Hello Steve.
In this case I’m using neato, and i can make it work, but from what i’m inferring in the docs, though it isn’t said explicitly, either you provide all the edge position information, or graphviz calculates it, meaning there might not be a way to specify edge label positions without the full curve of the edge.

I believe you are correct that the placement sequence is:

  1. nodes
  2. edges
  3. labels (lp & xlabels)

(clusters are in there, too) (possibly kitchen sinks) (humor)

I can’t think of any reasons why we rely on this particular restriction; I don’t think relaxing it would break other code. The problem is just who has time to figure it out - there is a somewhat long thread that has to be untangled, starting with common_init_edge in lib/common/utils.c · main · graphviz / graphviz · GitLab but I don’t see where ED_spl(e) is constructed or otherwise comes into the picture.

I hardly even recognize this code. It reminds me of this Bob Dylan interview quote except in my case it’s just bad code.

Hi Stephen,

With the amount of time I spend wrestling with graphviz, I think it’s probably time I start reading the code and helping out. Do you know if there is a how to / where to start readme somewhere?

I saw Dylan play, except he was miming, we’ll he was singing but he was only pretending to play guitar. He turned his back on the audience during solos, and I could see someone offstage in the shadows actually playing them.

I’ve found developers.md, i’ll start there

Seriously? What a showman.

I can answer some questions about the code, though Emden Gansner and John Ellson both did a lot of work that only they understand really. There is a lot of nonsense in dotgen that could be rewritten. There are a lot of tricks with coordinate systems that could have been done more cleanly. Later on we figured out that graph layout should be sort of a compiler where we compute a series of graphs to make the layout, rather than perform a lot of edits in-place on the input graph with a lot of bookkeeping to undo the edits later. It was just wrongheaded. I think fdp is written in a better style - it’s all Emden’s work.

Thanks Stephen,

Yeah seriously, it was a pretty bizarre mess of a show. I was at a festival and he made them turn off the cameras that had been projecting the other acts on the big screens, I’d assume because he didn’t want the cameras picking up the assistants hiding in the wings. He’d play an intro to a song, stop, futz around a bit. Strum for a bit and stop. He’d start a song you know but it would be barely recognisable, he’d sing the chorus, skip the verses and then swap his guitar and start something else. It went on and on and I don’t think anyone really understood whether he had started or was still warming up, and then he walked off and we realised it was all over.

I’m going to read a bit more of dot, try and get my head around it, started there cause it had a main(),
and when I’m too confused to carry on, or even better, I get to the point of understanding how the layout engines fit in, I’ll have a look at fdp. Actually that’s a good one for me to get into, because if fdp didn’t do mysterious things repositioning nodes in a subgraph, I’d be pretty happy with it.

To read dotgen it’s good to at least skim the 1993 (!!!) TSE paper. https://www.researchgate.net/profile/Emden-Gansner/publication/3187542_A_Technique_for_Drawing_Directed_Graphs/links/5c0abd024585157ac1b04523/A-Technique-for-Drawing-Directed-Graphs.pdf or http://www.graphviz.org/documentation/TSE93.pdf in draft form

It’s basically the same, except the generic spline router came later: (PDF) A Path Router for Graph Drawing.
There’s a lot of glop that finds a “feasible region” for each edge to be drawn, as a list of touching rectangles, converts this into a constraint polygon to pass off to the spline router, spline router does a lot of mysterious divide-and-conquer iterations, returns a piecewise cubic Bezier spline that is then clipped to the node shapes, then some space around the spline is recovered for drawing subsequent edge splines.

I can’t believe this paper isn’t listed in Theory/Publications | Graphviz Sorry. There used to be a nice video from a demo session at the ACM SoCG but it may be lost in the mists of time.

How about Bob’s Nobel Prize speech?