# Is there a way to justify/align nodes on the same rank?

**URL:** <https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494>\
**Category:** Help\
**Created:** [March 7, 2023, 8:15pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494 "2023-03-07T20:15:36Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![steveroush](https://avatars.discourse-cdn.com/v4/letter/s/a9adbd/32.png) [@steveroush](https://forum.graphviz.org/u/steveroush)\
**Post date:** [March 7, 2023, 8:15pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/1 "2023-03-07T20:15:36Z")

</div>

**Dot** aligns (justifies) all the nodes on the same rank to a common Y value (or common X value if **rankdir=LR/RL** )  
For example the two nodes on the right side are on the same rank and share a common X value:  
 ![yaJustify0](https://global.discourse-cdn.com/graphviz/original/1X/1744272a43258803b4ae382a737b823a6ea2d0ed.png)  
Is there a (built-in) way to left-justify the two nodes?

---

<div class="post-metadata">

**Author:** ![scnorth](https://sea2.discourse-cdn.com/graphviz/user_avatar/forum.graphviz.org/scnorth/32/89_2.png) [@scnorth](https://forum.graphviz.org/u/scnorth)\
**Post date:** [March 8, 2023, 1:55pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/2 "2023-03-08T13:55:57Z")

</div>

This is a great idea, but we did not implement this. I wonder how hard it would be - there might be some code that assumes node center points are on the midline of a rank, but I don’t think that’s baked in very deeply. Think the spline router already depends on a function that determines for each node, the box path from an edge port to the inter-rank space where the spline is going next, so this feature would fit in with that already.

---

<div class="post-metadata">

**Author:** ![steveroush](https://avatars.discourse-cdn.com/v4/letter/s/a9adbd/32.png) [@steveroush](https://forum.graphviz.org/u/steveroush)\
**Post date:** [March 9, 2023, 4:41pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/3 "2023-03-09T16:41:50Z")

</div>

- I have post-processing code that will allow justifying nodes within the confines of a rank, but I did not want to announce it until I was sure the capability did not already exist.
- post-processing is OK, but built-in would be better. I have created an issue ([dot - allow nodes to be justified/aligned off of rank centerline (#2359) · Issues · graphviz / graphviz · GitLab](https://gitlab.com/graphviz/graphviz/-/issues/2359)) requesting this feature

---

<div class="post-metadata">

**Author:** ![steveroush](https://avatars.discourse-cdn.com/v4/letter/s/a9adbd/32.png) [@steveroush](https://forum.graphviz.org/u/steveroush)\
**Post date:** [March 10, 2023, 6:44pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/4 "2023-03-10T18:44:58Z")

</div>

Here is a gvpr ([https://www.graphviz.org/pdf/gvpr.1.pdf](https://www.graphviz.org/pdf/gvpr.1.pdf)) program that will (re)justify/align nodes within their given rank.

use it like this:

- create a **dot** file as normal
- add this new attribute to all nodes you want justified **rankjustify=** X where X can be _t|b|l|r|c|max|min_. Different nodes can have different **rankjustify** values.
- run **dot** , **gvpr** , and **neato** to produce the desired graph
  - `dot -Tdot myfile.gv | gvpr -cf rankJustify.gvpr | neato -n -Tpng >myfile.png`

Here is rankJustify.gvpr:

```auto
//
// justify nodes that are on the same rank
//
BEGIN {
  int nxt=0, i, vert, rankPos[], OK[], checked[];
  string RankDir;
  float num, deltaX, deltaY, maxHeight[], maxWidth[];
  graph_t aGraph, Root, Parent[];
  node_t aNode; 
  ///////////////////////////////////////////////////////////////////////////////
  void nodeJustify(node_t aN){
    float XX, YY;
    int err;

    if(checked[aN]==1) return;
    checked[aN]=1;    
    if (! hasAttr(aN, "rankjustify") || aN.rankjustify=="") continue;
    XX=aN.X;
    YY=aN.Y;
    deltaX=0.;
    deltaY=0.;
    if (vert==1){
      deltaY=72.*(maxHeight[aN.Y]-(float)aN.height)/2.; // inches to points
    }else{
      deltaX=72.*(maxWidth[aN.X]-(float)aN.width)/2.; // inches to points
    }
    aN.oldPos=aN.pos;
    switch(OK[aN.rankjustify]){
      case "1":
        break;
      case "-1":
        deltaX=-deltaX;
        deltaY=-deltaY;
        break;
      case "0":
        deltaX=0.;
        deltaY=0.;
        break;    
      default:
        print("// Error:: node ",aN.name, " has invalid rankjustify value (", aN.rankjustify,")");
        printf(2, "Error:: node %s, has invalid rankjustify value (%s)\n", aN.name, aN.rankjustify);      
        continue 2; // exit two levels
        break;
    }
    aN.pos=sprintf("%.2f,%.2f", (aN.X + deltaX), (aN.Y + deltaY));  
    if (hasAttr(aN,"xlp") && aN.xlp!="") {
      sscanf (aN.xlp, "%lf,%lf", &XX, &YY);
      aN.xlp=sprintf("%.2f,%.2f", (XX + deltaX), (YY + deltaY));
    }
  }
  ///////////////////////////////////////////////////////////////////////////////
  void checkaGraph(graph_t aGraph){
      for (aNode=fstnode(aGraph);aNode;aNode = nxtnode_sg(aGraph, aNode)){
        Parent[aNode]=aGraph;
        // first pass through the nodes
        // find max width/height for each rank/cluster (based on common Y or X)
        if (RankDir=="TB|BT"){
          rankPos[aNode.Y]=1;
          if (maxHeight[aNode.Y]<aNode.height){
            maxHeight[aNode.Y]=aNode.height;
          }
        } else { // LR or RL
          rankPos[aNode.X]=1;
          if (maxWidth[aNode.X]<aNode.width)
            maxWidth[aNode.X]=aNode.width;
        }
      }
      for (aNode=fstnode(aGraph);aNode;aNode = nxtnode_sg(aGraph, aNode)){
        nodeJustify(aNode);
      }
  }
  ///////////////////////////////////////////////////////////////////////////////
  graph_t graphTraverse(graph_t thisG){
    for (aGraph = fstsubg(thisG); aGraph; aGraph = nxtsubg(aGraph)) {
      if (match(aGraph.name,"cluster")==0 || (hasAttr(aGraph, "cluster") && aGraph.cluster=="true")){
        unset(maxWidth);
        unset(maxHeight);	
        checkaGraph(aGraph);
      }
      aGraph = graphTraverse(aGraph);
    }
    return thisG;
  } // end of graphTraverse
}
BEG_G{
  Root=$G;
  if (hasAttr(Root, "layout")){
    // other values (including "") cause problems with later execution
    Root.layout="neato";  
  }
  if (! hasAttr(Root, "splines") || Root.splines==""){
    // dot defaults to true, neato defaults to false
    Root.splines="true";  
  }  
  // determine acceptable justification values
  if (! hasAttr($G, "rankdir") || $.rankdir==""){
    RankDir="TB";
  }else{
    RankDir=$G.rankdir;
  }
  vert=1;
  OK["c"] =0;
  if (RankDir=="TB") {
    OK["t"] =1;
    OK["b"] =-1;
    //OK["max"] =1;
    //OK["min"] =-1;
    OK["max"] =-1;
    OK["min"] =1;
    OK["l"] =1;
    OK["r"] =-1;
  } else if (RankDir=="BT"){
    OK["t"] =1;
    OK["b"] =-1;
    //OK["max"] =-1;
    //OK["min"] =1;
    OK["max"] =1;
    OK["min"] =-1;
    // do we really want these next two?
    OK["l"] =1;
    OK["r"] =-1;  
  } else if (RankDir=="LR"){
    vert=0;
    OK["l"] =-1;
    OK["r"] =1;
    //OK["max"] =-1;
    //OK["min"] =1;
    OK["max"] =1;
    OK["min"] =-1;
    // do we really want these next two?
    OK["t"] =-1;
    OK["b"] =1;
  } else if (RankDir=="RL"){
    vert=0;
    OK["l"] =-1;
    OK["r"] =1;
    //OK["max"] =1;
    //OK["min"] =-1;
    OK["max"] =-1;
    OK["min"] =1;
  }
  // traverse the graph
  // find all clusters
  // within each cluster, find all nodes (at highest level
  // find max & min Y (or X) within the set of nodes
  // then revisit all of the nodes and adjust Y (or X) as desired
  // (finally) for all nodes not visited
  // find max & min Y (or X) within the set of nodes
  // then revisit all of the nodes and adjust Y (or X) as desired
  graphTraverse (Root);
  unset(maxWidth);
  unset(maxHeight);
  checkaGraph(Root);
}

```

And an example of the result:

 ![orgJustify0.TB.min](https://global.discourse-cdn.com/graphviz/original/1X/a2ef8d75c2ae6797643dfd8ec229466f60279ba9.png)

---

<div class="post-metadata">

**Author:** ![bendecaf](https://avatars.discourse-cdn.com/v4/letter/b/2acd7d/32.png) [@bendecaf](https://forum.graphviz.org/u/bendecaf)\
**Post date:** [January 19, 2024, 6:02pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/5 "2024-01-19T18:02:34Z")

</div>

Hello Steve,

this looks like great work and might solve a problem I have. However, I can’t seem to reconstruct your result with your instructions. I must be doing something wrong. Would you mind posting the dot source for your example chart above?

Thanks!

---

<div class="post-metadata">

**Author:** ![steveroush](https://avatars.discourse-cdn.com/v4/letter/s/a9adbd/32.png) [@steveroush](https://forum.graphviz.org/u/steveroush)\
**Post date:** [January 19, 2024, 7:01pm UTC](https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/6 "2024-01-19T19:01:08Z")

</div>

Below is my input, HOWEVER I have not set **rankjustify=t** to any of the nodes, I used the command line to do that.  
Did you set **rankjustify**?

```auto
f=orgJustify0.gv;T=png; F=`basename -s .gv $f`;dot -Nrankjustify=t $f | gvpr -cf justifyRanks.gvpr | neato -n -T$T >$F.$T 

```

```auto
digraph G {
  fixedwidth = true;

  node [
    shape="box",
    style="rounded",
    penwidth = 1,
    width=2.0,
    fontname = "Arial",
    fontsize = 12
  ];

  edge [
    color="#142b30",
    arrowhead="vee",
    arrowsize=0.75,
    penwidth = 2,
    weight=1.0
  ];

  A1 [ label = <
    <TABLE BORDER="0" CELLSPACING="5">
      <TR>
        <TD><FONT POINT-SIZE="16">Top Level</FONT></TD>
      </TR>
      <TR>
        <TD><FONT POINT-SIZE="18">Owner</FONT></TD>
      </TR>
    </TABLE>>
  ];

  B3 [ label = <
    <TABLE BORDER="0" CELLSPACING="5">
      <TR>
        <TD><FONT POINT-SIZE="12">Second Level<BR/>(1)</FONT></TD>
      </TR>
      <TR>
        <TD><FONT POINT-SIZE="14">Owner</FONT></TD>
      </TR>
    </TABLE>>
  ];

  B4 [ label = <
    <TABLE BORDER="0" CELLSPACING="5">
      <TR>
        <TD><FONT POINT-SIZE="12">Second Level<BR/>(2)</FONT></TD>
      </TR>
      <TR>
        <TD><FONT POINT-SIZE="14">Owner</FONT></TD>
      </TR>
    </TABLE>>
  ];

  B5 [ label = <
    <TABLE BORDER="0" CELLSPACING="5">
      <TR>
        <TD><FONT POINT-SIZE="12">Second Level<BR/>(3)</FONT></TD>
      </TR>
      <TR>
        <TD><FONT POINT-SIZE="14">Owner</FONT></TD>
      </TR>
    </TABLE>>
  ];

  B6 [ label = <
    <TABLE BORDER="0" CELLSPACING="5">
      <TR>
        <TD><FONT POINT-SIZE="12">Second Level<BR/>(4)</FONT></TD>
      </TR>
      <TR>
        <TD><FONT POINT-SIZE="14">Owner</FONT></TD>
      </TR>
    </TABLE>>
  ];

  C4 [ label = <
    <TABLE BORDER="0" CELLPADDING="0" ALIGN="LEFT">
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
    </TABLE>>
  ];

  C5 [ label = <
    <TABLE BORDER="0" CELLPADDING="0" ALIGN="LEFT">
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
    </TABLE>>
  ];

  C6 [ label = <
    <TABLE BORDER="0" CELLPADDING="0" ALIGN="LEFT">
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
    </TABLE>>
  ];

  C7 [ label = <
    <TABLE BORDER="0" CELLPADDING="0" ALIGN="LEFT">
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
      <TR>
        <TD ALIGN="LEFT"><FONT POINT-SIZE="10">Full Name</FONT></TD>
      </TR>
    </TABLE>>
  ];

  { rank = same; B3; B4; B5; B6; }

  A1 -> B3:n;
  A1 -> B4:n;
  A1 -> B5:n;
  A1 -> B6:n;
  { rank = same; C4; C5; C6; C7; }
  B3 -> C4;
  B4 -> C5;
  B5 -> C6;
  B6 -> C7;
}

```
