使用 LaTeX 创建此树结构

使用 LaTeX 创建此树结构

我想轻松创建类似树状结构

D─┬───────────┬───A
  R─┬───A─┬─A R─┐  
    R─┐   R     R  
      R 

D 是顶部根节点。它从右侧开始一条水平线,代表“同一级别”。这条水平线可以A在任何位置穿入更多字母,这些字母没有额外的语义含义。这些字母A总是有一条从左侧进入的线。

更有趣的特性是,水平线可以向下分裂为子(基)节点R,该节点始终有一条线从顶部进入。这种分裂可以在水平基线上的多个位置发生。子节点递归地开始一个新的虚拟块,因此通常由向右的一条水平线继续。- 因此,下一个节点R实际上在自己的“边界框”(此处标记为)D中重新开始整个过程​​。+

D─┬───────────────┬───A
  +++++++++++     R─┐
 +R─┬───A─┬─A+      R 
 +  R─┐   R  +     
 +    R      + 
  +++++++++++

如果某个节点的右侧没有其他节点连接,则不会显示水平输出线。当然,需要避免树/文本重叠,即在开始新的子节点之前,一些水平线需要延伸得足够远。类型A节点也应该只在其左侧所有先前的子块都完成后才出现。

在 LaTeX 中创建这种树状图的最佳方法是什么?

我已经找到了\usetikzlibrary{trees}朝这个方向发展的方法,但我不确定如何翻转示例,如何在水平线上制作多个字母,如何控制文本重叠等......

关于如何重新创建该图表,有什么好的建议吗?

有一个可行的答案,可以手动定位所有内容,但我希望有另一个解决方案,例如 tikz-tree,它可以自动为我计算定位,这样我只需要指定子节点关系。

使用任意更长的标签:

D─┬───────────────────────G─┬─────C
  Cc─┬─────────Aa─┬──Aaaa   Eee─┐  
     Bbb─┐        Dd            A  
         Aaaaa 

答案1

带标签参数的变体

改编

  • 请参阅下面的原始版本以获取更多描述
  • \nodeD\nodeR并且\nodeA每个现在都有一个参数来设置文本。
  • 这里我使用了相对定位,而不是绝对定位。因此,考虑了不同的文本长度。
  • base node/.style={draw, dashed}只是为了澄清

结果

在此处输入图片描述

代码

\documentclass[border=2mm]{standalone}

\usepackage{etoolbox}
\usepackage{tikz}
\usetikzlibrary{positioning}

\def\nodedistance{5mm}
\def\treeY{0}

\newcounter{treeRnode}
\setcounter{treeRnode}{0}
\newcounter{treeAnode}
\setcounter{treeAnode}{0}

\def\lastXnode{}

