3

I'm trying to build a tree that grows horizontally and which gives the possibility to introduce lists with a "folder" aspect in such a way as to specify a series of topics related to that node.

In the code that I have attached, there are two problems that I cannot solve:

  1. the distance between the node whose children appear as folders and the first element of the folder nodes is greater than the other distances of the rest of the children.

  2. the nodes belonging to the folder overlap the rest of the tree.

I had already opened a question on a similar topic (Forest folder in forked edges tree spacing problem), but I can't take advantage of what was suggested to me for this other situation.

In order to have a node with subtrees growing in different directions I tried to use what is suggested in this question:

Forest: different growing directions for branches on the same level

Is there any way to fix these problems?

MWE:

\documentclass[varwidth=20cm,tikz]{standalone}

\usepackage[edges]{forest}

\forestset{
  rounded forked tree/.style={
    for tree={
      grow'=east,
      fit=band,
      rounded corners,
      line width=1.6pt,
      font=\sffamily\bfseries\Large,
      inner xsep=5mm,
      inner ysep=5mm,
      outer ysep=0pt,
      edge+={line width=1.6pt, rounded corners=2mm},
      if n children=1{
        for first={
          before typesetting nodes={
            edge+={rounded corners=0mm},
          }
        }
      }{},
      align=center,
      l sep'=20mm,
      s sep'=7mm,
      fork sep'=8mm,
      draw,
      minimum height=15mm,
      minimum width=20mm,
      anchor=west,
    },
    forked edges,
  },
  folder subtree/.style={
    for tree={
      folder,
      s sep'=5mm,
      l sep'=10mm,
      minimum height=5mm,
    },
    folder indent'=5mm,
    anchor=west,
    for descendants={
      align=left,
      anchor=base west,
      base=top,
      inner xsep=2mm,
      inner ysep=1mm,
      before typesetting nodes={
        edge+={rounded corners=2mm},
      },
      edge path'/.expanded={
        ([xshift=\forestregister{folder indent}]!u.parent anchor) |- ([yshift=.6ex].base west)
      },
    },
  },
  phantom node/.style={
    !c.content/.pgfmath=content("!parent"),
    text opacity=0,
    draw=none,
    no edge,
    before computing xy={
      l=0,
      s=0
    }
  },
  grow subtree/.style={
    for tree={grow=#1}
  }, 
  grow' subtree/.style={
    for tree={grow'=#1}
  },
}

\begin{document}

\begin{forest}
  rounded forked tree,
  [Root
    [Node first line\\(Node second line)
      [, phantom node, folder subtree
        [Folder element]
        [Folder element]
        [Folder element
          [Folder element child]
          [Folder element child]
        ]
        [Folder element first line\\(Folder element second line)]
        [Folder element]
        [Folder element]
      ]
      [, phantom node, grow' subtree=east,
        [Node]
        [Node]
      ]
    ]
    [Node
      [Node first line\\(Node second line)]
      [Node]
    ]
  ]
\end{forest}

\end{document}

1 Answer 1

5

Let me address (2) first. The subtrees were overlapping because l and s of the phantom nodes were set to zero at a wrong time. At before computing xy, the tree is already packed, and any manual changes to node coordinates may result in overlap. The solution is to change l and s of the phantom nodes just after packing the parent node: after packing node={for children={l=0,s=0}}. This essentially annuls the effect of packing the phantom parent, but crucially, the subsequent packing of the ancestor nodes will use the correct (i.e. manually changed) outline of the subtree containing the phantom nodes.

The problem with (1) is that folder relies on its own settings of anchor, which must be set to parent first for both the folder parent and its children. The OP's code, however, changed it to west/base west. And for a good reason: if the phantom copy is to be drawn in the same place as its original, they must have the same anchor. So if we remove anchor=west and anchor=base west in style folder subtree, which is applied to the phantom nodes, and draw these nodes (change draw=none to draw), we can see that they are shifted with respect to the originals.

The solution is to remove the offending anchor invocations, but simulate their effect by a manual shift of l and s. Subtracting the desired anchor (above, west) from these coordinates should do the trick. But how do we get the anchor coordinates? Here's when things get very low-level, and hacky.

Those who have delved into PGF know how to access the coordinates of a node anchor: using \pgfpointanchor. Well, Forest has a related private macro, \forest@pointanchor, which I almost made public and which I at first wanted to use in this situation. However, it turned out that the macro is incomplete, as it only accepts anchor options ((parent/child) anchor), but not the normal, TikZ anchor names like north, or Forest's anchors like parent first. (It seems that anchor options were the only usage case needed internally.) Below, I define \forestpointanchor, which remedies the problem, and which will make it into the package upon testing. (I don't believe it makes much sense to explain here how it works.)

And I also define the public interface to this macro, style get point anchor. This style takes three arguments: #1 is the name of the anchor to be retrieved (it can be either a normal TikZ anchor, or one of the Forest anchors like parent first, or an anchor option like parent anchor); #2 and #3 are the keys which will receive the x and y coordinates of the anchor, respectively. If you want to use them in subsequent calculations, storing them into tempdimx and tempdimy is a good idea. Below, we simply pass them on to l- and s-, which effectively substract the received x and y from l and s.

