7

In multiline mode (align=center) in forest, there is extra vertical space around the node. The first tree below is not in multiline mode. The second is in multiline mode, and there is extra space above and below the node text, even with only one line. How can I make the second tree look like the first? (Many of the nodes in my actual tree do have multiple lines, which is why I am using multiline.)

\documentclass{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
[A, for tree={parent anchor=south, child anchor=north, draw}
  [B]
  [C
    [D]
    [E]
  ]
]
\end{forest}

\begin{forest}
[A, for tree={parent anchor=south, child anchor=north, align=center, draw}
  [B]
  [C
    [D]
    [E]
  ]
]
\end{forest}
\end{document}

First tree has square nodes.  Tree where all nodes are align=center has rectangular nodes with extra space on top and bottom of node text.

I tried reducing inner ysep, and it initially appeared to help (the space around the node text is smaller but still off-center), but then I have problems with some nodes overlapping:

\documentclass{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
[A, for tree={draw, parent anchor=south, child anchor=north, align=center, inner ysep=1pt }
  [B\\long node]
  [C
    [D\\long multiline, triangle]
  ]
]
\end{forest}
\end{document}

Multi-line node overlapping the triangle of another multi-line node.

I should also mention that I am not using draw to box the nodes in my real trees, just bare node text. I use it here because it makes it clear that it is the node size, not the edge position, that is the issue. This extra space makes it look like the edges are starting farther from the nodes, rather than the nodes being taller.

10
  • 1
    Welcome! There's no easy way to do this, I don't think. That is, you can alter the effect of the tabular environment by looking at the standard LaTeX definitions and playing with that. But there's nothing in forest which is just using TikZ which is just using the standard environment, I believe (but could be wrong). Note that one advantage of this is that your nodes will align better. Can you not compensate by simply reducing inner ysep for the tree?
    – cfr
    Nov 29, 2015 at 1:19
  • @cfr Thanks. I had tried inner ysep. I edited my post to show why it didn't work well for me.
    – kek
    Nov 29, 2015 at 1:43
  • 1
    The reason there's more space beneath than above is because your nodes don't have long descenders (cf. Cy for example).
    – cfr
    Nov 29, 2015 at 1:45
  • 2
    The problem with edges cutting through a sibling is an inherent bug/feature of the package. It can happen whether you change inner ysep or not. See page 54 of the manual for discussion about why this happens and why manual intervention is better than 'fixing' the package.
    – cfr
    Nov 29, 2015 at 1:51
  • To be honest, I'm not sure there is much to be done about this. If you put a tabular manually inside an \mbox{} inside a node in a tree without multiline nodes, you get exactly the same spacing. It is just the effect of the tabular environment and the way it aligns things, for example.
    – cfr
    Nov 29, 2015 at 1:52

1 Answer 1

5

This is a hack. It is provided as is. There are no guarantees. I don't even guarantee that it has anything to do with forest, multiline nodes or TeX. At this point, in case it is not clear, I should state that I guarantee nothing whatsoever. If the code dials the CIA, MI5 and the KGB and offers them your cat in exchange for a tube of Smarties, don't say I didn't warn you.

Caveat emptor...


Here's the idea:

  • If we put a tabular into a node in a tree which does not use align to create multiline nodes, we get just the same results as we do using align. However, tabular itself doesn't add vertical space, so we can't set that space to 0 as it is already zero.

  • If we put a minipage into a node in the same tree, the spacing is the same as putting the contents of the minipage in the node alone. (Assuming the node has only one line.)

  • So, if we could create multiline nodes as minipages rather than tabulars, then it would work as desired. (Whether what is desired is desirable is left for you to determine, gentle reader.)

This is not as straightforward as it sounds because a minipage, unlike a tabular requires a width and we don't want to have to set a width for all nodes in the tree.

Surely, there are easier ways to do this and doubtless there are far more efficient ones. Especially a TeX guru would likely do this far better.

At this point, the gentle reader is urged to review again the statement above concerning guarantees and the danger to the cats of all readers, however gentle.

cat leaves home

We start by creating a new style squat multiline for trees of this kind:

\forestset{
  squat multiline/.style={

We need a couple of custom forest options:

    declare dimen={thiswidth}{0pt},
    declare autowrapped toks={thiscontent}{},

Now we start manipulating the nodes. We do this in cycles to ensure that each set of keys are processed for each node before the next set of keys is processed for each node.

    for tree={
      delay={

This is round one:

        thiscontent/.wrap pgfmath arg={##1}{content()},

In effect, this saves the content of the node. It copies it, basically. We delay this else the content won't be set when we try to copy it.

      },

Now for the next round:

      delay+={
        content/.wrap value={\begin{tabular}{@{}c@{}}##1\\\end{tabular}},

This wraps the content in a tabular environment like that used by align=center. We are going to use this to get the width for the minipage.

        typeset node,

We typeset the node now, earlier than usual, because we need to get the width next.

      },

The final round:

      before typesetting nodes={

We set thiswidth using the width of the node as it currently is.

        thiswidth/.wrap pgfmath arg={##1}{int(abs(max_x()-min_x()))},

Now we reset the content of the node in a minipage of width thiswidth. We use the copy of the contents from thiscontent because otherwise we'll get the tabular wrapper as well.

        content/.wrap pgfmath arg={\begin{minipage}[t]{\dimexpr\forestove{thiswidth}}\centering ##1\end{minipage}}{thiscontent()},

We've already got one lot of inner xsep because it was included when we typeset the node originally. So we set it to 0 to avoid getting double the usual amount:

        inner xsep=0pt,

We need to tell forest to typeset the node again:

        typeset node,
      },

Just the usual anchors from the original example:

      parent anchor=south,
      child anchor=north,
    },
  }
}

Now we can use the new style. The first example compares the default spacing with that used by squat multiline when the content of the nodes are identical:

\begin{forest}
  for tree={draw}
  [, phantom
    [A, for tree={parent anchor=south, child anchor=north}
      [B]
      [C
        [D]
        [E]
      ]
    ]
    [A, squat multiline
      [B]
      [C
        [D]
        [E]
      ]
    ]
  ]
\end{forest}

comparison of spacing

Then the example with larger nodes and a roof:

\begin{forest}
  for tree={draw},
  squat multiline
  [A,
    [B\\long node]
    [C
      [D\\long multiline, triangle]
    ]
  ]
\end{forest}

more complex case

The style does not appear be responsible for the slight misalignment of the roof here as I get the same issue when I compare the default style with drawn nodes.

Comparison:

tree 1 tree 2

In any case, I assume that the combination of a roof and draw is unlikely to be desirable, and the misalignment is only noticeable because the nodes are drawn for purposes of illustration.

Complete code:

\documentclass[multi,tikz,border=10pt]{standalone}
\usepackage{forest}
\begin{document}
\forestset{
  squat multiline/.style={
    declare dimen={thiswidth}{0pt},
    declare autowrapped toks={thiscontent}{},
    for tree={
      delay={
        thiscontent/.wrap pgfmath arg={##1}{content()},
      },
      delay+={
        content/.wrap value={\begin{tabular}{@{}c@{}}##1\\\end{tabular}},
        typeset node,
      },
      before typesetting nodes={
        thiswidth/.wrap pgfmath arg={##1}{int(abs(max_x()-min_x()))},
        content/.wrap pgfmath arg={\begin{minipage}[t]{\dimexpr\forestove{thiswidth}}\centering ##1\end{minipage}}{thiscontent()},
        inner xsep=0pt,
        typeset node,
      },
      parent anchor=south,
      child anchor=north,
    },
  }
}
\begin{forest}
  for tree={draw}
  [, phantom
    [A, for tree={parent anchor=south, child anchor=north}
      [B]
      [C
        [D]
        [E]
      ]
    ]
    [A, squat multiline
      [B]
      [C
        [D]
        [E]
      ]
    ]
  ]
\end{forest}
\begin{forest}
  for tree={draw},
  squat multiline
  [A,
    [B\\long node]
    [C
      [D\\long multiline, triangle]
    ]
  ]
\end{forest}
\end{document}
6
  • Ingenious! (Btw, the roof issue is solved in v2.0. I have used a coordinate instead of cycle ...) Nov 29, 2015 at 9:43
  • Thanks. Is there a better way to get the width? I tried some other methods but none of them worked, but I'm not very good with TeX stuff.
    – cfr
    Nov 29, 2015 at 14:14
  • I'm guessing /tikz/line join could help here. Nov 29, 2015 at 15:26
  • Impressive! I admit I don't understand it all, but it mostly does the trick. (I haven't yet recompiled all of my syntax trees, and some of them end up with too little horizontal spacing now, but I can work around that pretty easily.) Thank you!
    – kek
    Nov 30, 2015 at 3:03
  • @kek It isn't actually changing the horizontal spacing. However, you may need more to compensate for tighter vertical spacing, I guess. Glad it works!
    – cfr
    Nov 30, 2015 at 3:09

You must log in to answer this question.

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