I found a slightly more reliable way. Iβm am working on formal methods system (fizzbee.io) to validate distributed systems and algorithms, and I also have visualization feature, so I need a way to visualize a tree.
For trees that are sparse with too many nodes having a single child, the easiest and visually pleasing is add hidden nodes, and weights to get good alignment following this algorithm.
- If a node has both left and right children, add another node say M1 as the middle child, with weight=2. This will increase the probability the M1 node will be directly under the node1. (Add :s direction as well to tail edge)
- If a node only has left child, add a node R1 after the left edge. and weight=2 and direction :s
- If a node only has right child, add a node L1 before the right edge and and weight=2 and direction :s
digraph{
node [shape=circle]
splines=false
1 -> 2 [arrowhead=none];
1:s -> R1 [weight=2 style=invisible arrowhead=none];
2 -> 5 [arrowhead=none];
2:s -> M1 [weight=2 style=invisible arrowhead=none];
2 -> 4 [arrowhead=none];
5 -> 7 [arrowhead=none];
5:s -> R5 [weight=2 style=invisible arrowhead=none];
7 -> 6 [arrowhead=none];
7:s -> R7 [weight=2 style=invisible arrowhead=none];
6:s -> L6 [weight=2 style=invisible arrowhead=none];
6 -> 13 [side=R arrowhead=none] // added a new attribute (side), send this edge to the right
13 -> 11 [arrowhead=none];
13:s -> R13 [weight=2 style=invisible arrowhead=none]
11 -> 12 [side=R arrowhead=none] // this edge to the right
11:s -> L11 [weight=2 style=invisible arrowhead=none];
12 -> 14 [arrowhead=none];
12:s -> R12 [weight=2 style=invisible arrowhead=none]
4 -> 8 [arrowhead=none];
4:s -> M4 [weight=2 style=invisible arrowhead=none]
4 -> 3 [arrowhead=none];
3 -> 9 [arrowhead=none];
3:s -> R3 [weight=2 style=invisible arrowhead=none];
9 -> 10 [side=R arrowhead=none] // this edge to the right
9:s -> L9 [weight=2 style=invisible arrowhead=none]
R1 [style=invisible label=""]
R5 [style=invisible label=""]
R7 [style=invisible label=""]
R13 [style=invisible label=""]
R12 [style=invisible label=""]
R3 [style=invisible label=""]
L6 [style=invisible label=""]
L11 [style=invisible label=""]
L9 [style=invisible label=""]
M1 [style=invisible label=""]
M4 [style=invisible label=""]
R5 -> 7 [arrowhead=vee constraint=false label="i"]
M4 -> 8 [arrowhead=vee constraint=false label="index1"]
}
[dot]
digraph{
node [shape=circle]
splines=false
1 β 2 [arrowhead=none];
1:s β R1 [weight=2 style=invisible arrowhead=none];
2 β 5 [arrowhead=none];
2:s β M1 [weight=2 style=invisible arrowhead=none];
2 β 4 [arrowhead=none];
5 β 7 [arrowhead=none];
5:s β R5 [weight=2 style=invisible arrowhead=none];
7 β 6 [arrowhead=none];
7:s β R7 [weight=2 style=invisible arrowhead=none];
6:s β L6 [weight=2 style=invisible arrowhead=none];
6 β 13 [side=R arrowhead=none] // added a new attribute (side), send this edge to the right
13 β 11 [arrowhead=none];
13:s β R13 [weight=2 style=invisible arrowhead=none]
11 β 12 [side=R arrowhead=none] // this edge to the right
11:s β L11 [weight=2 style=invisible arrowhead=none];
12 β 14 [arrowhead=none];
12:s β R12 [weight=2 style=invisible arrowhead=none]
4 β 8 [arrowhead=none];
4:s β M4 [weight=2 style=invisible arrowhead=none]
4 β 3 [arrowhead=none];
3 β 9 [arrowhead=none];
3:s β R3 [weight=2 style=invisible arrowhead=none];
9 β 10 [side=R arrowhead=none] // this edge to the right
9:s β L9 [weight=2 style=invisible arrowhead=none]
R1 [style=invisible label=ββ]
R5 [style=invisible label=ββ]
R7 [style=invisible label=ββ]
R13 [style=invisible label=ββ]
R12 [style=invisible label=ββ]
R3 [style=invisible label=ββ]
L6 [style=invisible label=ββ]
L11 [style=invisible label=ββ]
L9 [style=invisible label=ββ]
M1 [style=invisible label=ββ]
M4 [style=invisible label=ββ]
R5 β 7 [arrowhead=vee constraint=false label=βiβ]
M4 β 8 [arrowhead=vee constraint=false label=βindex1β]
}
[/dot]
One big issue with this solution is adding any edge from outside the graph to a node will easily mess up the visual. So any link you have should have constraint=false.
I wanted to also, show a variable i refers to a specific node. Instead of adding a new node, that messes up the design, I see adding a link from the hidden nodes M*, L*, R* makes it look better.
Another option, if you really want the nodes to be placed correctly, or when the graph is mostly full, using a table works. But has a few drawbacks.
- The style can only be a box.
- The edges must be drawn separately with a javascript (or any other SVG editor API).
The biggest advantage is, the tree will look like a tree irrespective of other nodes in the system.
digraph {
tree [shape=plaintext class="fizzbee binarytree" label=<<table cellspacing="16" cellpadding="0" margin="1" border="0" cellborder="0">
<tr><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td port="0">0</td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td></tr>
<tr><td> </td><td> </td><td> </td><td port="1">1</td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td>2</td><td> </td><td> </td><td> </td></tr>
<tr><td> </td><td>3</td><td> </td><td> </td><td> </td><td>4</td><td> </td><td> </td><td> </td><td>5</td><td> </td><td> </td><td> </td><td>6</td><td> </td></tr>
<tr><td>7</td><td> </td><td>8</td><td> </td><td>9</td><td> </td><td>10</td><td> </td><td> 11</td><td> </td><td>12</td><td> </td><td>13</td><td> </td><td>14</td></tr>
</table>>]
tree:0:sw -> tree:1:ne [constraint="false" class="fizzbee binarytree edge" ]
}