2

I've generate a tree that consists of two subtrees, each containing a lot of subtrees. By default, forest renders them next to each other. However, the width of the tree extends the page and scaling it down makes it unreadable.

Thus I thought about placing one of the root's subtrees below the other. How could I achieve this without changing the orientation of all subnodes?

Simple example:

Instead of this: Instead of this

I want something like this: enter image description here

LaTeX code for the first tree:

\documentclass{article}
\usepackage{forest}

\begin{document}    
\begin{forest}
[A 
    [B [B1] [B2]] 
    [C [C1] [C2]]
]
\end{forest}  
\end{document}

Complex example: (Note: to make the example work, add the linguistics option to the forest package)

[proc [body
    [decls
        [decl [decl\_tbl\_var
            [var\_name ['v1']]
            [tbl\_type\_def [col\_list\_def
                [col\_elem ['b1 INT', roof]]
                [col\_elem ['b2 DOUBLE', roof]]
                [col\_elem
                    [col\_name ['c1']]
                    [datatype ['INT']]
                ]
            ]]
        ]]
        [decl [decl\_tbl\_var
            [var\_name ['v2']]
            [tbl\_type\_def [col\_list\_def
                [col\_elem
                    [col\_name ['c1']]
                    [datatype ['INT']]
                ]
                [col\_elem ['c2 DOUBLE', roof]]
            ]]
        ]]
    ]
    [stmts 
        [stmt\_assign
            [var ['v1']]
            [expr [subquery 
                [select\_clause [attrs 
                    [attr ['a1 b1', roof]]
                    [attr ['SUM('a2 * a3') b2', roof]]
                    [attr ['a4'] [alias ['c1']]]
                ]]
                [from\_clause [tables ['t1']]]
                [group\_by\_clause [group\_by\_expr\_list 
                    [expr ['a1']]
                    [grouping\_set [grouping\_expr\_list 
                        [grouping\_expr [expr ['a1']]]
                        [grouping\_expr [expr ['a4']]]
                    ]]
                ]]
            ]]
        ]
        [stmt\_assign
            [var ['v2']]
            [expr [subquery 
                [select\_clause [attrs 
                    [attr ['a4'] [alias ['c1']]]
                    [attr ['SUM (a2 * a3) c2', roof]]
                ]]
                [from\_clause [tables ['t1']]]
                [group\_by\_clause [group\_by\_expr\_list [expr ['a4']]]]
            ]]
        ]
    ]
]]

enter image description here

In this example, I would like to move the stmts subtree below the decls subtree.

  • I'd just use the first method I gave in your complex case: just put the sub-tree where you want it and don't make it a child of its parent at all. There's no real regularity to your tree, so there's not much to be gained by handling it more automatically. Or am I missing something? – cfr Jan 26 '18 at 1:51
6

The easiest way is to just change the structure of the tree. For example,

