A variation on @greg's answer, that's a bit more compact and less dependent on the order of statements in the graph.
Upsides:
- It doesn't use
rank=same
at all
- Generally the source organization is a bit more 'tree-like', which makes edits easy.
- Only 2 minor changes to show/hide the all nodes if needed.
- It doesn't matter what order any of the edges are declared in, as long as the parent and children are all on the same line.
Downside:
Like the FAQ, it still depends on hidden nodes to balance the tree.
graph calc {
graph[nodesep=0.25, ranksep=0.3, splines=line];
node [fontname = "Bitstream Vera Sans", fontsize=14,
style=filled, fillcolor=lightblue,
shape=circle, fixedsize=true, width=0.3];
// layout all nodes 1 row at a time
// order matters on each line, but not the order of lines
"+";
"/", am, "**";
// or can be more spread out if you need to . . .
n1 [label="1"];
dm;
n2 [label="2"];
"*", bm, "3", "4", cm, "5";
// make 'mid' nodes invisible
am, bm, cm, dm [style=dotted, label=""];
// layout all visible edges as parent -> left_child, right_child
"+" -- "/","**";
"/" -- "*","3"
"**"-- "4","5";
"*" -- n1, n2;
// link mid nodes with a larger weight:
edge [style=dotted, weight=10];
"+" -- am;
"/" -- bm;
"**"-- cm;
"*" -- dm;
}
Which results in:

This is the technique I usually use when I need to draw a tree.
I don't often use gvpr because it doesn't play well in environments where I often want to use a graph (sphinx, doxygen, etc.).
But if you can use a standard pipeline to make your graph and you don't care what the resulting graph source looks like, then gvpr is your friend.