Decoration in graph edges

Hi all!
I am new to Graphviz. Right now, I need to add decorations to graph edges as shown in the picture below.
Does anyone know if it is possible, I searched the site and the documentation but could not find anything related.
I appreciate your help!
Thanks!
My graph should have dots in the edges. Some dots are linked by an arc.

Recreating this would be very difficult (or worse). The “4” points and flags, the “5” points, and the “20” flags would probably be the hardest. Almost none of it would be easy.
I’d suggest trying the pic/dpic/gpic/pikchr language (see Pikchr: Documentation).
If Graphviz still is your 1st choice, come back & we’ll step through it.

Hi steveroush!

Thank you for the reply! I saw in Pikchr documentation that its code is intended to be inserted in markdown blocks. Unfortunately, my knowledge of markdown language is almost null. Since I am working with the graph data structure, tools like graphviz suit me well. I have been trying pyvis (visualization) and networkx (graph manipulation) too. The problem with representing the decorations on the edges remains, though :slight_smile:

You should find something relevant if you search on the internet for “graphviz edge decorators” though I am not sure they will satisfy your requirements.

Hi,
I am not sure how close you want the layout to resemble your example but if you consider each dot on the edges as a node and each label as a node you could achieve something like this:
[dot]
strict digraph “main”
{
layout=“dot”;
concentrate=“true”;
rankdir=“LR”;

"b" -> "e";
"c" -> "e";
"d" -> "e";
{rank="same";b;c;d};
"a" -> "p1"[ arrowhead="none" ];
"p1" -> "p2"[ arrowhead="none" ];
"p2" -> "p3"[ arrowhead="none" ];
"p3" -> "b";
"a" -> "p4"[ arrowhead="none" ];
"p4" -> "p5"[ arrowhead="none" ];
"p5" -> "p6"[ arrowhead="none" ];
"p6" -> "c";
"a" -> "p7"[ arrowhead="none" ];
"p7" -> "p8"[ arrowhead="none" ];
"p8" -> "d";
"a" -> "p9"[ arrowhead="none" ];
"p9" -> "p10"[ arrowhead="none" ];
"p10" -> "e";
"p2" -> "mid1"[ arrowhead="none" ];
"mid1" -> "p5"[ arrowhead="none" ];
{rank="same";p2;mid1}
"mid1" -> "20"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";mid1;20}
"p1"[ shape="point" ];
"p2"[ shape="point" ];
"p3"[ shape="point" ];
"p4"[ shape="point" ];
"p5"[ shape="point" ];
"p6"[ shape="point" ];
"p7"[ shape="point" ];
"p8"[ shape="point" ];
"p9"[ shape="point" ];
"p10"[ shape="point" ];
"p1" -> "1"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p1;1}
"p3" -> "11"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p3;11}
"p10" -> "51"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p10;51}
"p9" -> "52"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p9;52}
"p4" -> "1bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p4;"1bis"}
"p6" -> "11bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p6;"11bis"}
"p7" -> "13"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p7;"13"}
"p8" -> "13bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
{rank="same";p8;"13bis"}
"1"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
"11"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
"51"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="5" ];
"52"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="5" ];
"20"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
"1bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="1" ];
"11bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="11" ];
"13"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="13" ];
"13bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="13" ];
"mid1"[ shape="point" height="0.003" label="" ];

}
[/dot]

source:

strict digraph "main"
{
    layout="dot";
    concentrate="true";
    rankdir="LR";
    "b" -> "e";
    "c" -> "e";
    "d" -> "e";
    {rank="same";b;c;d};
    "a" -> "p1"[ arrowhead="none" ];
    "p1" -> "p2"[ arrowhead="none" ];
    "p2" -> "p3"[ arrowhead="none" ];
    "p3" -> "b";
    "a" -> "p4"[ arrowhead="none" ];
    "p4" -> "p5"[ arrowhead="none" ];
    "p5" -> "p6"[ arrowhead="none" ];
    "p6" -> "c";
    "a" -> "p7"[ arrowhead="none" ];
    "p7" -> "p8"[ arrowhead="none" ];
    "p8" -> "d";
    "a" -> "p9"[ arrowhead="none" ];
    "p9" -> "p10"[ arrowhead="none" ];
    "p10" -> "e";
    "p2" -> "mid1"[ arrowhead="none" ];
    "mid1" -> "p5"[ arrowhead="none" ];
    {rank="same";p2;mid1}
    "mid1" -> "20"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";mid1;20}
    "p1"[ shape="point" ];
    "p2"[ shape="point" ];
    "p3"[ shape="point" ];
    "p4"[ shape="point" ];
    "p5"[ shape="point" ];
    "p6"[ shape="point" ];
    "p7"[ shape="point" ];
    "p8"[ shape="point" ];
    "p9"[ shape="point" ];
    "p10"[ shape="point" ];
    "p1" -> "1"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p1;1}
    "p3" -> "11"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p3;11}
    "p10" -> "51"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p10;51}
    "p9" -> "52"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p9;52}
    "p4" -> "1bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p4;"1bis"}
    "p6" -> "11bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p6;"11bis"}
    "p7" -> "13"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p7;"13"}
    "p8" -> "13bis"[ fontname="Arial" fontsize="10" style="dotted" arrowhead="none" ];
    {rank="same";p8;"13bis"}
    "1"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
    "11"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
    "51"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="5" ];
    "52"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="5" ];
    "20"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" ];
    "1bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="1" ];
    "11bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="11" ];
    "13"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="13" ];
    "13bis"[ shape="rect" height="0.25" width="0.25" fixedsize="True" fillcolor="White" fontname="Arial" fontsize="10" style="filled" label="13" ];
    "mid1"[ shape="point" height="0.003" label="" ];
}

