有人知道怎样用 TeX 构建这样的树吗?
答案1
下次您提问时,请记住先自己努力一下,然后发布您已完成的工作,即使工作量不大。至少,您应该提供文档的结构 ( \documentclass ... \end{document}
) 和树节点的内容,这样人们就不必从头开始来帮助您。我恰好对了解更多信息感兴趣,forest
否则我不会尝试回答您的问题。如果您不依赖像我这样的拖延者的古怪兴趣,您更有可能得到答案!
构建树
步骤 1:基本树
第一步是使用forest
的符号来设置基本树:
\documentclass[tikz]{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
[Language workbench
[Notation
[Textual
[Symbols]
]
[Graphical]
[Tabular]
]
[Semantics
[Translational
[Model to text]
[Model to model
[Concrete syntax]
]
]
[Interpretative]
]
[Editor
[Editing mode
[Free form]
[Projectional]
]
[Syntactic services
[Highlighting]
[Outline]
[Folding]
[Syntactic completion]
[Diff]
[Auto formatting]
]
[Semantic services
[Reference resolution]
[Semantic completion]
[Redo factoring]
[Error marking]
[Quick fixes]
[Origin tracking]
[Live translation]
]
]
[Validation
[Structural]
[Semantic
[Naming]
[Types]
[Programmatic]
]
]
[Testing, optional
[DSL testing]
[DSL debugging]
[More DSL debugging]
]
[Composition
[Syntax/views]
[Validation]
[Semantics]
[Editor services]
]
]
\end{forest}
\end{document}
这将产生以下内容:
第 2 步:修复层级对齐
正如 Kevin C 对您的问题的评论中所指出的,我们可以使用以下方法使“编辑器”节点与“具体语法”节点对齐tier=
:
...
[Concrete syntax, tier=edits]
]
]
[Interpretative]
]
[Editor, tier=edits
...
这样可以正确对齐节点,但是存在一个问题:
这是因为tier
对齐将“编辑器”节点移得离其通常的位置“太远”,并且forest
无法跟踪正在发生的事情。(我相信有更技术复杂的解释,但这就是我的想法。)
我们可以forest
通过使用phantom
键来创建一些不可见的节点来提供帮助。这些节点不会被绘制,但它们有助于留出空间。我们将基于“验证子树”创建一个幻影子树,并将其插入到“编辑器”子树之前:
...
[Semantics
[Translational
[Model to text]
[Model to model
[Concrete syntax, tier=edits]
]
]
[Interpretative]
]
[Validation, phantom
[Structural, phantom]
[Semantic, phantom
[Naming, phantom]
[Types, phantom]
[Programmatic, phantom]
]
]
[Editor, tier=edits
...
这样更好,因为我们现在有空间容纳所有东西了:
步骤3:节点外观
现在该考虑一下节点的外观了。我们希望在每个节点周围画一个框,并且使用无衬线字体,因此我们在环境的开头添加了以下内容forest
:
for tree={
font=\sffamily,
node options={draw=black!25},
}
这给了我们:
但是,我们希望节点的高度相同,即使特定节点中没有上升部和下降部,也能容纳它们。也就是说,我们希望同一层上的盒子都具有相同的高度。
我们增加
anchor=base,
align=center,
base=b,
通过树选项来实现这一点。
步骤 4:节点定位和锚点
然而事情还不太对劲:
直观来看,节点之间没有足够的空间,因此事物会相互交叉。此外,通向子节点的线不是从一个点辐射出去的,而且考虑到它们的宽度,这些层似乎太靠近了。
我们可以添加
parent anchor=south,
child anchor=north,
使线条从单个点辐射出去。这确保了从父节点到子节点的线条始终离开south
父节点的锚点并进入north
子节点的锚点。
为了增加层之间的距离,我们添加
l sep+=20pt,
至此我们的树看起来好一些了:
步骤 5:加粗线条
为了加粗节点周围的线条,我们可以添加
line width=1pt,
但为了加粗父母与孩子之间的界线,我们需要调整edge path
:
edge path={\noexpand\path[line width=1pt, \forestoption{edge}](!u.parent anchor)--(.child anchor)\forestoption{edge label};},
我们现在有:
步骤 6:设置节点样式
我们需要为节点设置 4 种不同的样式:我们已经实现的普通默认样式;北锚点处的“强制”实心圆;北锚点处的“可选”白色中心绘制圆;南锚点处的实心角。实心角可以与强制/可选样式共存,因此我们要确保附加样式选项而不是覆盖它们。
for tree
因此,我们在环境开始时添加以下样式定义forest
:
mandatory/.append style={edge label={node [circle, fill=black!80] {}}},
optional/.append style={edge label={node [circle, draw=black!80, fill=white] {}}},
or/.append style={for first={disjunct}},
disjunct/.append style={
tikz={\begin{scope}\clip (!u.south) -- (!u1.north) -- (!ul.north) -- cycle; \node [circle, fill=black!80, minimum width=15pt] at (!u.south) {};\end{scope}}
},
需要将styleor
选项赋予相关节点的孩子们而不是作为选项传递给节点本身。(也许可以避免这种情况,但我不知道该怎么做。)optional
和mandatory
样式传递给相关节点本身。例如,这是“语义”子树:
...
[Semantics, mandatory, or
[Translational, or
[Model to text]
[Model to model
[Concrete syntax, optional, tier=edits]
]
]
[Interpretative, or]
]
...
其结果为:
步骤 7:添加图例
剩下的就是添加“图例”。为此,我们将使用两个tikz
库,positioning
和calc
。我们还将定义一个legend
样式以方便使用:
\tikzset{
legend/.append style={line width=1pt, font=\sffamily, align=left},
}
先前的设置仅适用于树所以我们在定义中重复它们。
我们现在命名其中一个节点,以便我们可以将图例框相对于该节点定位:
...
[Syntax/views, name=my node]
...
现在我们可以创建节点来保存图例文本和符号:
\node (legend title) [below=of my node, legend] {Legend:};
\node (legend mandatory) [below=5pt of legend title, legend, circle, fill=black!80] {} ;
\node (legend mandatory text) [right=5pt of legend mandatory.east, legend] {Mandatory};
\node (legend optional) [below=10pt of legend mandatory, legend, circle, draw=black!80, fill=white] {};
\node (legend optional text) [right=5pt of legend optional.east, legend] {Optional};
\node (legend or) [below=5pt of legend optional, legend] {};
\node (legend or text) [right=5pt of legend or.east, legend, yshift=-5pt, xshift=1pt] {Or};
\begin{scope}
\draw [draw, line width=1pt] ($(legend or) + (5pt,-10pt)$) coordinate (A) -- (legend or.center) coordinate (B) -- ($(legend or) - (5pt,10pt)$) coordinate (C);
\clip (A) -- (B) -- (C) -- cycle;
\node [circle, fill=black!80, minimum width=15pt] at (B) {};
\end{scope}
最后,我们在图例周围画出方框:
\draw [line width=1pt] (legend title.north west) -- (legend title.north west -| legend mandatory text.north east) -- (legend mandatory text.north east |- legend or text.south east) -- (legend or text.south east -| legend title.north west) -- cycle;
我们完成了!
最终代码
\documentclass[tikz]{standalone}
\usepackage{forest}
\usetikzlibrary{positioning, calc}
\begin{document}
}
\begin{forest}
for tree={
mandatory/.append style={edge label={node [circle, fill=black!80] {}}},
optional/.append style={edge label={node [circle, draw=black!80, fill=white] {}}},
or/.append style={for first={disjunct}},
disjunct/.append style={
tikz={\begin{scope}\clip (!u.south) -- (!u1.north) -- (!ul.north) -- cycle; \node [circle, fill=black!80, minimum width=15pt] at (!u.south) {};\end{scope}}
},
font=\sffamily,
parent anchor=south,
child anchor=north,
node options={draw=black!25},
edge path={\noexpand\path[line width=1pt, \forestoption{edge}](!u.parent anchor)--(.child anchor)\forestoption{edge label};},
anchor=base,
align=center,
base=b,
l sep+=20pt,
line width=1pt,
}
[Language workbench,
[Notation, mandatory, or
[Textual
[Symbols, optional]
]
[Graphical]
[Tabular]
]
[Semantics, mandatory, or
[Translational, or
[Model to text]
[Model to model
[Concrete syntax, optional, tier=edits]
]
]
[Interpretative, or]
]
[Validation, optional, phantom
[Structural, phantom]
[Semantic, phantom
[Naming, phantom]
[Types, phantom]
[Programmatic, phantom]
]
]
[Editor, mandatory, tier=edits
[Editing mode, mandatory, or
[Free form]
[Projectional]
]
[Syntactic services, optional, or
[Highlighting]
[Outline]
[Folding]
[Syntactic completion]
[Diff]
[Auto formatting]
]
[Semantic services, optional, or
[Reference resolution]
[Semantic completion]
[Redo factoring]
[Error marking]
[Quick fixes]
[Origin tracking]
[Live translation]
]
]
[,phantom
[,phantom
[,phantom]
]
]
[Validation, optional, or
[Structural]
[Semantic, or
[Naming]
[Types]
[Programmatic]
]
]
[Testing, optional, or
[DSL testing]
[DSL debugging]
[More DSL debugging]
]
[Composition, optional, or
[Syntax/views, name=my node]
[Validation]
[Semantics]
[Editor services]
]
]
\tikzset{
legend/.append style={line width=1pt, font=\sffamily, align=left},
}
\node (legend title) [below=of my node, legend] {Legend:};
\node (legend mandatory) [below=5pt of legend title, legend, circle, fill=black!80] {} ;
\node (legend mandatory text) [right=5pt of legend mandatory.east, legend] {Mandatory};
\node (legend optional) [below=10pt of legend mandatory, legend, circle, draw=black!80, fill=white] {};
\node (legend optional text) [right=5pt of legend optional.east, legend] {Optional};
\node (legend or) [below=5pt of legend optional, legend] {};
\node (legend or text) [right=5pt of legend or.east, legend, yshift=-5pt, xshift=1pt] {Or};
\begin{scope}
\draw [draw, line width=1pt] ($(legend or) + (5pt,-10pt)$) coordinate (A) -- (legend or.center) coordinate (B) -- ($(legend or) - (5pt,10pt)$) coordinate (C);
\clip (A) -- (B) -- (C) -- cycle;
\node [circle, fill=black!80, minimum width=15pt] at (B) {};
\end{scope}
\draw [line width=1pt] (legend title.north west) -- (legend title.north west -| legend mandatory text.north east) -- (legend mandatory text.north east |- legend or text.south east) -- (legend or text.south east -| legend title.north west) -- cycle;
\end{forest}
\end{document}