# Automatic page break with Forest? (Spanning a tree over multiple pages.)

My software produces some documentation automatically. One of the elements I am adding to the documentation is a tree in the style described here:

Making a (simple) directory tree

As the tree can be large it would span several pages. I see here that there is a way to manually tell the tree where to break pages:

How do I split a forest tree into two trees with proper vertical alignment?

But what I really want is to have the tree automatically page break. Is there a way to do this with Tikz Forest?

An example of the forest code is below:

\documentclass[english,10pt,oneside,table,xcdraw]{book}

\usepackage{lmodern}
\usepackage[lgr,T1]{fontenc}
\usepackage{geometry}
\usepackage[utf8]{inputenc}
\usepackage{forest}
\usepackage{longtable}
\usepackage{tikz}

\forestset{
nodeStyle/.style={
before typesetting nodes={
edge=#1,
for ancestors={
edge=#1,
#1,
},
#1,
}
},
my edge label/.style={
edge label={
node [midway, fill=white, font=\scriptsize] {#1}
}
}
}

\begin{document}
\newcolumntype{C}[1]{>{\centering}p{#1}}
\newcolumntype{M}{>{\begin{varwidth}{4cm}}l<{\end{varwidth}}} %M is for Maximal column

\definecolor{tempColor}{rgb}{0.2,1,0.2}
\begin{figure}
\begin{center}
\par\medskip
{
\footnotesize
\begin{forest}
for tree=
{
if level=0{align=center}
{
% allow multi-line text and set alignment
align={@{}C{50mm}@{}},
},
grow'=0,
font=\sffamily\bfseries,
edge path=
{
\noexpand\path [draw, \forestoption{edge}] (!u.south west) +(7.5pt,0) |- (.child anchor) \forestoption{edge label};
},
before typesetting nodes=
{
if n=1
{
insert before={[,phantom]}
}
{}
},
parent anchor=south,
child anchor=west,
anchor=west,
calign=first,
inner xsep=7pt,
edge={ultra thick, rounded corners=2pt},
fill=white,
rounded corners=2pt,
fit=band,
before computing xy={l=15pt},
}
[{TypeData}
[{ModelType}
]
[{StationConfig},fill=lightgray
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig},drop shadow,fill=lightgray[child, font=\sffamily, align=left][child, font=\sffamily, align=left][child, font=\sffamily, align=left]
]
[{StationConfig}
]
[{StationConfig}
]
[{StationConfig}
]
]
[{SecondType}
]
]
\end{forest}
}
\caption{ }
\end{center}
\end{figure}
\end{document}

• Please can you make your code compilable? This makes it much easier for people to help.
– cfr
Nov 17 '16 at 22:51
• How is C defined for a start?
– cfr
Nov 17 '16 at 22:54
• See my comment. There is now a much easier way of doing this with current Forest.
– cfr
Nov 17 '16 at 23:06
• You cannot split your tree with the methods given there because your tree does not grow downwards but to the right. That is, you have grow'=0 and the methods there rely on the tree growing down. That is, the manual methods given there can't be applied to your tree. So I'd suggest trying to solve that problem first.
– cfr
Nov 18 '16 at 0:09

Please always post complete, working code unless the issue is a compilation error. Just persuading your code to compile was an exercise in guesswork. For this reason, I may have changed your code in ways I do not even realise.

Here's my guess at your preamble:

\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage{forest}
\newcolumntype{C}[1]{@{}>{\centering\arraybackslash}m{#1}@{}}


Obviously, this is probably not identical with yours, which is only to be expected when guessing. The answer below is based on the assumption that this is your preamble.

If the problem were merely one of automation, then I would expect your example to demonstrate the manual method. Having a suspicious mind, I'm guessing you know that's not the case, even if you do not know why.

None of this is terribly helpful, of course. It means time which could be spent on your question gets spent instead on a bunch of other things such as trying to figure out which packages to load and inventing a definition for C.

OK. So the code given in the answers to the question you linked can only be used to split trees which grow downwards. Yours grows eastwards.

Moreover, at least some of the examples there (e.g. mind) cannot be compiled with current Forest without responding to TeX's prompts on the console or modifying forest.sty.

So you will probably need to compile them on the command line so that you can dismiss the prompts. There is no error. TeX is just \showing you something as it will tell you if you ask for further help. So there are no concerns here about ignoring errors. You just need to let it show you what it wants to show you.

The following code does the following:

• it updates your code to use Forest 2's folder style, which makes drawing this style of tree easier;
• it provides a dir tree style for your particular flavour of that style;
• it provides a split dir here=<text> style to split the tree at the current node, analogous to the linked split here=<text> style.

The style definitions are based on my adaption of Sašo's code, as included in the answers to the linked question and noted in the comments below.

Limitations:

• nothing is split automatically;
• the split will not work correctly if you do strange things, where 'strange' means roughly 'things which will cause the split not to work correctly';
• even if you do less strange things, the split will lose all edges drawn to siblings of the current node typeset after the split;
• only one split per tree is supported (but see below for multi-split version);
• the split(s) depend(s) on the default 'linear order' of nodes, so that the next node in the linear order is the first node which should be drawn after the split and all and only that and later nodes in that order should be drawn after that split.

Code:

\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage[edges]{forest}
\newcolumntype{C}[1]{@{}>{\centering\arraybackslash}m{#1}@{}}
\forestset{%
dir tree/.style={%
for tree={%
folder,
grow'=0,
if level=0{align=center}
{
align={C{50mm}},
},
font=\sffamily\bfseries\footnotesize,
inner xsep=7pt,
edge={ultra thick, rounded corners=2pt},
fill=white,
rounded corners=2pt,
},
},
}
% addaswyd o gôd Sašo Živanović: https://tex.stackexchange.com/a/296771/
\def\hiddenparcommand{\par}
\forestset{%
declare toks register={split here interject},
declare toks register={split here node},
declare toks register={split resume here node},
split here interject={},
split here node={},
split resume here node={},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
split dir here/.style={%
split here node/.option=name,
split here interject={#1},
split dir tree,
delay={
for next node={split dir resume here},
},
},
split dir resume here/.style={%
split resume here node/.option=name,
},
split dir tree/.code={%
\forestset{%
draw tree stage/.style={
for root'={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
for tree={%
to widest,
if name/.wrap pgfmath arg={{####1}{label={[text=gray, anchor=north, font=\scriptsize]below:{[cont.]}}}{}}{split_here_node},
if name/.wrap pgfmath arg={{####1}{edge={densely dotted, gray}, label={[font=\scriptsize, anchor=south, text=gray]above:{[cont.]}}}{}}{split_resume_here_node},
},
},
for nodewalk/.wrap pgfmath arg={{draw tree processing order/.style={name=####1,preceding nodes}}{}}{split_here_node},
for root'={draw tree},
TeX/.wrap pgfmath arg={\hiddenparcommand ####1\hiddenparcommand}{split_here_interject},
for nodewalk/.wrap pgfmath arg={{draw tree processing order/.style={name=####1,following nodes}}{}}{split_resume_here_node},
for root'={draw tree},
},
}
}
}
\begin{document}
\begin{forest}
dir tree,
before drawing tree={
for tree={
tikz+/.wrap 2 pgfmath args={\node [anchor=west, font=\footnotesize, text=red] at (.east) {L:#1; n:#2};}{level()}{n()}
}
}
[TypeData
[ModelData
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig, split dir here=continued
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
]
\end{forest}
\end{document}


Result:

It should not be too difficult, I think, to apply these modifications to my experimental code allowing multiple splits, in the same way that the above modifies the code I wrote to stylise Sašo'd single-split strategy.

For example (later edit) here's a modified version of that experimental proof-of-concept code for multi-splits (note that the splits don't make any particular sense, so don't necessarily involve a page break):

% ateb: https://tex.stackexchange.com/a/339790/ addaswyd o gwestiwn DiB: https://tex.stackexchange.com/q/339669/
\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage[edges]{forest}
\newcolumntype{C}[1]{@{}>{\centering\arraybackslash}m{#1}@{}}
\forestset{%
dir tree/.style={%
for tree={%
folder,
grow'=0,
if level=0{align=center}
{
align={C{50mm}},
},
font=\sffamily\bfseries\footnotesize,
inner xsep=7pt,
edge={ultra thick, rounded corners=2pt},
fill=white,
rounded corners=2pt,
},
},
}
% addaswyd o gôd Sašo Živanović: https://tex.stackexchange.com/a/296771/
\def\hiddenparcommand{\par}
\newcommand\otherhiddenparcommand{\par\noindent}
\newcommand\hiddencommacommand{, }
\forestset{%
declare keylist register={split here ids},
declare keylist register={split here interjects},
declare toks register=split here toks,
split here ids={},
split here interjects={},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
hide commas/.style={%
split here toks+={\hiddencommacommand},
split here toks+={#1},
},
split dir here/.style={%
split dir tree pre,
delay={%
!next.split dir tree post,
},
before drawing tree={%
split here ids+/.option=id,
!next.split resume here ids+/.option=id,
},
split={#1}{,}{split here toks,hide commas},
split here interjects/.register=split here toks,
split dir tree
},
split dir tree pre/.style={%
label={[text=gray, anchor=north, font=\scriptsize]below:{[cont.]}{}},
},
split dir tree post/.style={%
label={[font=\scriptsize, anchor=south, text=gray]above:{[cont.]}{}},
},
split dir tree/.code={%
\forestset{%
draw tree stage/.style={
for root'={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
for tree={%
to widest,
},
},
tempcountb'=-1,
do until={%
strequal((split_here_ids),"")
}{%
tempkeylistb'={},
tempkeylista'={},
split register={split here ids}{,}{tempcounta,tempkeylistb+},
split register={split here interjects}{,}{temptoksa,tempkeylista+},
split here ids'/.register=tempkeylistb,
split here interjects'/.register=tempkeylista,
% Sašo Živanović: http://chat.stackexchange.com/transcript/message/28484520#28484520
for nodewalk/.wrap 2 pgfmath args={%
{%
draw tree processing order/.style={%
filter={tree}{(id()<=########1)&&(id()>########2)}%
}%
}{}%
}{tempcounta}{tempcountb},
for root'={draw tree},
TeX/.wrap pgfmath arg={\otherhiddenparcommand ########1\hiddenparcommand}{temptoksa},
tempcountb'/.register=tempcounta,
},
for nodewalk/.wrap pgfmath arg={%
{%
draw tree processing order/.style={%
filter={tree}{id()>=####1}%
}%
}{}%
}{(tempcountb)+1},
for root'={draw tree},
},
}
},
}
\begin{document}
\begin{forest}
dir tree,
[TypeData
[ModelData
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig, split dir here=continued
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
[StationData
[StationConfig
]
[StationConfig, split dir here=more to come
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig, split dir here=last part coming up
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
]
\end{forest}
\end{document}


I think you might be able to then automate it. It would be easier if trees can always start on new pages so you could just compare y for each node with the height of the text block. So if that's an option, I guess I would suggest looking into that. Otherwise, you could maybe search for ways of figuring out how much of the text block remains. But that would be a non-Forest issue, it seems to me.

I don't think, however, that there is likely to be a neat way of restoring the lost edges, although you could possibly simulate them. To restore them as edges, you'd need to interfere with the structure of the tree, I think. To simulate them, you could maybe just save the x value of the parent and then create a coordinate with TikZ and draw from there after the split.

I would suggest tackling this step-by-step if you still want to do this. If you need the edges restored (which I'm unclear about), maybe do that first and then try to modify the multi-split case, if you need multiple splits. Only then would I think about trying to automate it and I would focus on the new-page-per-tree scenario in the first instance, even if you hope to avoid that eventually (which I'm not clear about either). All of that (less the no-new-page bit, if applicable) should, I think, be doable in Forest, although obviously that is no more than a somewhat educated guess. (Sašo would be able to say for sure and would likely have a better idea anyway.)

# EIDT

This splits the tree automatically and tries to restore the missing edges. Note that it precludes using different edge options for different nodes with restored edges and ignores any specified edge label.

Updated for Forest 2017/02/02 v2.1.4

% ateb: https://tex.stackexchange.com/a/326884/ i gwestiwn Amir: https://tex.stackexchange.com/q/326875/
% agenir y fersiwn hwn Forest 2017/02/02 v2.1.4
\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage[edges]{forest}
\newcolumntype{C}[1]{@{}>{\centering\arraybackslash}m{#1}@{}}
\forestset{%
dir tree/.style={%
for tree={%
folder,
grow'=0,
if level=0{align=center}
{
align={C{50mm}},
},
font=\sffamily\bfseries\footnotesize,
inner xsep=7pt,
edge={ultra thick, rounded corners=2pt},
fill=white,
rounded corners=2pt,
},
},
}
% addaswyd o gôd Sašo Živanović: https://tex.stackexchange.com/a/296771/
\def\hiddenparcommand{\par}
\newcommand\otherhiddenparcommand{\par\noindent}
\newcommand\hiddencommacommand{, }
\forestset{%
declare keylist register={split here ids},
declare keylist register={split here interjects},
declare keylist={split here auto siblings}{},
declare toks register=split here toks,
declare dimen register=tmpdima,
tmpdima'=0pt,
split here ids={},
split here interjects={},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
hide commas/.style={%
split here toks+={\hiddencommacommand},
split here toks+={#1},
},
split dir tree pre/.style={%
label={[text=gray, anchor=north, font=\scriptsize]below:{[cont.]}{}},
},
split dir tree post/.style={%
label={[font=\scriptsize, anchor=south, text=gray]above:{[cont.]}{}},
},
split dir tree auto post/.style={%
split dir tree post,
tempkeylistc'={},
for following siblings={%
tempkeylistc/.option=name,
},
split here auto siblings/.register=tempkeylistc,
label/.option=split here auto siblings,
typeset node,
tikz+/.process={%
OOw2{edge}{id}%
{%
\path [##1] (!u.parent anchor |- .north) ++(\forestregister{folder indent},1ex) coordinate (before ##2) |- (.child anchor);
\edef\tempa{\foresteoption{split here auto siblings}}
\foreach \i in \tempa \path [##1] (before ##2) |- ({forest cs:\i.child anchor});
}%
},
},
split dir tree/.code={%
\forestset{%
draw tree stage/.style={
for root'={
tempdima/.min={%
>OOw2+d{x}{min x}{####1+####2}%
}{tree},
tempdimb/.max={%
>OOw2+d{x}{max x}{####1+####2}%
}{tree},
for tree={%
to widest,
},
},
tempcountb'=-1,
do until={%
strequal((split_here_ids),"")
}{%
tempkeylistb'={},
tempkeylista'={},
split register={split here ids}{,}{tempcounta,tempkeylistb+},
split register={split here interjects}{,}{temptoksa,tempkeylista+},
split here ids'/.register=tempkeylistb,
split here interjects'/.register=tempkeylista,
% Sašo Živanović: http://chat.stackexchange.com/transcript/message/28484520#28484520
for nodewalk={%
draw tree processing order/.style={%
filter={tree}{> ORw+n< OR> & {id}{tempcounta}{########1+1}{id}{tempcountb}}%
}%
}{},
for root'={draw tree},
TeX/.process={Rw{temptoksa}{\otherhiddenparcommand ####1\hiddenparcommand}},
tempcountb'/.register=tempcounta,
},
for nodewalk={%
draw tree processing order/.style={%
filter={tree}{>OR>{id}{tempcountb}}%
}%
}{},
for root'={draw tree},
},
}%
},
split dir here auto/.style={%
split dir tree pre,
!next node.split dir tree auto post,
split here ids+/.option=id,
!next node.split resume here ids+/.option=id,
split={#1}{,}{split here toks,hide commas},
split here interjects/.register=split here toks,
},
split dir tree auto/.style={%
split dir tree,
before drawing tree={%
tempdima/.max={y}{tree},
tempdimc/.register=tempdima,
tempdimd/.min={y}{tree},
tempdima-/.register=tempdimd,
tempdimb'=\textheight,
tmpdima'=10ex,
while={%
>RR>{tempdima}{tempdimb}%
}{%
for nodewalk={%
root',
until={%
> ROw2+d Rw+d > {tempdimc}{y}{##1-##2} {tmpdima}{\textheight-##1}%
}{next node},
previous node,
split dir here auto=continued,
next node,
tempdima/.option=y,
tempdimc/.register=tempdima,
tempdima-/.register=tempdimd,
tmpdima'=15ex
}{},
},
},
},
}
\begin{document}
\begin{forest}
dir tree,
split dir tree auto,
[TypeData
[ModelData
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
[StationData
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
[StationConfig
]
]
]
\end{forest}
\end{document}


• My take-away is that the easiest solution might be to simply count the number of nodes (as there is one per "row") and manually break the pages. Since restoring lost edges is a problem and this data could be very large, it may be beneficial to break and create a new forest for each page.
– DiB
Nov 18 '16 at 13:55
• If that's an option, that would probably be easiest. I think you could fake edges if the structure is sufficiently simple e.g. you only need to fake them for the edges drawn from the single absent node. And you could probably break automatically if every tree starts a new page. But it would surely be easier if you can produce separate trees. The only real reason not to do that is if you need horizontal alignment.
– cfr
Nov 18 '16 at 16:14
• @DiB Please see edit. This does restore the missing edges, subject to certain presumptions (e.g. that the edge option is the same for all affected nodes and edge label isn't required).
– cfr
Nov 19 '16 at 13:38
• That is a very nice solution. The assumptions are correct for my use case. Blending this solution with some automation will help when the trees get unreasonably large as well. This solution is very much appreciated!
– DiB
Nov 19 '16 at 13:46
• The code from last edit does not work, at least on my environment. I get lots of errors like: Illegal parameter number in definition of \pgfkeys@temp. \end{forest} or Package PGF Math Error: Unknown operator ##' or ##2' (in '(id()<=)&&(id()>##2)'). \end{forest} Mar 3 '17 at 17:26