Incidentally, !c.content in the original phantom node style is equivalent to a plain content, so !c. (which stands for "move to the current node) may be dropped. Otherwise, I would say that the OP's code is the cleanest piece of Forest code ever posted on this site. Kudos!

\documentclass[varwidth=20cm,tikz]{standalone}

\usepackage[edges]{forest}

\forestset{
  rounded forked tree/.style={
    for tree={
      grow'=east,
      fit=band,
      rounded corners,
      line width=1.6pt,
      font=\sffamily\bfseries\Large,
      inner xsep=5mm,
      inner ysep=5mm,
      outer ysep=0pt,
      edge+={line width=1.6pt, rounded corners=2mm},
      if n children=1{
        for first={
          before typesetting nodes={
            edge+={rounded corners=0mm},
          }
        }
      }{},
      align=center,
      l sep'=20mm,
      s sep'=7mm,
      fork sep'=8mm,
      draw,
      minimum height=15mm,
      minimum width=20mm,
      anchor=west,
    },
    forked edges,
  },
  folder subtree/.style={
    for tree={
      folder,
      s sep'=5mm,
      l sep'=10mm,
      minimum height=5mm,
    },
    folder indent'=5mm,
    % anchor=west,
    for descendants={
      align=left,
      % anchor=base west,
      base=top,
      inner xsep=2mm,
      inner ysep=1mm,
      before typesetting nodes={
        edge+={rounded corners=2mm},
      },
      edge path'/.expanded={
        ([xshift=\forestregister{folder indent}]!u.parent anchor) |- ([yshift=.6ex].base west)
      },
    },
  },
  phantom children/.style={
    delay={
      for children={
        content/.pgfmath=content("!parent"),
        text opacity=0,
        draw,%=none,
        no edge,
      },
    },
    after packing node={
      for children={
        l=0,
        s=0,
        get point anchor={west}{l-}{s-},
      },
    },
  },
  grow subtree/.style={
    for tree={grow=#1}
  }, 
  grow' subtree/.style={
    for tree={grow'=#1}
  },
}

\makeatletter
% Analogous to \pgfpointanchor, but also allows forest anchors ("parent first"
% etc.)  and forest anchor options ("parent anchor" etc.) as #1.
\def\forestpointanchor#1{% #1 = anchor
  \forestanchortotikzanchor{#1}\forest@temp@anchor
  \forestoifdefined{@box}{%
    \forestoget{@box}\forest@temp
    \ifdefempty\forest@temp{%
      \forestpointanchor@callpgfpointanchor{name}%
    }{%
      \setbox0\hbox{%
        \begin{pgfpicture}%
          \forestpointanchor@callpgfpointanchor{later@name}%
        \end{pgfpicture}%
      }%
    }%
  }{%
    \forestpointanchor@callpgfpointanchor{name}%
  }%
  \forest@global@marshal
}
\def\forestpointanchor@callpgfpointanchor#1{% #1 = (later@)name
  \pgfpointanchor{\forestove{#1}}{\forest@temp@anchor}%
  \xdef\forest@global@marshal{%
    \global\pgf@x=\the\pgf@x\relax
    \global\pgf@y=\the\pgf@y\relax\relax
  }%
}
\csdef{forest@anchor@@}{\def\forest@temp@anchor{center}}%
\def\forestPointanchor#1#2{% #1 = node id, #2 = anchor
  \forest@fornode{#1}{\forestpointanchor{#2}}%
}
\forestset{
  % Get the coordinates of anchor #1
  % and assign the resulting x and y to keys #2 and #3, respectively.
  get point anchor/.code n args=3{%
    \forestpointanchor{#1}%
    \forestset{#2=\pgf@x,#3=\pgf@y}%
  },
}
\makeatother

\begin{document}

\begin{forest}
  rounded forked tree,
  [Root
    [Node first line\\(Node second line), phantom children
      [, folder subtree
        [Folder element]
        [Folder element]
        [Folder element
          [Folder element child]
          [Folder element child]
        ]
        [Folder element first line\\(Folder element second line)]
        [Folder element]
        [Folder element]
      ]
      [, grow' subtree=east,
        [Node]
        [Node]
      ]
    ]
    [Node
      [Node first line\\(Node second line)]
      [Node]
    ]
  ]
\end{forest}

\end{document}

the result of the compilation

2
  • Hi Sašo, thanks for the solution and explanations, I really appreciated your answer. However copying the code you provided, I get this error: Control sequence not defined.<argument> \forest@unmarknextpictureposition Can you tell me what it's due to?
    – Datboi
    Commented Jul 24, 2023 at 8:10
  • 2
    Oh, this is something from a future release of the package. Just remove it. (I will edit the code above.) Commented Jul 24, 2023 at 8:20

You must log in to answer this question.

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