\documentclass{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
  [A 
    [B [B1] [, no edge, coordinate  [C, edge path'={(!uuu.parent anchor) -| (!uul.east |- .east) -- (.east)} [C1] [C2]]] [B2]] 
  ]
\end{forest}   
\end{document}

restructured tree

Depending on how often you need this, you can make it a bit more automatic i.e. not explicitly changing the tree's structure, but having Forest do it for you.

Forest is certainly going to lose track of things in this kind of case, so you need to take care of ensuring that things don't crossover each other (e.g. edges over nodes, nodes over nodes etc.).

So it is a bit more work, but very doable, especially in a relatively simple case like this one where you're just moving some sub-trees under their siblings' sub-trees and changing the edge in a pretty consistent (so algorithmic) way. So automation requires being explicit about what Forest should do, but once you define the routine, you could apply it everywhere you need it.

If you only need this a few times, though, it is easier to just restructure the tree yourself and define a style for the changes to the edge path and invisible parent.

For example, the manual approach might look like this:

\documentclass{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
  move me down/.style={
    edge path'={(!uuu.parent anchor) -| (!uul.east |- .east) -- (.east)},
    !u.no edge,
    !u.coordinate,
  }
  [A 
    [B [B1] [  [C, move me down  [C1] [C2]]] [B2]] 
  ]
\end{forest}  
\end{document}

which obviously produces the same output as the previous code.

EDIT

EDITED Since the original method didn't work when a representative example was supplied (sigh).

Basically, you put movies in the tree's preamble, to tell Forest you want this style. Then you add the option move me down to the root nodes of whichever sub-trees you want moved. Within reasonable limits, that is. I wouldn't recommend trying to move a sub-tree beneath another moved sub-tree, for example. I didn't try it, but I imagine that would not go well.

However, the style can cope with neighbouring sub-trees with differing numbers of levels. When a moved sub-tree is moved down, it will move below its neighbouring sub-tree, whether that has 2 levels or (theoretically) 77.

Note that you cannot expect Forest to figure out placement if things get complicated, since things are already pretty hairy.

For example,

\begin{forest}
  movies
  [A 
    [B [B1] [B2]] [C, move me down  [C1] [C2]]
    [B [B1] [q[x[y[z]]]B2]] [C, move me down  [C1v] [Cg2]]
 ]
\end{forest}  

produces

<code>movies</code> tree

Here is the code with for your current example:

\begin{forest}
  movies
  [proc [body
      [decls
          [decl [decl\_tbl\_var
              [var\_name ['v1']]
              [tbl\_type\_def [col\_list\_def
                  [col\_elem ['b1 INT', roof]]
                  [col\_elem ['b2 DOUBLE', roof]]
                  [col\_elem
                      [col\_name ['c1']]
                      [datatype ['INT']]
                  ]
              ]]
          ]]
          [decl [decl\_tbl\_var
              [var\_name ['v2']]
              [tbl\_type\_def [col\_list\_def
                  [col\_elem
                      [col\_name ['c1']]
                      [datatype ['INT']]
                  ]
                  [col\_elem ['c2 DOUBLE', roof]]
              ]]
          ]]
      ]
      [stmts, move me down
          [stmt\_assign
              [var ['v1']]
              [expr [subquery 
                  [select\_clause [attrs 
                      [attr ['a1 b1', roof]]
                      [attr ['SUM('a2 * a3') b2', roof]]
                      [attr ['a4'] [alias ['c1']]]
                  ]]
                  [from\_clause [tables ['t1']]]
                  [group\_by\_clause [group\_by\_expr\_list 
                      [expr ['a1']]
                      [grouping\_set [grouping\_expr\_list 
                          [grouping\_expr [expr ['a1']]]
                          [grouping\_expr [expr ['a4']]]
                      ]]
                  ]]
              ]]
          ]
          [stmt\_assign
              [var ['v2']]
              [expr [subquery 
                  [select\_clause [attrs 
                      [attr ['a4'] [alias ['c1']]]
                      [attr ['SUM (a2 * a3) c2', roof]]
                  ]]
                  [from\_clause [tables ['t1']]]
                  [group\_by\_clause [group\_by\_expr\_list [expr ['a4']]]]
              ]]
          ]
      ]
  ]]
\end{forest}

which produces

OP's representative example with <code>movies</code> style

Complete code:

\documentclass[border=10pt]{standalone}
\usepackage[linguistics,edges]{forest}
\forestset{%
  declare toks={move me}{},
  declare toks={bio}{},
  declare toks={symud}{},
  movies/.style={
    forked edges,
  },
  move me down/.style={
    before typesetting nodes={
      if nodewalk valid={p2}{
        if={>O_={!u.n children}{2}}{!u.calign=first}{},
        temptoksa/.option=name,
        bio/.option=!u.name,
        for nodewalk={p,descendants}{
          if n children=0{
            append={[, phantom, tier/.register=temptoksa]},
          }{}
        },
        for nodewalk={
          group={p2}
        }{
          symud/.register=temptoksa, 
          insert before={[, move me/.option=!n.symud, tier/.option=!n.symud, no edge, before packing={
              append/.option=move me,
            }]}
        },
        replace by/.process={Ow{name}{[, no edge, coordinate, tier=##1, before computing xy={
              for nodewalk={
                p,
                tempdimc/.option=s,
                while nodewalk valid={l}{l,tempdimc+/.option=s, typeset node, if n children=0{tempdimc+/.option=max x}{}}
              }{},
              s/.register=tempdimc,
              s+/.option=s sep,
            }, name=##1-MT, append]}},
        edge path'/.process={
          OOw2 {bio}{name}
          {(##1.parent anchor) -- ++(\forestoption{fork sep},0) |- (##2-MT) -| (.east) }%
        },
      }{},
    },
  }
}
\begin{document}
\begin{forest}
  movies
  [proc [body
      [decls
          [decl [decl\_tbl\_var
              [var\_name ['v1']]
              [tbl\_type\_def [col\_list\_def
                  [col\_elem ['b1 INT', roof]]
                  [col\_elem ['b2 DOUBLE', roof]]
                  [col\_elem
                      [col\_name ['c1']]
                      [datatype ['INT']]
                  ]
              ]]
          ]]
          [decl [decl\_tbl\_var
              [var\_name ['v2']]
              [tbl\_type\_def [col\_list\_def
                  [col\_elem
                      [col\_name ['c1']]
                      [datatype ['INT']]
                  ]
                  [col\_elem ['c2 DOUBLE', roof]]
              ]]
          ]]
      ]
      [stmts, move me down
          [stmt\_assign
              [var ['v1']]
              [expr [subquery 
                  [select\_clause [attrs 
                      [attr ['a1 b1', roof]]
                      [attr ['SUM('a2 * a3') b2', roof]]
                      [attr ['a4'] [alias ['c1']]]
                  ]]
                  [from\_clause [tables ['t1']]]
                  [group\_by\_clause [group\_by\_expr\_list 
                      [expr ['a1']]
                      [grouping\_set [grouping\_expr\_list 
                          [grouping\_expr [expr ['a1']]]
                          [grouping\_expr [expr ['a4']]]
                      ]]
                  ]]
              ]]
          ]
          [stmt\_assign
              [var ['v2']]
              [expr [subquery 
                  [select\_clause [attrs 
                      [attr ['a4'] [alias ['c1']]]
                      [attr ['SUM (a2 * a3) c2', roof]]
                  ]]
                  [from\_clause [tables ['t1']]]
                  [group\_by\_clause [group\_by\_expr\_list [expr ['a4']]]]
              ]]
          ]
      ]
  ]]
\end{forest}

\end{document}

I don't recommend trying this without forked edges as the squared lines will certainly cut through other things, even in the best possible scenarios.

  • That's kind of what I was looking for. However, my real tree is a bit more complex and I need to move one subtree a few more levels down. Could you extend your answer for more complex trees (e.g. if the left subtree has height 7)? – Paddre Jan 23 '18 at 11:40
  • @Paddre Could you edit your question with a more representative example? For instance, if different sub-trees may have different heights and you might need to move one below a tree of height 7 and another below a tree of height 3, include 4 sub-trees. If all the top layer sub-trees have height 7, that's straightforward to extend, but a slightly different approach will work better if they vary, so the details of what you need make a difference, I think. – cfr Jan 23 '18 at 16:25
  • @Paddre Well you didn't provide anything. The above should give you a start. You'll probably need to tweak it to whatever your use-case actually is, since I don't know that, and this needs to be quite tailored. – cfr Jan 24 '18 at 4:52
  • Thx for your answer and the effort and sorry I didn't answer yesterday. Been busy with other important stuff. I now added a more complex and real example. As far as I understood, I would need to include the style in the preamble, add "movies" to the beginning of the forest environment and add ", move me down" to the stmt node. However, the stmt node is moved down to the same level as the lowest leaf of the left decl subtree which creates a lot of spacing between the decl subtrees. – Paddre Jan 25 '18 at 8:02
  • 1
    @Paddre It depends on the situation. You might be able to tweak edge path' to shift the nodes a bit one way or the other e.g. using ([xshift=<whatever>]<coordinate/node>). Or you can change x in before drawing tree i.e. after Forest has calculated the x and y automatically. Or you can try altering s or s sep for all or part of the tree. Part of the trick is to do things during the correct stage of processing, but partly it is a matter of just fiddling in corner cases because you're overriding Forest's placement algorithm. Do you have an example? – cfr Jan 29 '18 at 23:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.