22

I am trying to recreate an example diagram for a binary search tree with GraphViz. This is how it should look in the end:

enter image description here

This is my first attempt:

digraph G {
    nodesep=0.3;
    ranksep=0.2;
    margin=0.1;
    node [shape=circle];
    edge [arrowsize=0.8];
    6 -> 4;
    6 -> 11;
    4 -> 2;
    4 -> 5;
    2 -> 1;
    2 -> 3;
    11 -> 8;
    11 -> 14;
    8 -> 7;
    8 -> 10;
    10 -> 9;
    14 -> 13;
    14 -> 16;
    13 -> 12;
    16 -> 15;
    16 -> 17;
}

But unfortunately GraphViz doesn't care about the horizontal positions of the tree, so I get:

enter image description here

How can I add constraints so that the horizontal positions of the vertices reflects their total ordering?

0__
  • 66,707
  • 21
  • 171
  • 266

1 Answers1

28

You could follow the usual approach of adding invisible nodes and invisible edges, and playing with edge weight etc. as proposed on the graphviz FAQ about balanced trees. In some simple cases, this is enough.

But there is a better solution: Graphviz comes with a tool called gvpr (graph pattern scanning and processing language) which allows to

copy input graphs to its output, possibly transforming their structure and attributes, creating new graphs, or printing arbitrary information

And since Emden R. Gansner did all the work already by creating a script which does layout nicely binary trees, heres how to to that (all credit goes to ERG):

Save the following gvpr script into a file called tree.gv :

BEGIN {
  double tw[node_t];    // width of tree rooted at node
  double nw[node_t];    // width of node
  double xoff[node_t];  // x offset of root from left side of its tree
  double sp = 36;       // extra space between left and right subtrees
  double wd, w, w1, w2; 
  double x, y, z;
  edge_t e1, e2;
  node_t n;
}
BEG_G {
  $.bb = "";
  $tvtype=TV_postfwd;   // visit root after all children visited
}
N {
  sscanf ($.width, "%f", &w);
  w *= 72;  // convert inches to points
  nw[$] = w;
  if ($.outdegree == 0) {
    tw[$] = w;
    xoff[$] = w/2.0;
  }
  else if ($.outdegree == 1) {
    e1 = fstout($);
    w1 = tw[e1.head];    
    tw[$] = w1 + (sp+w)/2.0;
    if (e1.side == "left")
      xoff[$] = tw[$] - w/2.0;
    else
      xoff[$] = w/2.0;
  }
  else {
    e1 = fstout($);
    w1 = tw[e1.head];    
    e2 = nxtout(e1);
    w2 = tw[e2.head];    
    wd = w1 + w2 + sp;
    if (w > wd)
      wd = w;
    tw[$] = wd;
    xoff[$] = w1 + sp/2.0;
  }
}
BEG_G {
  $tvtype=TV_fwd;   // visit root first, then children
}
N {
  if ($.indegree == 0) {
    sscanf ($.pos, "%f,%f", &x, &y);
    $.pos = sprintf("0,%f", y);
  }
  if ($.outdegree == 0) return;
  sscanf ($.pos, "%f,%f", &x, &y);
  wd = tw[$];
  e1 = fstout($);
  n = e1.head;
  sscanf (n.pos, "%f,%f", &z, &y);
  if ($.outdegree == 1) {
    if (e1.side == "left")
      n.pos = sprintf("%f,%f",  x - tw[n] - sp/2.0 + xoff[n], y);
    else
      n.pos = sprintf("%f,%f", x + sp/2.0 + xoff[n], y);
  }
  else {
    n.pos = sprintf("%f,%f", x - tw[n] - sp/2.0 + xoff[n], y);
    e2 = nxtout(e1);
    n = e2.head;
    sscanf (n.pos, "%f,%f", &z, &y);
    n.pos = sprintf("%f,%f", x + sp/2.0 + xoff[n], y);
  }
}

Assuming your dot file containing the graph is called binarytree.gv, you may execute the following line:

dot binarytree.gv | gvpr -c -ftree.gv | neato -n -Tpng -o binarytree.png

The result is:

Binary tree nicely layouted with graphiv and a gvpr script thanks to Emden R. Gansner

By switching around a line or two in the script, you'll even get to have the single child nodes go to the left instead of the right side.

cards
  • 3,936
  • 1
  • 7
  • 25
marapet
  • 54,856
  • 12
  • 170
  • 184
  • 1
    I am trying, but I only get `gvpr: "./tree.gv", line 46: _nd_a0:<<< -- syntax error` (I verified that the code got copied properly, also tried to copy from the Gansner post). Now I have no clue what that is supposed to mean? I see nothing suspicious in line 46 :-( – 0__ Jun 05 '12 at 22:18
  • It is the last `N { }` block somehow, if I remove that the error disappears, but also the layout is not done. Might this be a version problem? I have `gvpr version 2.26.3 (20100126.1600)` – 0__ Jun 05 '12 at 22:35
  • 1
    I used 2.28.0, and it worked right away. Gansner's post was made about 6 months after your graphviz version was released, so I'd try with a more recent version. – marapet Jun 05 '12 at 23:25
  • 1
    Indeed, I updated to GraphViz 2.28.0, and the script runs -- Fantastic, thanks so much! – 0__ Jun 06 '12 at 00:01
  • When the tree above is a binary tree the nodes 9, 12 should be drawn to the left of their parents. Can I do this with the tree.gv script? – Marten Bauer Nov 26 '13 at 07:16
  • @MartenBauer Yes, you'll just have to switch a line or two in the script. – marapet Nov 26 '13 at 08:24