\newcommand{\nodeD}[1]{%
    \node[D] (D0) at (0,0) {#1};
    \csdef{treeLastY\treeY}{D0}
    \xdef\lastXnode{D0}
}

\newcommand{\nodeR}[1]{%
    \pgfmathtruncatemacro\lastY{\treeY}
    \pgfmathtruncatemacro\treeY{\treeY+1}
    \ifcsdef{treeLastY\treeY}{%
        \node[R, anchor=west] (R\arabic{treeRnode}) at ([xshift=\nodedistance] \csuse{treeLastY\treeY}.east-|\lastXnode.east) {#1};
    }{%
        \node[R, below right=of \csuse{treeLastY\lastY}] (R\arabic{treeRnode}) {#1};
    }
    \draw (\csuse{treeLastY\lastY}) -| (R\arabic{treeRnode});
    \csxdef{treeLastY\treeY}{R\arabic{treeRnode}}
    \xdef\lastXnode{R\arabic{treeRnode}}
    \stepcounter{treeRnode}
}

\newcommand{\nodeA}[1]{%
    \node[A, anchor=west] (A) at ([xshift=\nodedistance] \csuse{treeLastY\treeY}.east-|\lastXnode.east) (A\arabic{treeAnode}) {#1};
    \draw (\csuse{treeLastY\treeY}) -- (A\arabic{treeAnode});
    \csxdef{treeLastY\treeY}{A\arabic{treeAnode}}
    \xdef\lastXnode{A\arabic{treeAnode}}
    \stepcounter{treeAnode}
}

\newcommand{\branchend}{%
    \pgfmathtruncatemacro\treeY{\treeY-1}
}

\begin{document}

\begin{tikzpicture}[
    node distance=\nodedistance,
    base node/.style={draw, dashed},
    D/.style={base node},
    R/.style={base node},
    A/.style={base node},
]
    \nodeD{$D_1$ with longer text}
        \nodeR{$R_1$}
            \nodeR{$R_2$ with longer text}
                \nodeR{$R_3$}
                \branchend
            \branchend
        \nodeA{$A_1$}
            \nodeR{$R_4$}
            \branchend
        \nodeA{$A_2$ with longer text}
        \branchend
        %
        \nodeR{$R_5$}
            \nodeR{$R_6$}
            \branchend
        \branchend
    \nodeA{$A_3$}
\end{tikzpicture}

\end{document}

无参数的变体(原始)

描述

我定义了一些可以在 tikz 中使用的命令:

  • \nodeD以及不同的节点\nodeR类型\nodeA
  • \branchend停止一个分支并上升一级。

我使用计数器treeRnodetreeAnode来命名节点和变量\treeX\treeY保存当前位置。此外,\treeLastY<y>保存层中的最后一个节点<y>

因此,给定树的代码符合我的语法:

\nodeD
    \nodeR
        \nodeR
            \nodeR
            \branchend
        \branchend
    \nodeA
        \nodeR
        \branchend
    \nodeA
    \branchend
    %
    \nodeR
        \nodeR
        \branchend
    \branchend
\nodeA

源代码中的缩进只是为了便于阅读。因此,这将是相同的:

\nodeD\nodeR\nodeR\nodeR\branchend\branchend\nodeA\nodeR\branchend\nodeA\branchend\nodeR\nodeR\branchend\branchend\nodeA

结果

在此处输入图片描述

代码

\documentclass{article}

\usepackage{etoolbox}
\usepackage{tikz}

\begin{document}

\def\treeX{0}
\def\treeY{0}

\newcounter{treeRnode}
\setcounter{treeRnode}{0}
\newcounter{treeAnode}
\setcounter{treeAnode}{0}

\newcommand{\nodeD}{%
    \node[D] (D0) at (0,0) {D};
    \csdef{treeLastY\treeY}{D0}
}

\newcommand{\nodeR}{%
    \pgfmathtruncatemacro\treeX{\treeX+1}
    \pgfmathtruncatemacro\lastY{\treeY}
    \pgfmathtruncatemacro\treeY{\treeY+1}
    \node[R] (R\arabic{treeRnode}) at (\treeX, -\treeY) {R};
    \draw (\csuse{treeLastY\lastY}) -| (R\arabic{treeRnode});
    \csxdef{treeLastY\treeY}{R\arabic{treeRnode}}
    \stepcounter{treeRnode}
}

\newcommand{\nodeA}{%
    \pgfmathtruncatemacro\treeX{\treeX+1}
    \node[A] (A) (A\arabic{treeAnode}) at (\treeX, -\treeY) {A};
    \draw (\csuse{treeLastY\treeY}) -- (A\arabic{treeAnode});
    \csxdef{treeLastY\treeY}{A\arabic{treeAnode}}
    \stepcounter{treeAnode}
}

\newcommand{\branchend}{%
    \pgfmathtruncatemacro\treeY{\treeY-1}
}

\begin{tikzpicture}[
    base node/.style={},
    D/.style={base node},
    R/.style={base node},
    A/.style={base node},
]
    \nodeD
        \nodeR
            \nodeR
                \nodeR
                \branchend
            \branchend
        \nodeA
            \nodeR
            \branchend
        \nodeA
        \branchend
        %
        \nodeR
            \nodeR
            \branchend
        \branchend
    \nodeA
\end{tikzpicture}

\end{document}

答案2

TikZ 是一个多用途绘图系统,带有\draw\node(acoordinate是特殊的node)。这是我的建议。代码是不言自明的(稍微注意+++)。

在此处输入图片描述

\documentclass[tikz,border=5mm]{standalone}
\begin{document}
\begin{tikzpicture}
\path 
(0,0) node[left]  (D) {D}
(5,0) node[right] (A) {A}
(4,0)  coordinate (P1) 
++(0,-.5)    node (P2) {R}
++(.5,-.5)   node (P3) {R}
(.5,0) coordinate (T1)
++(0,-.5)    node (T2) {R}
++(.5,-.5)   node (T3) {R}
++(.5,-.5)   node (T4) {R}
(T2)
+(2.5,0)  node (A2) {A}
+(1.5,0)  node (A1) {A}
+(2,-.5)  node (A3) {R}
;
\draw[thick,magenta] (D)--(A) 
(P1)--(P2)-|(P3)
(T1)--(T2)-|(T3)-|(T4)
(T2)--(A1)--(A2)-|(A3)
;
\end{tikzpicture}
\end{document}

答案3

以下是我的方法forest

规则:

  1. 一個空的根。

  2. 同一视觉级别的节点是兄弟节点。

  3. 节点是否应与其先前的兄弟节点连接connect left

    如果您必须自定义所有边或者想要沿这些边放置节点,则可能需要做更多的工作。

  4. 笔记:connect left当连接到具有其自身子项的兄弟项时,某些线段会被绘制多次。

    在合适的 PDF 查看器中,这不应该被注意到,在纸上更是如此。(不要相信我添加到答案中的图片。)

gere tree风格安装了几件事:

  1. 父级是一个坐标而不是一个节点(不是真的必要,但何必呢)。

    其子元素的边缘被隐藏,并且其子元素没有任何l意义,因为第一个元素上方没有垂直空间视觉的排。

  2. 第一个子元素(第一行除外)会获得一个隐藏的上一个兄弟元素。这会将第一的 孩子向右移动,不要弄乱ls坐标系。

  3. fit = rectangle实现你的

    当然,需要避免树/文本重叠,即在开始新的子节点之前,一些水平线需要延伸得足够远。类型A节点也应该只在其左侧所有先前的子块完成后才出现。

  4. edge path'for tree

    edge path'={
      ([xshift={\pgfkeysvalueof{/pgf/inner xsep}+.4em}].north west) |- (!u)}
    

    将所有边设置为一条(child) |- (parent)路径(意味着从子节点向上,然后与父节点正交,父节点始终在左边)。

    现在设置的方式是将子元素大致连接到第一个(大写)字母上方。要达到第一个字母的真正水平中心,还需要做更多的工作。

    如果该线应该击中整个宽度的中心子元素,则只需使用

    edge path' = {() |- (!u)}
    

代码

\documentclass[tikz,convert]{standalone}
\usepackage{forest}
\forestset{
  connect left/.style={% don't connect to parent but to previous sibling
    tikz+={\draw()--(!p);}, no edge},
  gere tree/.style={
    shape=coordinate,               % parent hidden
    for tree={
      calign=first, fit=rectangle,
      edge path'={
        ([xshift={\pgfkeysvalueof{/pgf/inner xsep}+.4em}].north west)|-(!u)}},
    for children={
      no edge,                      % parent hidden
      before computing xy={l=+0pt}, % parent hidden
      where n children>={0}{prepend={[,phantom]}}{},% invisible sibling at first
    }
  }
}
\begin{document}
\begin{forest} gere tree
[
  [D
    [Cc
      [Bbb
        [Aaaaa]]]
    [Aa, connect left
      [Dd]]
    [Aaaa, connect left]
  ]
  [G, connect left
    [Eee
      [A]]
  ]
  [C, connect left]
]
\end{forest}
\end{document}

输出

在此处输入图片描述

相关内容