Lost grow/rotate somewhere? calign=fixed edge angles behaves inconsistently when grow is not equal to 270
Copy of TeX SE question so it (hopefully) doesn't get lost (lazy):
I would like to understand what causes the following behaviour in forest.
MWE [Note that most of the code isn't code necessary to reproduce the problem but rather annotations I've added in an attempt to understand the problem.]
According to the documentation (page 41):
calign=fixed edge angles: The angle between the direction of growth at the current node (specified by optiongrow) and the line through the parent’s parent anchor and the primary/secondary child’s child anchor will equal the primary/secondary angle. To achieve this, the block of children might be spread or further distanced from the parent.
When the default growth direction (270) is used, the option has more-or-less the effect I expect. This is the second tree in the example below. Note that the edges align with the lines I've added by hand to indicate where I think they should be drawn. (The colours match the dots marking the children the edges are drawn to.)
When the growth direction is flipped (90), however, the option does not at all do what I would expect. This is the first tree in the example below. Note that the edges do not align with the lines added by hand this time.
When the growth direction is turned through a right angle (0), I would expect the edges to end up orthogonal to the lines added by hand. I would not expect what I actually get at all.
Note that the dashed lines indicate the direction of growth for the first two trees and are orthogonal to that direction for the third tree. In each case, the dashed lines pass through the parent nodes' regular node anchors.
The coloured dots mark the parent and child anchors of the nodes, as appropriate.
Code
Note that the example is from Stefan Müller's question and the explanation for my looking at this is in my answer:
\documentclass{standalone}
\usepackage{forest}
\forestset{%
test style/.style={%
for tree={%
grow=north,
parent anchor=children,
child anchor=parent,
calign=fixed edge angles,
calign angle=25,
},
},
}
\newcommand \testcmd {%
\foreach \i/\j in {%
(!r.parent anchor)/red,%
(!r1.child anchor)/blue,
(!r2.child anchor)/green,
(!r2.parent anchor)/magenta,
(!r21.child anchor)/gray,
(!r22.child anchor)/orange%
} \path [fill=\j] \i circle (1pt);
\foreach \i/\j/\k in {%
(!r.parent anchor)/blue/65,%
(!r2.parent anchor)/orange/115,%
(!r.parent anchor)/green/115,%
(!r2.parent anchor)/gray/65%
}
\draw [ultra thin,\j] \i edge +(\k:50mm) -- ++({180+\k}:50mm);
\foreach \i in {%
!r,!r2%
} \draw [help lines,densely dashed] (\i) edge +(90:40mm) -- ++(-90:40mm);
}
\begin{document}
\begin{forest}
test style,
[lesbarkeit
[keit]
[lesbar
[bar]
[les]
]
]
\testcmd
\end{forest}
\begin{forest}
test style,
for tree={grow=-90},
[lesbarkeit
[keit]
[lesbar
[bar]
[les]
]
]
\testcmd
\end{forest}
\begin{forest}
test style,
for tree={grow=0},
[lesbarkeit
[keit]
[lesbar
[bar]
[les]
]
]
\testcmd
\end{forest}
\end{document}
The definition of fixed edge angles is
% from forest.sty 2017/07/14 v2.1.5
\csdef{forest@calign@fixed edge angles}{%
\ifnum\forestove{n children}>1
\edef\forest@ca@first@child{\forest@node@nornbarthchildid{\forestove{calign primary child}}}%
\edef\forest@ca@second@child{\forest@node@nornbarthchildid{\forestove{calign secondary child}}}%
\ifnum\forestove{reversed}=1
\let\forest@temp\forest@ca@first@child
\let\forest@ca@first@child\forest@ca@second@child
\let\forest@ca@second@child\forest@temp
\fi
\forestOget{\forest@ca@first@child}{l}\forest@ca@first@l
\forestOget{\forest@ca@second@child}{l}\forest@ca@second@l
\forest@pointanchor{parent anchor}%
\edef\forest@ca@parent@anchor@s{\the\pgf@x}%
\edef\forest@ca@parent@anchor@l{\the\pgf@y}%
\forest@Pointanchor{\forest@ca@first@child}{child anchor}%
\edef\forest@ca@first@child@anchor@s{\the\pgf@x}%
\edef\forest@ca@first@child@anchor@l{\the\pgf@y}%
\forest@Pointanchor{\forest@ca@second@child}{child anchor}%
\edef\forest@ca@second@child@anchor@s{\the\pgf@x}%
\edef\forest@ca@second@child@anchor@l{\the\pgf@y}%
\pgfmathtan@{\forestove{calign secondary angle}}%
\edef\forest@temp{\the\dimexpr
\forest@ca@second@l-\forest@ca@second@child@anchor@l+\forest@ca@parent@anchor@l}%
\pgfmathmultiply@{\pgfmathresult}{\expandafter\Pgf@geT\forest@temp}%
\edef\forest@ca@desired@second@edge@s{\the\dimexpr\pgfmathresult pt}%
%\pgfmathsetlengthmacro\forest@ca@desired@second@edge@s{%
% tan(\forestove{calign secondary angle})*%
% (\forest@ca@second@l-\forest@ca@second@child@anchor@l+\forest@ca@parent@anchor@l)}%
\pgfmathtan@{\forestove{calign primary angle}}%
\edef\forest@temp{\the\dimexpr
\forest@ca@first@l-\forest@ca@first@child@anchor@l+\forest@ca@parent@anchor@l}%
\pgfmathmultiply@{\pgfmathresult}{\expandafter\Pgf@geT\forest@temp}%
\edef\forest@ca@desired@first@edge@s{\the\dimexpr\pgfmathresult pt}%
%\pgfmathsetlengthmacro\forest@ca@desired@first@edge@s{%
% tan(\forestove{calign primary angle})*%
% (\forest@ca@first@l-\forest@ca@first@child@anchor@l+\forest@ca@parent@anchor@l)}%
\edef\forest@ca@desired@s@distance{\the\dimexpr
\forest@ca@desired@second@edge@s-\forest@ca@desired@first@edge@s}%
%\pgfmathsetlengthmacro\forest@ca@desired@s@distance{\forest@ca@desired@second@edge@s-\forest@ca@desired@first@edge@s}%
\forestOget{\forest@ca@first@child}{s}\forest@ca@first@s
\forestOget{\forest@ca@second@child}{s}\forest@ca@second@s
\edef\forest@ca@actual@s@distance{\the\dimexpr
\forest@ca@second@s+\forest@ca@second@child@anchor@s
-\forest@ca@first@s-\forest@ca@first@child@anchor@s}%
%\pgfmathsetlengthmacro\forest@ca@actual@s@distance{%
% \forest@ca@second@s+\forest@ca@second@child@anchor@s
% -\forest@ca@first@s-\forest@ca@first@child@anchor@s}%
\ifdim\forest@ca@desired@s@distance>\forest@ca@actual@s@distance\relax
\ifdim\forest@ca@actual@s@distance=0pt
\forestoget{n children}\forest@temp@n@children
\forest@node@foreachchild{%
\forest@pointanchor{child anchor}%
\edef\forest@temp@child@anchor@s{\the\pgf@x}%
\forestoeset{s}{\the\dimexpr
\forest@ca@desired@first@edge@s+\forest@ca@desired@s@distance*(\forestove{n}-1)/(\forest@temp@n@children-1)+\forest@ca@first@child@anchor@s-\forest@temp@child@anchor@s}%
%\pgfmathsetlengthmacro\forest@temp{%
% \forest@ca@desired@first@edge@s+(\forestove{n}-1)*\forest@ca@desired@s@distance/(\forest@temp@n@children-1)+\forest@ca@first@child@anchor@s-\forest@temp@child@anchor@s}%
%\forestolet{s}\forest@temp
}%
\def\forest@calign@anchor{0pt}%
\else
\edef\forest@marshal{\noexpand\pgfmathdivide@
{\expandafter\Pgf@geT\forest@ca@desired@s@distance}%
{\expandafter\Pgf@geT\forest@ca@actual@s@distance}%
}\forest@marshal
\let\forest@ca@ratio\pgfmathresult
%\pgfmathsetmacro\forest@ca@ratio{%
% \forest@ca@desired@s@distance/\forest@ca@actual@s@distance}%
\forest@node@foreachchild{%
\forest@pointanchor{child anchor}%
\edef\forest@temp@child@anchor@s{\the\pgf@x}%
\edef\forest@marshal{\noexpand\pgfmathmultiply@
{\forest@ca@ratio}%
{\expandafter\Pgf@geT\the\dimexpr
\forestove{s}-\forest@ca@first@s+%
\forest@temp@child@anchor@s-\forest@ca@first@child@anchor@s}%
}\forest@marshal
\forestoeset{s}{\the\dimexpr\pgfmathresult pt+\forest@ca@first@s
+\forest@ca@first@child@anchor@s-\forest@temp@child@anchor@s}%
% \pgfmathsetlengthmacro\forest@temp{%
% \forest@ca@ratio*(%
% \forestove{s}-\forest@ca@first@s
% +\forest@temp@child@anchor@s-\forest@ca@first@child@anchor@s)%
% +\forest@ca@first@s
% +\forest@ca@first@child@anchor@s-\forest@temp@child@anchor@s}%
% \forestolet{s}\forest@temp
}%
\pgfmathtan@{\forestove{calign primary angle}}%
\edef\forest@marshal{\noexpand\pgfmathmultiply@
{\pgfmathresult}%
{\expandafter\Pgf@geT\the\dimexpr
\forest@ca@first@l-\forest@ca@first@child@anchor@l+\forest@ca@parent@anchor@l}%
}\forest@marshal
\edef\forest@calign@anchor{\the\dimexpr
-\pgfmathresult pt+\forest@ca@first@child@anchor@s-\forest@ca@parent@anchor@s}%
% \pgfmathsetlengthmacro\forest@calign@anchor{%
% -tan(\forestove{calign primary angle})*(\forest@ca@first@l-\forest@ca@first@child@anchor@l+\forest@ca@parent@anchor@l)%
% +\forest@ca@first@child@anchor@s-\forest@ca@parent@anchor@s
% }%
\fi
\else
\ifdim\forest@ca@desired@s@distance<\forest@ca@actual@s@distance\relax
\edef\forest@marshal{\noexpand\pgfmathdivide@
{\expandafter\Pgf@geT\forest@ca@actual@s@distance}%
{\expandafter\Pgf@geT\forest@ca@desired@s@distance}%
}\forest@marshal
\let\forest@ca@ratio\pgfmathresult
%\pgfmathsetlengthmacro\forest@ca@ratio{%
% \forest@ca@actual@s@distance/\forest@ca@desired@s@distance}%
\forest@node@foreachchild{%
\forest@pointanchor{child anchor}%
\edef\forest@temp@child@anchor@l{\the\pgf@y}%
\edef\forest@marshal{\noexpand\pgfmathmultiply@
{\forest@ca@ratio}%
{\expandafter\Pgf@geT\the\dimexpr\forestove{l}+\forest@ca@parent@anchor@l-\forest@temp@child@anchor@l}%
}\forest@marshal
\forestoeset{l}{\the\dimexpr
\pgfmathresult pt-\forest@ca@parent@anchor@l+\forest@temp@child@anchor@l}%
% \pgfmathsetlengthmacro\forest@temp{%
% \forest@ca@ratio*(%
% \forestove{l}+\forest@ca@parent@anchor@l-\forest@temp@child@anchor@l)
% -\forest@ca@parent@anchor@l+\forest@temp@child@anchor@l}%
% \forestolet{l}\forest@temp
}%
\forestOget{\forest@ca@first@child}{l}\forest@ca@first@l
\pgfmathtan@{\forestove{calign primary angle}}%
\edef\forest@marshal{\noexpand\pgfmathmultiply@
{\pgfmathresult}%
{\expandafter\Pgf@geT\the\dimexpr
\forest@ca@first@l+\forest@ca@parent@anchor@l-\forest@temp@child@anchor@l}%
}\forest@marshal
\edef\forest@calign@anchor{\the\dimexpr
-\pgfmathresult pt+\forest@ca@first@child@anchor@s-\forest@ca@parent@anchor@s}%
% \pgfmathsetlengthmacro\forest@calign@anchor{%
% -tan(\forestove{calign primary angle})*(\forest@ca@first@l+\forest@ca@parent@anchor@l-\forest@temp@child@anchor@l)%
% +\forest@ca@first@child@anchor@s-\forest@ca@parent@anchor@s
% }%
\fi
\fi
\forest@calign@s@shift{-\forest@calign@anchor}%
\fi
}
I know that in the case that grow=90, \forest@ca@actual@s@distance is calculated to be less than \forest@ca@desired@s@distance, whereas when grow=270, it is calculated to be greater. This means that when the tree is growing down, forest tries to adjust the tree by pushing the children closer together, relative to the distance from the children to the parent. This works. But when the tree is growing upwards, forest tries to adjust the tree by pulling the children apart, relative to the distance from the children to the parent. This doesn't work because, in fact, the children need to be pushed closer together.
The fact that the tree is growing up rather than down really shouldn't matter because everything is being done using l and s which are relative to the parent node and growth direction. But it does.
Originally, I thought the problem was that \forest@ca@first@s always turned out to be 0pt, but that's true even when the tree grows downwards, so that can't be it.
Please note that I'm NOT asking for a workaround or an implementation using a different method or package. 'Workaround' being somewhat ambiguous, I'm definitely interested in, say, a way to patch forest or redefine something so that it works as intended. But I'm not interested in a method which involves, for example, overriding forest's decisions in before drawing tree or adding an additional stage to do manual corrections. I don't want somebody else to spend time doing something I can do for myself.
I'm more interested in understanding the problem than in solving it. Obviously, I'd like to solve it, but I really just want to understand it at this point.
EDIT
I'm not at all certain, but it seems to me that the problem is in the way the calculation of the desired s distance between parent and children is done. This is based on finding the l distance between the parent's parent anchor and the child anchors of the primary and secondary children.
To get the l distance, forest retrieves the value of l for the first/secondary child and then adjusts it by retrieving the differences between the parent's node anchor and the parent's parent anchor, on the one hand, and the child's node anchor and the child's child anchor, on the other. So if the child anchor is closer to the parent than the node anchor, for example, that gets subtracted from l before calculating the desired s.
But when forest retrieves l for the children, l is relative to the growth direction of the tree. So it is always positive. However, when it retrieves the displacement of the child/parent anchors, it gets the underlying value of y, which is not relative to the growth direction. So if the tree is growing upwards, the signs of 2 of the 3 components in the calculation are reversed and the value used for l in calculating desired s is larger than it should be. This is why forest thinks it needs to increase the separation of the children, rather than decreasing it.
% this is relative to the growth direction
\forestOget{\forest@ca@first@child}{l}\forest@ca@first@l
% this is relative to the growth direction
\forestOget{\forest@ca@second@child}{l}\forest@ca@second@l
\forest@pointanchor{parent anchor}%
\edef\forest@ca@parent@anchor@s{\the\pgf@x}%
\edef\forest@ca@parent@anchor@l{\the\pgf@y}% but this isn't
\forest@Pointanchor{\forest@ca@first@child}{child anchor}%
\edef\forest@ca@first@child@anchor@s{\the\pgf@x}%
\edef\forest@ca@first@child@anchor@l{\the\pgf@y}% and nor is this
\forest@Pointanchor{\forest@ca@second@child}{child anchor}%
\edef\forest@ca@second@child@anchor@s{\the\pgf@x}%
\edef\forest@ca@second@child@anchor@l{\the\pgf@y}% and nor is this
Does this seem right? Unfortunately, I'm not sure how to correct it. It wouldn't be hard to correct for upwards growth (I could just multiply the ys by sin of grow, for example), but obviously the tree could be growing at an angle of 45 or 0 or 180, so merely correcting for the case where grow=90 isn't much use.
