我的软件会自动生成一些文档。我添加到文档中的元素之一是下面描述的样式的树:
由于树可能很大,因此可能会跨越多个页面。我在这里看到有一种方法可以手动告诉树在哪里分页:
但我真正想要的是让树自动分页。有没有办法用 Tikz Forest 做到这一点?
森林代码示例如下:
\documentclass[english,10pt,oneside,table,xcdraw]{book}
\usepackage{lmodern}
\usepackage[lgr,T1]{fontenc}
\usepackage{geometry}
\usepackage[utf8]{inputenc}
\usepackage{forest}
\usepackage{longtable}
\usepackage{tikz}
\usetikzlibrary{calc,shapes,shapes.arrows,arrows,trees,shadows,backgrounds,positioning}
\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}
]
[{StationType},drop shadow
[{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}
答案1
除非问题是编译错误,否则请始终发布完整、有效的代码。说服您的代码进行编译只是一种猜测。因此,我可能以我甚至没有意识到的方式更改了您的代码。
以下是我对你的序言的猜测:
\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage{forest}
\usetikzlibrary{shadows}
\newcolumntype{C}[1]{@{}>{\centering\arraybackslash}m{#1}@{}}
显然,这可能与您的不一样,这在猜测时是可以预料到的。下面的答案基于这是您的序言的假设。
如果问题仅仅是自动化问题,那么我希望你的例子能够展示手动方法。由于你心存疑虑,我猜你知道情况并非如此,即使你不知道原因。
当然,这些都没什么用。这意味着本来可以花在回答你问题上的时间却花在了一堆其他事情上,比如试图弄清楚要加载哪些包以及为 定义C
。
好的。所以你链接的问题的答案中给出的代码只能用于分割向下生长的树。你的树向东生长。
此外,如果不响应控制台上的 TeX 提示或修改,至少那里的一些例子(例如 mind)无法用当前的 Forest 进行编译forest.sty
。
因此,您可能需要在命令行上编译它们,以便可以忽略提示。没有错误。TeX 只是\show
在向您输入一些信息,如果您要求进一步帮助,它会告诉您。因此,这里无需担心忽略错误。您只需让它向您显示它想要显示的内容即可。
以下代码执行以下操作:
- 它更新您的代码以使用 Forest 2 的
folder
风格,这使得绘制这种风格的树更容易; - 它
dir tree
为您的特定风格提供了一种风格; - 它提供了一种
split dir here=<text>
在当前节点分割树的样式,类似于链接split here=<text>
样式。
样式定义基于我对 Sašo 代码的改编,如链接问题的答案中所包含的那样,并在下面的评论中注明。
限制:
- 没有任何东西会自动分割;
- 如果做一些奇怪的事情,分裂将无法正常工作,其中“奇怪”的意思是“导致分裂无法正常工作的事情”;
- 即使你做的奇怪的事情不那么明显,分割也会丢失分割后绘制到当前节点排版的兄弟节点的所有边;
- 每棵树只支持一次分割(多分割版本请参见下文);
- 分割取决于节点的默认“线性顺序”,因此
next node
按线性顺序排列的节点应在分割后第一个被绘制,并且只有按该顺序排列的节点和后续节点才应在该分割后被绘制。
代码:
\documentclass[a4paper]{article}
\usepackage{geometry,array}
\usepackage[edges]{forest}
\usetikzlibrary{shadows}
\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,
drop shadow,
},
},
}
% 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}
结果:
我认为,应用这些修改应该不会太难我的实验代码允许多次分割,就像上面修改我编写的代码以风格化 Sašo 的单分割策略一样。
例如(后来编辑)这是针对多重分割的实验性概念验证代码的修改版本(请注意,分割没有任何特殊意义,因此不一定涉及分页符):
% 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}
\usetikzlibrary{shadows}
\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,
drop shadow,
},
},
}
% 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}
我觉得你可能然后能够自动化。如果树总是可以从新页面开始,那么就更容易了,这样你就可以比较y
每个节点与文本块的高度。所以如果这是一个选择,我想我会建议研究一下。否则,你也许可以寻找找出剩余文本块的方法。但在我看来,这将是一个非森林问题。
不过,我认为不太可能有一种巧妙的方法来恢复丢失的边缘,尽管你可以模拟它们。要将它们恢复为边缘,我认为你需要干扰树的结构。要模拟它们,你可能只需保存x
父节点的值,然后创建一个带有 Ti 的坐标钾Z 并在分裂后从那里绘制。
如果您仍想这样做,我建议您逐步解决这个问题。如果您需要恢复边缘(我不清楚),也许先这样做,然后尝试修改多分割情况(如果您需要多次分割)。只有这样我才会考虑尝试自动化它,并且我会首先关注每棵树一个新页面的情况,即使您希望最终避免这种情况(我也不清楚)。我认为所有这些(如果适用,则减去无新页面部分)都应该在 Forest 中可行,尽管这显然只是一个有根据的猜测。(Sašo 可以肯定地说,而且无论如何可能会有更好的主意。)
电离辐射检测
这会自动分割树并尝试恢复缺失的边。请注意,它禁止edge
对具有恢复边的不同节点使用不同的选项,并且会忽略任何指定的edge label
。
错误修复 2023-12-04现在,您可以绘制两棵这样的树,如果您愿意的话,而且在那么多边缘情况下您不应该遇到错误。
% 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}
\usetikzlibrary{shadows}
\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,
drop shadow,
},
},
}
% 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},
split here ids={},
declare keylist register={split here interjects}, the tree parts
split here interjects={},
declare keylist={split here auto siblings}{},
declare toks register=split here toks,
declare dimen register=tmpdima,
tmpdima'=0pt,
declare dimen register=tmpdimb,
tmpdimb'=0pt,
declare dimen register=tmpdimc,
tmpdimc'=0pt,
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'={},
tmpdimb/.option=y,
for nodewalk={
while={
> ORw2+d _+d < On=! & {y}{tmpdimb}{##2-##1} {\textheight-#1} {n'}{1}%
}{
next,
tempkeylistc/.option=name
}%
}{},
split here auto siblings/.register=tempkeylistc,
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,
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,
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 n args=2{
split dir tree pre,
!next node.split dir tree auto post=#2,
split 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,
tmpdimc'=\pagetotal,
while={
>RR>{tempdima}{tempdimb}
}{
for nodewalk={
root',
until={
> ROw2+d RRw2+d > {tempdimc}{y}{##1-##2} {tmpdima}{tmpdimc}{\textheight-##2-##1}
}{next node},
if nodewalk valid={previous node}{%
previous node,
split dir here auto/.process={R_w2{tmpdima}{continued}{{##2}{##1}}},
next node,
}{},
tempdima/.option=y,
tempdimc/.register=tempdima,
tempdima-/.register=tempdimd,
tmpdima'=15ex,
tmpdimc'=0pt
}{},
},
},
},
}
\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}