3

I would like to easily create tree-like structures like

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

D is the top root node. It starts a horizontal line to the right, which stands for the "same level". This horizontal line can have more letters A threaded at any position, which do not have additional semantic meaning. These letters A always have a line incoming from the left.

The more interesting feature is that a horizontal line can split downwards to a child (base) node R which always have a line incoming from the top. This split can happen at multiple positions on the horizontal baseline. The child nodes recursively start a new virtual block and hence are usually continued by an outgoing horizontal line to the right.- Hence, the R next to the D effectively starts the whole thing again in its own "bounding box" (here marked with +).

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

If for a node there are no more nodes connected to the right, then the horizontal outgoing line is not shown. And of course tree/text overlap needs to be avoided, i.e. some horizontal lines need to stretch far enough before starting a new child node. A type A node also should only appear after all previous child blocks have completed to the left of them.

What would be the best way to create such a tree-like drawing in LaTeX?

I've found \usetikzlibrary{trees} which goes in this direction, but I'm not sure how to flip the example, how to make multiple letters on a horizontal line, how to control text overlap, etc...

Any good suggestion how to recreate that diagram?

There is a working answer with manually positioning everything, but I hope there is another solution like tikz-tree which can calculate the positioning automatically for me, so that I only specify the child-node relations.

With arbitrary and longer labels:

D─┬───────────────────────G─┬─────C
  Cc─┬─────────Aa─┬──Aaaa   Eee─┐  
     Bbb─┐        Dd            A  
         Aaaaa 
8
  • I am not so adapt at using TikZ that I can provide you with an answer. I took a look at the manual (chapter 21) and at the examples about the options in the tree-library as shown in VisualTikZ. See from page 187 onwards in this link mirrors.evoluso.com/CTAN/info/visualtikz/VisualTikZ.pdf. Perhaps those examples provide you ideas how to proceed.
    – alchemist
    Jan 23, 2023 at 21:32
  • Could you explain the "tree" structure more? Is D the root with children C, A, A? But what is C on the right? Whose child is E? You could draw this manually with tikz of course, but for automation we need more information.
    – dexteritas
    Jan 24, 2023 at 8:19
  • It is unclear to me why the C (seemingly a child of D) is to the right of D, but the A (a child of E) would be to the right and below of E. What is the rule for this? Jan 24, 2023 at 9:52
  • @dexteritas I've added some explanation and I renamed the nodes to show the 2 types ("D" is just the top "R" node)
    – Gere
    Jan 24, 2023 at 10:05
  • 2
    This question is not so concise, but the asker was trying hard to describe the situation he faced. I voted to reopen the question.
    – Black Mild
    Jan 24, 2023 at 14:52

3 Answers 3

2

Variant with parameter for label

Adaptations

  • see original variant below for more description
  • \nodeD, \nodeR and \nodeA each have now one parameter to set the text.
  • Here I used relative positioning instead of absolute. Therefore, different text lengths are considered.
  • I set base node/.style={draw, dashed} just for clarification

Result

enter image description here

Code

\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}

Variant without parameters (original)

Description

I defined some commands that can be used within tikz:

  • \nodeD, \nodeR and \nodeA for the different node types
  • \branchend to stop a branch and go up one level.

I used counters treeRnode and treeAnode to name the nodes and variables \treeX and \treeY to save the current position. Futhermore, \treeLastY<y> saves the last node in layer <y>.

So the code for the given tree is in my syntax:

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

The indentation in the source code is just for readability. So, this would be the same:

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

Result

enter image description here

Code

\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}
9
  • Looks good! branchend goes up in the hierarchy? Could you please adapt it so that D/R/A labels are variable (math) expressions (i.e. text)? I still have only those 2 types, but I need to give a different displayed label to each node. I hope it still would work with avoiding overlap. It could be the solution :)
    – Gere
    Jan 24, 2023 at 16:44
  • Yes, \branchend goes up in the hierarchy. So you want arbitrary text in the node, instead of just these single letters? I didn't read this in the question. Then the code would have to be adjusted to accommodate differently sized nodes.
    – dexteritas
    Jan 24, 2023 at 16:49
  • Oh sorry for not being explicit enough. Yes, arbitrary labels. And the overlapping avoiding may become trickier. Is it solvable?
    – Gere
    Jan 24, 2023 at 16:50
  • Also the A-type nodes should not appear before all of the sub-tree is closed to the left of it (in the current version is fine!)
    – Gere
    Jan 24, 2023 at 16:52
  • I added a variant where you can set the label text. But I would have to make some more changes so it works also for longer texts. Maybe you could add an image to your question how it should look like, if there is a longer text (shoud there be a maximum text width and linebreaks?).
    – dexteritas
    Jan 24, 2023 at 16:57
5

TikZ is a multi-purpose drawing system with \draw and \node (a coordinate is a special node). Here is my suggestion. The code is self-explained (a bit care on + and ++).

enter image description here

\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}
2

Here's my approach with forest.

Rules:

  1. One empty root.

  2. The nodes on the same visual level are siblings.

  3. Should nodes connect to their previous sibling use connect left.

    If you have to customize all edges or want to place nodes along these edges more work might be necessary.

  4. Note: Some line segments are drawn multiple times when connect left connects to a sibling that has children on its own.

    In a proper PDF viewer this shouldn't be noticable, less so on paper. (Don't trust the picture I added to the answer.)

The gere tree style install a few things:

  1. The parent is a coordinate not a node (not really necessary but why bother).

    The edges to its children are hidden and its children have no l meaning there's no vertical space above the first visual row.

  2. The first children (except the one in the first row) get a previous sibling that gets hidden. This shifts the first child to the right without messing with the ls coordinate system.

  3. fit = rectangle implements your

    And of course tree/text overlap needs to be avoided, i.e. some horizontal lines need to stretch far enough before starting a new child node. A type A node also should only appear after all previous child blocks have completed to the left of them.

  4. The edge path' in for tree

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

    sets all edges to a (child) |- (parent) path (meaning up from the children and then orthogonally to the parent which will always be to the left).

    It is now setup in such a way that it connects the children roughly above the first (capital) letter. For hitting the true horizontal center of the first letter much more work is needed.

    If the line should hit the children centered across the whole width, simply use

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

Code

\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}

Output

enter image description here

6
  • This could be a very neat approach! I do need arbitrary labels for the nodes. I tried removing the LaTeX line but got an error. Before removing the line the diagram was actually wrong. What do you mean by providing style A-node?
    – Gere
    Jan 24, 2023 at 17:29
  • Also, that there are 2 lines going off from the D to the right which are off by one pixel. At least when I zoom in in my output. But maybe it's just my rendering.
    – Gere
    Jan 24, 2023 at 17:31
  • (when I tried make the 2nd A a longer text, weirdly the diagram turned out wrong)
    – Gere
    Jan 24, 2023 at 17:43
  • @Gere I've updated my answer to allow for arbitrary text. You need to specify connect left with the children that connect with their previous siblings and not their parent. The “2 lines” should only be an artefact of bad PDF rendering, if I zoom in the lines line up perfectly. Jan 24, 2023 at 18:14
  • Yes, it would be nicer if we don't need to overlap lines but it either needs a little bit manual work or someone with a better understanding of forest. (I have ideas but I don't know how to implement them.) Jan 24, 2023 at 18:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .