7

As I am not proficient with Tikz and despite my efforts I did not find a suitable package, I would like to ask for an advice or alternatively for a solution concerning the following subject.

The aim is to replicate the following list, which is somehow mixed with a tree diagram with a vertical propagation. I need only 2 branches, but I added a third branch to make it more clear. List design

Since some input entries are pretty long, it would be appreciable to have a flush left alignment. The dimensions shown in the picture can be set differently, unless they are realizable.

It is quite obvious that this design is not appropriate for numerous branches, as it has to fulfil the function of a tree diagram and a list simultaneously. So probably 3 branches may be the maximum for a readable and comprehensive structure with one single depth (in case this design will be used by others).

Here I add substantially a not minimal working example using itemize, but hopefully it can be somehow considered as a starting point.

\documentclass{article}

\newlength{\BrVert}
\setlength{\BrVert}{1.3\baselineskip}

\newlength{\ItemSep}
\setlength{\ItemSep}{0.5\baselineskip}

\usepackage{enumitem}
\setlist{nosep, label = $\longrightarrow$, leftmargin = 2cm,
        topsep = \BrVert, itemsep = \ItemSep}

\begin{document}
\underline{Main text}
\begin{itemize}
\item Rome
\item Paris
\item Madrid
\end{itemize}
\begin{itemize}
\item Mercedes
\item Renault
\item Volvo
\end{itemize} 
\begin{itemize}
\item apple
\item pear
\item orange
\end{itemize} 
\end{document}

There are no particular requirements for "how it can be done".

0

4 Answers 4

8

Edit See below for version which takes account of longer node texts.

A different forest solution which uses forest's argument processor and avoids the need to draw edges manually. The children of the root node don't have edges. Instead, we use a custom edge path for the tree's leaves which connects them to a point on their grandparent's (i.e. the root's) border. These paths are displaced by a factor which depends on their invisible parent's child number i.e. if their parent is the first, second or third child. You could use more or fewer branches. That is, the style allows it. Whether it would make good sense is a different question.

We define two forest styles.

\forestset{%

folders descending

folders descending imposes the required structure on the tree.

  folders descending/.style={%
    for tree={
      folder,
      grow'=0,
    },

This applies the folder style from the edges library (as in Sandy G's answer and sets the direction of growth. This is actually east as we aren't going to grow much. What we want is not that the tree grow down, but that its branches do. Telling the tree to grow across ensures the branches go down (or up, depending on whether we use the prime).

    for children={
      l sep'+=5pt,
      coordinate,
      no edge,
    },

We increase the distance of the root's children, zap their edges and make them into coordinates. These are our invisible nodes. They are only here as scaffolding to support the tree's structure.

    for leaves={
      edge path'/.process={ O+nO+nw2+Pw {!r.n children} {!u.n} {(##1-##2)*5}  {(!uu.south west) ++(##1 pt,0pt) |- (.child anchor) } },

This alters the way the edges of the leaves are drawn. Normally, an edge is drawn between (!u.parent anchor), which is the parent's parent anchor, and (.child anchor), which is the current node's child anchor. In this case, we use !uu rather than !u to draw from the grandparent rather than the parent. !uu tells forest to start from the parent of the parent of the current node, which is the root of our tree.

Moreover, we don't use the parent anchor for drawing the edge. Instead, we start from the south west anchor and move 0pt or more to the right before starting to draw. This is what ++(##1 pt,0pt) is doing. The trick here is that ##1 is a calculated dimension, which depends on the current node's relationship to its siblings.

O+nO+nw2+Pw {!r.n children} {!u.n} {(##1-##2)*5}  {(!uu.south west) ++(##1 pt,0pt) |- (.child anchor) }

looks complicated, but is actually a relatively simple instruction for achieving this.

We read O+nO+nw2+Pw from left to right. This is an instruction to forest's argument processor. Its job is to describe the arguments which follow and to tell forest what to do with them.

O+nO+n

O is a forest option. O+n means a forest option which should be treated as a numerical expression. So, O+nO+n tells us the first two arguments are forest options and should be treated as numerical expressions. What are the first two arguments?

{!r.n children} {!u.n}

The first of these retrieves the root's number of children. !r means the root of the tree and n children is the number of its children. In our tree, this will return 3. The second is the child number of the current node's parent, n. All children have a number relative to their siblings. Here, there are 3 children, so they will be numbered 1, 2 and 3.

w2+P

This tells forest that the next argument should be understood as a function of the preceding two (w2) and that it should then be parsed by pgfmath (+P).

{(##1-##2)*5}

So this means take the second argument from the first and multiply by five. That is, take the parent's child number from the root's number of children and multiply by five. This will yield (approximately) 10, 5 and 0.

Finally,

w

tells forest the last argument should be understood as a function of the result so far. So (approximately) 10, 5 and 0 will get plugged into

{(!uu.south west) ++(##1 pt,0pt) |- (.child anchor) }

and so the displacement from the root's southwestern corner will vary according to the branch in just the way we want it to. All that's left is to specify the arrow tip of our choice to the edge.

      edge+={-Stealth},
    },    
  },

lined root

lined root deals with underlining its root.

  lined root/.style={%
    inner xsep=0pt,
    outer xsep=0pt,
    inner ysep=1pt,
    tikz+={\draw (.south west) -- (.south east);},
  },
}

This is actually pure TikZ and could be specified in \tikzset rather than \forestset, if the same style was wanted in other kinds of tikzpictures.

Applying these styles yields the following output,

tree-list thing

Complete code

\documentclass[tikz,border=10pt]{standalone}
% ateb: https://tex.stackexchange.com/a/700537/
\usepackage[edges]{forest}
\usetikzlibrary{arrows.meta}
\forestset{%
  folders descending/.style={%
    for tree={
      folder,
      grow'=0,
    },
    for children={
      l sep'+=5pt,
      coordinate,
      no edge,
    },
    for leaves={
      edge path'/.process={ O+nO+nw2+Pw {!r.n children} {!u.n} {(##1-##2)*5}  {(!uu.south west) ++(##1 pt,0pt) |- (.child anchor) } },
      edge+={-Stealth},
    },    
  },
  lined root/.style={%
    inner xsep=0pt,
    outer xsep=0pt,
    inner ysep=1pt,
    tikz+={\draw (.south west) -- (.south east);},
  },
}
\begin{document}
\begin{forest}
  lined root,
  folders descending,
  [Main text
    [
      [Rome]
      [Paris]
      [Madrid]
    ]
    [
      [Mercedes]
      [Renault]east
      [Volvo]
    ]
    [
      [apple]
      [pear]
      [orange]
    ] 
  ]
\end{forest}

\end{document}

Longer node texts

The solution above is problematic, given the details of the question. In particular, it will produce poor results if the text content of nodes exceeds the remaining width of the text block.

Nodes can easily be made multi-line and can easily be told to automatically break lines to fit a particular width. However, in this case, the arrows will still point to the west anchor of the child. To my eyes, this looks rather strange.

strange looking places for arrows to end

The longer the content of the nodes is, of course, the more pronounced the oddness.

There's no easy ready-to-use anchor truly suited to the purpose here. What we'd like is an anchor which is centre left of the first line of text. (At least, that's what I'd like here.) Moreover, it would be good if we could get forest to adapt the width of the leaf nodes appropriately.

The following solution is not ideal. In particular, I'm not certain why I need to adjust the width by as much as 4pt. I expect 2.5pt or, perhaps, 2.5pt plus the line width, or, even, twice the line width, but 4pt seems altogether over the top.

result with auto-adjustments

This is trying to get l without packing the whole tree, which may be part of the problem. Packing and repacking the whole thing might help, but it would also slow things down, especially if you use many or complex trees of this kind.

Testing with drawn nodes and showframe:

result with drawn boxes for testing

I'd recommend using folders descending where width isn't an issue and wide folders descending only where necessary, in part, because the latter involves more skulduggery and, in part, because it will be slower due to re-packing and re-typesetting nodes.

\documentclass[a4paper]{article}
% ateb: https://tex.stackexchange.com/a/700537/
%\usepackage[showframe]{geometry}
\usepackage{geometry}
\usepackage[edges]{forest}
\usetikzlibrary{arrows.meta}
\forestset{%
  common folders descending/.style={%
    for tree={
      folder,
      grow'=0,
    },
    for children={
      l sep'+=7.5pt,
      coordinate,
      no edge,
    },
    for leaves={
      edge+={-Stealth,shorten >=2.5pt},
    },    
  },
  folders descending/.style={%
    common folders descending,
    for leaves={
      edge path'/.process={ O+nO+nw2+Pw {!r.n children} {!u.n} {(##1-##2)*5}  {(!uu.south west) ++(##1 pt,0pt) |- (.child anchor) } },
    },    
  },
  wide folders descending/.style={%
    common folders descending,
    for leaves={%
      child anchor=north west,
      edge path'/.process={ O+nO+nw2+Pw {!r.n children} {!u.n} {(##1-##2)*5}  {(!uu.south west) ++(##1 pt,0pt) |- ([yshift=-0.5\baselineskip].child anchor) } },
    },
    before packing={%
      for first leaf={%
        !u.pack',
        tempdima'=\textwidth,
        tempdima'-/.option=l,
        tempdima'-=4pt,
      },
      for leaves={%
        inner sep=0pt,
        text width/.register=tempdima,
        content+=\strut,
        typeset node,
      },
    },
  },
  lined root/.style={%
    inner xsep=0pt,
    outer xsep=0pt,
    inner ysep=1pt,
    tikz+={\draw (.south west) -- (.south east);},
  },
}
\begin{document}
\noindent
\begin{forest}
  lined root,
  wide folders descending,
  %for tree={draw},% used for the test
  [Main text
    [
      [Rome]
      [Paris]
      [Madrid]
    ]
    [
      [Mercedes]
      [Renault]
      [Volvo]
    ]
    [
      [apple]
      [pear]
      [orange]
    ] 
    [
      [% Translation at http://welshnurseryrhymes.wales/Gartref?cerdd=15
        {Oes gafr eto? Oes heb ei godro?  Gafr wen, wen, wen, ie finwen, finwen, finwen, \dots{} wen-wen-wen.}]
      [{Oes gafr eto? Oes heb ei godro?  Gafr wyrdd, wyrdd, wyrdd, ie finwyrdd, finwyrdd, finwyrdd, \dots{} wyrdd-wyrdd-wyrdd.}]
    ]
  ]
\end{forest}

\end{document}
7

Here is a possibility using forest and the built-in folder style.

enter image description here

\documentclass{article}

\usepackage[edges]{forest}
\forestset{root/.style={name=main, inner xsep=0pt, inner ysep=2pt,
    before drawing tree={x-=10*n_children, x+=22.5},
    for children={coordinate, no edge, before packing={l=-10*n}}}}
    
\newcount\nch

\begin{document}

\begin{forest}
TeX={\nch=0},
for tree={folder, folder indent=0pt, grow'=0, edge={->},
    if level=2{tier=yes}{}, 
    if level=1{TeX={\advance\nch1}}{}
}
[Main text, root
    [[Rome][Paris][Madrid]]
    [[Mercedes][Renault][Volvo]]
    [[apple][pear][orange]]
    [[red][green][blue][yellow]]
]
{\foreach \k in {1,...,\nch}{\draw(!\k)|-(main.south east);}}
\end{forest}

\end{document}
9
  • What does x refer to in the case of the root node? The manual's general description says it is relative to the formal root. I thought the formal root here would be the root as nothing has been done to change that. But I find the explanation of x in the case of the (formal or otherwise) root node a bit confusing.
    – cfr
    Commented Nov 6, 2023 at 1:33
  • @cfr: From the manual, referring to x and y: "the coordinates of the node in the 'normal' (paper) coordinate system, relative to the root of the tree that is being drawn. So, essentially, they are absolute coordinates." This is confusing. In fact, x and y are absolute, including the root node. To see this, make a simple forest with one line: [A]\draw(0,0)circle[radius=2pt];. Then add before drawing tree={x-=5mm}. You can see that the root node's position changes relative to (0,0). Maybe "relative to the root of the tree that is being drawn" should be deleted from the manual.
    – Sandy G
    Commented Nov 6, 2023 at 2:16
  • I realised it didn't work the way I thought by playing with your answer. I think I've previously just assumed the manual was right. I don't think it would work to just delete the phrase because sometimes x and y are relative for other nodes. Try \begin{forest} [a, before computing xy={x'+=50pt} [b, before drawing tree={x'+=50pt}] ] \draw (0,0) circle (2pt); \end{forest}. In this case, b is moved 100pt and not 50pt i.e. 50pt relative to a. Whereas if before drawing tree is used in both cases, it is moved 50pt. So it is relative, but it's using an earlier reference?
    – cfr
    Commented Nov 6, 2023 at 2:29
  • @cfr: I've never changed x before the compute xy stage. I'm not sure that is intended usage. From 3.7.2 in the manual: "The values of x and y are computed in stage compute xy. It only makes sense to inspect and change them (for manual adjustments) afterwards (normally, in the before drawing tree hook, see §3.4.1.)"
    – Sandy G
    Commented Nov 6, 2023 at 2:37
  • I got the idea from page 44 of the manual, where it says it makes no sense to change it before that except for the root node, since that isn't altered during that stage. So I don't think it is unintended usage. Right after the bit you quote, it says x and y of the (formal) root node are exceptions, as they are not changed in stage compute xy.
    – cfr
    Commented Nov 6, 2023 at 2:40
4

It's possible to use a list to do this, but I think that using a tabular environment makes it easier. The following solution is not fully automatic, but it is easily adaptable, and I think allows you to construct new examples quite easily.

\documentclass{article}
\usepackage{enumitem}
\usepackage{calc}
\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{array,tabularx}
\newcounter{titem}
\newlength{\alistoffset}
\setlength{\alistoffset}{4pt}
\newlength{\alistwidth}
\newlength{\alistinitial}
\newlength{\alistheight}
\setlength{\alistheight}{7pt}
\setlength{\alistinitial}{\alistheight+\extrarowheight}
\newcolumntype{A}{@{\hspace{\alistwidth}\stepcounter{titem}\mkitem{\thetitem}}p{2in}}
% alternative if using tabularx
\newcolumntype{B}{@{\hspace{\alistwidth}\stepcounter{titem}\mkitem{\thetitem}}X}

\NewDocumentCommand{\mkitem}{m}{
{\tikz[remember picture]{\node[anchor=center,inner sep=0pt, outer sep=0pt,minimum height=1ex] (#1) {};}}}
\NewDocumentCommand{\alistpreamble}{m}{
\setlength{\alistwidth}{\widthof{#1}}
\mkitem{begin}#1\mkitem{end}
\vspace*{\alistheight}\par
}
\begin{document}
\alistpreamble{This is some text.}
% adjust item separation with \extrarowheight
%\setlength{\extrarowheight}{6pt}
\begin{tabularx}{\textwidth}[t]{A}
Rome is the capital city of the Italian republic.\\
Paris is the capital city of the French republic.\\
Madrid\\
Mercedes\\
Renault\\
Volvo\\
apple\\
pear\\
orange
\end{tabularx}
\tikz[remember picture,overlay,]{
\draw ($(begin)+(0,-\alistinitial)$) -- ($(end)+(-6pt,-\alistinitial)$);
\foreach \x in {1,...,3} {\draw[->] ($(begin)+(.5\alistwidth,-\alistheight)$) |- ($(\x)+(-\alistoffset,0)$);}
\foreach \x in {4,...,6} {\draw[->] ($(begin)+(.25\alistwidth,-\alistheight)$) |-  ($(\x)+(-\alistoffset,0)$);}
\foreach \x in {7,...,9} {\draw[->] ($(begin)+(0\alistwidth,-\alistheight)$) |-  ($(\x)+(-\alistoffset,0)$);}}

\end{document}

output of code

2
  • Can you make one of those entries even longer, so that it goes to a second line? Tho question seems to imply that that might happen. Commented Nov 5, 2023 at 20:32
  • @barbarabeeton Yes if you use the other column type that sets a p column instead of an X column you can see the wrapping in action.
    – Alan Munn
    Commented Nov 5, 2023 at 21:28
3

Update

There was some spurious code present which has been deleted. Also, someone pointed out a useful possibility of treating the first item in a group specially. Done.

I'm a bit late to the party. I've been fooling around with TikZ chains recently, so this occurred to me as a possibility. Long node texts don't pose a problem.

\documentclass{article}

\usepackage[papersize={5.5in,8.5in},margin=0.6in]{geometry}
\usepackage{tikz}

\usetikzlibrary{positioning,chains}

\newcounter{gennum}
\newcounter{itemnum}
\newcounter{ingennum}
\newtoks\gentox

%% |=====8><-----| %%

\NewDocumentCommand{\levels}{s >{\SplitList{;}}m }{% splits the list
    \ProcessList{#2}{\fooaux}%
}
\NewDocumentCommand{\fooaux}{m} {% manipulates the entries
    \stepcounter{itemnum}%
    \stepcounter{ingennum}%
    \node[on chain,text width=3in,name=L\thegennum-\theitemnum]%change <text width> to suit
        {% content
            \ifnum\theingennum=1 \bfseries\color{red}\fi %% omit/alter to suit
            \strut#1\strut
        };% 
    \draw[<-] (L\thegennum-\theitemnum.west) --
        ++(-8pt*\thegennum,0)coordinate(C\thegennum-\theitemnum);% change <8pt> to suit
}

\NewDocumentEnvironment{mkgenlist}{m}{% #1=topic text
    \setcounter{itemnum}{0}%
    \setcounter{gennum}{0}%
    \begin{tikzpicture}[outer sep=0pt,start chain=going {below=of \tikzchainprevious.south west},
        every on chain/.style={anchor=north west},node distance=-3pt]% change node distance to suit
}{%
    \coordinate (cbbnw) at (current bounding box.north west);
    \the\gentox
    \node[above=0pt of cbbnw,anchor=south west,inner xsep=0pt,name=T] {#1};
    \draw (cbbnw) -- (T.south east);
    \end{tikzpicture}
}

\NewDocumentCommand{\gen}{m}{% a ;-separated list as argument
    \stepcounter{gennum}
    \setcounter{ingennum}{0}
    \levels{#1}
    \edef\foo{\gentox={\the\gentox \noexpand\draw (C\thegennum-\theitemnum) --
        (C\thegennum-\theitemnum |- cbbnw);}}\foo
}

\begin{document}

\thispagestyle{empty}

\begin{mkgenlist}{An assortment of words\dots}
\gen{Rome;
Paris;
Madrid}

\gen{Mercedes;
Renault;
Volvo}

\gen{apple;
pear;
orange;
This is a bunch of bla bla bla to see what is happening with long text.}

\gen{acorn;squash;alfalfa;sprouts;almond;bacon;bagel;baked~Alaska;fennel;fig;fillet}
\end{mkgenlist}

\end{document}

tree revised

A new version

In quite another application, some additional functionality proved useful. Note the use of keyval.sty to make customization easier.

\documentclass{article}
%% +first item of each group gets special treatment; keyval; first line; number items
\usepackage[papersize={5.5in,8.5in},margin=0.6in]{geometry}
\usepackage{tikz,keyval}

\usetikzlibrary{positioning,chains}

\newcounter{gennum}
\newcounter{itemnum}
\newcounter{ingennum}
\newlength{\arrowln}
\newlength{\gentextwd}
\newlength{\nodedist}
\newtoks\gentox
\newif\iffirstline % first line of group treat differently
\newif\ifnumline % the arrows carry the number of the item

\setlength{\arrowln}{8pt}%  length of arrow
\setlength{\gentextwd}{3in} % text width of item
\setlength{\nodedist}{0pt}% vertical distance between nodes

\makeatletter
\define@key{vtree}{firstline}[true]{\csname firstline#1\endcsname}
\define@key{vtree}{arrowln}{\setlength{\arrowln}{#1}}
\define@key{vtree}{gentextwd}{\setlength{\gentextwd}{#1}}
\define@key{vtree}{nodedist}{\setlength{\nodedist}{#1}}
\define@key{vtree}{numline}[true]{\csname numline#1\endcsname}
\makeatother

%% |=====8><-----| %%

\NewDocumentCommand{\levels}{s >{\SplitList{;}}m }{% splits the list
    \ProcessList{#2}{\fooaux}%
}
\NewDocumentCommand{\fooaux}{m} {% manipulates the entries
    \stepcounter{itemnum}%
    \stepcounter{ingennum}%
    \node[on chain,text width=\gentextwd,name=L\thegennum-\theitemnum]%change <text width> to suit
        {% content
            \iffirstline\ifnum\theingennum=1 \bfseries\color{red}\fi\fi %% omit/alter to suit
            \strut#1\strut
        };%
    \ifnumline
        \draw[<-] (L\thegennum-\theitemnum.west)
            --node[font=\tiny,fill=white,inner sep=1pt]{\theitemnum.\theingennum}
            ++(-\arrowln*\thegennum,0)coordinate(C\thegennum-\theitemnum);
    \else
        \draw[<-] (L\thegennum-\theitemnum.west)
            -- ++(-\arrowln*\thegennum,0)coordinate(C\thegennum-\theitemnum);
    \fi
}

\NewDocumentEnvironment{mkgenlist}{s O{} m}{% #1=topic text
    \setkeys{vtree}{#2}
    \setcounter{itemnum}{0}%
    \setcounter{gennum}{0}%
    \begin{tikzpicture}[outer sep=0pt,start chain=going {below=of \tikzchainprevious.south west},
        every on chain/.style={anchor=north west},node distance=\nodedist]%
}{%
    \coordinate (cbbnw) at (current bounding box.north west);
    \the\gentox
    \node[above=0pt of cbbnw,anchor=south west,inner xsep=0pt,name=T] {#3\kern3pt};
    \draw (cbbnw) -- (T.south east);
    \end{tikzpicture}
}

\NewDocumentCommand{\gen}{m}{% a ;-separated list as argument
    \stepcounter{gennum}
    \setcounter{ingennum}{0}
    \levels{#1}
    \edef\foo{\gentox={\the\gentox \noexpand\draw (C\thegennum-\theitemnum) --
        (C\thegennum-\theitemnum |- cbbnw);}}\foo
}

\begin{document}

\thispagestyle{empty}

\begin{mkgenlist}[arrowln=16pt,firstline,numline,gentextwd=2in,nodedist=-3pt]{An assortment of words\dots}
\gen{Rome;
Paris;
Madrid}

\gen{Mercedes;
Renault;
Volvo}

\gen{apple;
pear;
orange;
This is a bunch of bla bla bla to see what is happening with long text.}

\gen{acorn;squash;alfalfa;sprouts;almond;bacon;bagel;baked~Alaska;fennel;fig;fillet}
\end{mkgenlist}

\end{document}

new version with keyval options

You must log in to answer this question.

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