OK, I suppose I could recreate the graph on a bet, but below is as far as I got. It took dot, neato, and two gvpr scripts. And that was the easy part. I believe almost all of the rest would require manual (eyeball) placement.
fancyGraph

On the other hand, below is a partial recreation done with pikchr (written here Pikchr: PikchrShow). It is not trivial, but only 54 lines to get this far.
Conclusion - sometimes Graphviz is not the best solution, yet.

BartB and Steveroush,

Thank you for your help! It is much appreciated!
Steveroush you got super close, I am impressed. You are right the solution in Pickchr is superior for my case. Because the arcs connecting some dots are important in the graph representation. I will study this. Would you mind sharing the code for both solutions? It would help my studies.
Just one question, is it possible to have graph edges and nodes as input for Pikchr? Because my data structure is a graph. Or would I need to do everything manually?
Once more, thank you guys!

Graphviz is often difficult to “tweak”. pic is easy to “tweak”, but does not do high-level layout. You will probably need to do most layout manually.
There have been at least 5 implementations of the pic language. {The original pic, picasso (part of DWB from AT&T), gpic / gnu pic (part of groff), dpic (Dwight Aplevich / dpic · GitLab), and pikchr}. All slightly different, but none do high-level layout. That is up to the user. Note that Graphviz can produce some ugly pic (I wrote some of the driver, blush).
Here is the pikchr file:

## duplicating a desired Graphviz graph 
A:box rad 10px "a" "40" 
AC:arrow right 2
C:box rad 10px "c" "xx" 
CE:arrow right 2
E:box rad 10px "e" "xx" 
B:box rad 10px "b" "xx" at C + (0, 2)
D:box rad 10px "d" "xx" at C - (0, 2)
AB: arrow from A to B chop
AD: arrow from A to D chop
BE: arrow from B to E chop
DE: arrow from D to E chop
AB1: dot rad 3pt with .c at 20/100 of the way between A and B
AB2: dot rad 3pt with .c at 35/100 of the way between A and B
AB3: dot rad 3pt with .c at 75/100 of the way between A and B

AC1: dot rad 3pt with .c at 20/100 of the way between A and C
AC2: dot rad 3pt with .c at 35/100 of the way between A and C
AC3: dot rad 3pt with .c at 75/100 of the way between A and C

AD1: dot rad 3pt with .c at 20/100 of the way between A and D
AD3: dot rad 3pt with .c at 75/100 of the way between A and D

ARC1: arc from AC2 to AB2
# pikchr does not do arcs w/ rad correctly, so we use splines
dy=boxht+B.n.y-A.n.y
dx=E.n.x-A.n.x
ARC2: spline -> from A.n up dy then right dx then down to E.n
ARC3: spline -> from D.se - (boxwid/4,0) down boxht*.7 then left boxwid - (boxwid/2) then  to D.sw + (boxwid/4,0) chop
DD1: dot rad 3pt with .c at ARC3.start - (0,boxwid/5) 
# the X value was manually tweaked
DD1: dot rad 3pt with .c at ARC3.end - (.03,boxwid/4) 

AE1: dot rad 3pt with .c at A.n + (0,boxht)
AE2: dot rad 3pt with .c at E.n + (0,boxht)
# create all the rest of the nodes here
########
# now create "edge labels"
boxht=boxht/3
boxwid=boxht
dx=boxwid*2
dy=dx
LBL_AE1: box "5" at AE1.c - (dx,0)
LBL_AB1: box "1" at AB1.c + (0,dy)
LBL_AC1: box "1" at AC1.c + (dx,-dy)
LBL_AD1: box "13" at AD1.c - (0,dy)
LBL_ARC1: box "20" at ARC1.c + (dx,0)
## now, connect labels to edges
line thin thin from LBL_AB1 to AB1 chop
line thin thin from LBL_AE1 to AE1 chop
line thin thin from LBL_AC1 to AC1 chop
line thin thin from LBL_AD1 to AD1 chop
### the following was manually tweaked
line thin thin from LBL_ARC1 to ARC1 + (.072,0) chop

And here is the Graphviz file:

digraph fancy{
  rankdir=LR
  ranksep=1.8
  nodesep=.95
  splines=false
  //nextSplines=curved
 // newrank=true

  node[shape=square style=rounded]
  edge[XXXXlabelfloat=true decorate=true labeldistance=2]

  a [label="a\n40" group=X]

  {rank=same
  b [label="b\n21"]
  c [label="c\n21" group=X]
  d [label="d\n17"]
  }
  e [label="e\n40" group=X]

a -> b [addPoints="10,25,80" label=<<table cellspacing="0" cellpadding="0"><tr><td border="0">5</td></tr></table>> headlabel=<<table cellspacing="0" cellpadding="0"><tr><td border="0">B</td></tr></table>> taillabel=<<table cellspacing="0" cellpadding="0"><tr><td border="0">Y</td></tr></table>>]
a -> c [addPoints="10,25,80" label=<<table cellspacing="0" cellpadding="0"><tr><td border="0">6</td></tr></table>> headlabel=<<table cellspacing="0" cellpadding="0"><tr><td border="0">5</td></tr></table>> taillabel=<<table cellspacing="0" cellpadding="0"><tr><td border="0">7</td></tr></table>>]
a -> d [addPoints="10,80"]

b -> e [addPoints="10,65,80"]
c -> e [addPoints="10,65,80"]
d -> e [addPoints="10,80"]

// feed to alterSimpleEdge.gvpr
 d:se -> d:sw [edgeType=curved edgeDirection=cw]

// do this later
// a:n -> e:n [edgeType=curved edgeDirection=cw edgeOffset="3in"]
}

Here is a quick&dirty gvpr (https://www.graphviz.org/pdf/gvpr.1.pdf) script to place nodes on a straight-line edge:

/**************************************************************
quick and dirty: insert "point" nodes on a straight edge, n% from tail to head
**************************************************************/
BEGIN{
  node_t  aNode;
  string  point[int], tok[int], tName, tmpS;
  int     cnt, i, Tail, Head, pIndx;
  float   percent, xT, yT, xH, yH;
}
BEG_G{
  if (hasAttr($, "nextSplines") &&  $.nextSplines!="")
    $G.splines=$G.nextSplines;
}
E{
  if (hasAttr($, "erasePos") &&  $.erasePos!="")
    $.pos="";
  if (hasAttr($, "addPoints") &&  $.addPoints!="") {
    cnt=tokens($.pos, point);
    Tail=0;
    Head=cnt-1;
    for (i=0; i<cnt; i++) {
      tmpS=point[i];
      // arrowheads?
      if (point[i]=="[se]*") {
        //print("//  arrowhead : ",point[i]);
        tmpS=substr(point[i],2);
        if (point[i]=="s*") {
          Tail=i;
          point[i]=tmpS;
        } else if (point[i]=="e*") {
          Head=i;
          // if no tail (but head), set to 1
          if (Tail==Head) {
            Tail++;
          }
          point[i]=tmpS;
        }
        //print("// Head: ", point[Head], "   Tail: ",point[Tail]);
        sscanf (point[Tail], "%lf,%lf", &xT, &yT);
        sscanf (point[Head], "%lf,%lf", &xH, &yH);
        //print("// : ", xT, ",",yT, "  ", xH,",",yH);
      }
    }  // end of loop through points
    cnt=tokens($.addPoints, tok, "[;,]");
    //print("// tok[0] >", tok[0],"<");
    pIndx=0;
    for (tok[i]) {
      tName="__" + $.tail.name + "_" +$.head.name + "_" + (string)(++pIndx);
      aNode=node($G, tName);
      aNode.shape="point";
      aNode.width=".09";
      percent=((float)tok[i])/100.;
      aNode.pos=sprintf("%d,%d", xT + (percent * (xH-xT)), yT+ (percent * (yH-yT)));
    }
  }
}

Finally, the (Linux) command line:

f=fancyGraph.gv; T=png;F=`BASE $f`;
  dot $f |gvpr -cf fancyGraph.gvpr|
  gvpr -cf alterSimpleEdge.gvpr |
  neato -n2 -T$T  >$F.$T

alterSimpleEdge.gvpr can be found here: Graphviz post-processor to customize edges · GitHub

Perfect! Thank you!

It is worth noting that the PIC backend does not appear to work, PIC backend broken (#2487) · Issues · graphviz / graphviz · GitLab.