5

Problem

The following image shows an organization chart:

Not desired output

The Full Name boxes are aligned along their centre. The desired layout, however, aligns the boxes along the top:

Desired output

Source Code

The code for the graph is as follows:

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;
}

Question

How would you align the Full Name boxes along the top?

Thank you!

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315

3 Answers3

2

Probably not exactly what you are looking for but you can set the height of the boxes to be the same (by using fixedsize=true,height="some number" then you can have the text be centered in the middle (by default). So adding the following lines:

C4 [fixedsize=true,height=2];
C5 [fixedsize=true,height=2];
C6 [fixedsize=true,height=2];
C7 [fixedsize=true,height=2];

would result in: looks better if not fully there...

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Assaf Mendelson
  • 12,701
  • 5
  • 47
  • 56
1

[A more complete answer can be found here: https://forum.graphviz.org/t/is-there-a-way-to-justify-align-nodes-on-the-same-rank/1494/4]
It can be done, but it requires several steps:

  • 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

gvpr is part of the Graphviz package, see https://www.graphviz.org/pdf/gvpr.1.pdf

Here is rankJustify.gvpr:

//
// 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);
}

Here is the original input after justification:
enter image description here

sroush
  • 5,375
  • 2
  • 5
  • 11
1

This is basically the same question as:

graphviz: left/right align nodes instead of centering?

Unfortunately, although I've been using Graphviz for about 8 years, I'm not sure how to accomplish what you want.

Community
  • 1
  • 1
ryandesign
  • 1,115
  • 6
  • 8