如何使森林中的边长度与每一级边标签的最大长度相适应?

如何使森林中的边长度与每一级边标签的最大长度相适应?

我面临以下问题:我想创建一种森林风格,使森林边缘的长度取决于边缘上标签的长度。为了得到节省空间的树木,同时按级别对齐,我希望边缘的长度取决于最大标签大小在每个层面上树的。

为了解决这个问题,我从针对该问题提供的解决方案开始:

如何将森林中的边缘延长至边缘标签的长度?

我稍微调整了这个例子,以计算整棵树中的最大标签大小,并得出以下解决方案,将边缘大小调整为出现在整棵树

\documentclass[border=10pt, tikz, multi, varwidth]{standalone}
\usepackage{forest}
\standaloneenv{forest}

\begin{document}
\forestset{
   my edge label/.style={
    if={n>.5*(n_children("!u"))}{
      edge label = {node [midway, below, sloped] {#1}},
    }{
      edge label = {node [midway, above, sloped] {#1}},
    },
    TeX={\settowidth{\mylabelwidth}{#1}},
    TeX={\pgfmathsetlength{\mymaxlabelwidth}{max(\mylabelwidth,\mymaxlabelwidth)}},
  },
  ptreetest/.style= {
     TeX={\newlength{\mylabelwidth}},
     TeX={\newlength{\mymaxlabelwidth}},
     TeX={\settowidth{\mymaxlabelwidth}{5pt}}, % minimum width is 5pt
     for tree={%
        grow' = east,
        before computing xy={
            if={
                (l)<(sqrt(abs((\mymaxlabelwidth)^2 - s^2))) + 10pt)
            }{
                l={sqrt(abs((\mymaxlabelwidth)^2  - s^2))) + 10pt},
            }{},
         },
    },
  },
}

\begin{forest}
ptreetest
[A
    [B, my edge label={very long label}
        [E, my edge label={short}]
        [F, my edge label={ws}]
    ]
    [C, my edge label= {short}]
]
\end{forest}

\end{document}

不过,我真的希望长度能够根据等级。我的第一个想法是,这应该很容易实现,不用变量\mymaxlabelwidth,而是用一个这样的变量数组来计算(然后访问)每个级别的最大值。不幸的是,我没能实现这个想法。我尝试了几种在 latex 中实现数组的方法(例如,使用pgfforpgfkeys),但没有一种对我有用(我想是因为相应数组的范围不正确)。我也对进行了一些尝试expl3,但可能是因为我对此缺乏了解,所以我没能实现所需的功能。

如果有人能帮助我解决这个问题,那就太好了!非常感谢!

答案1

这个答案超出了OP的要求。它不仅实现了每级调整,而且还尝试计算l需要更可靠地适合边缘标签。

让我们从级别开始。正如 OP 所建议的,我们需要存储多个边标签的标签宽度,最简单的方法是声明一个新的森林(维度)选项:declare dimen={my edge label width}{0pt}。森林选项自动与节点相关,因此数据结构立即到位。处理数据的工具也是如此;特别是,我们将利用聚合函数max

实际上,下面的代码实现了三种样式,不同之处在于拟合的“局部性”。 和 之间的区别fit edge labels childrenfit edge labels levelfit edge labels tree图中应该很明显。

在此处输入图片描述

这三种风格都使用 Forest 的聚合函数max(处理程序形式为.max)来计算所需的l,并使用空间传播器在相关节点for ...设置 的值l。不同之处在于.max聚合的内容:children或;这些相同的节点行走(形式为)也用于将 的新值分发level=...到相关节点。descendantsfor ...l

现在讨论另一个问题,即更可靠地计算l。原始代码的工作原理是假设ls坐标(父节点和子节点的坐标)是边缘端点的良好近似值。并且在某种程度上,它们是,并且可以手动修复差异 --- 这是+ 10pt原始代码中实现的。但如果我们精确的话,ls是节点的 的坐标anchor,而边缘的端点对应于parent anchor父节点的 和child anchor子节点的 。

但我们如何才能坐标父节点和子节点的宽度是多少?为了得到它们,下面的代码实际上排版了父节点和子节点(当然,结果会被丢弃)。这是通过宏 完成的\measureedge,这个宏是从我们定义的 pgfmath 函数 调用的l_for_my_edge_label,它使用 提供的坐标\measureedge和 中存储的边缘标签的宽度my edge label width来计算l边缘标签适合所需的 。有关更多详细信息,请参阅代码中的注释。

然而,即使所有这些也不能给我们带来完全万无一失的解决方案。当parent/child anchor为空(默认值)时,TikZ 会将边缘绘制到一个神奇确定的边界点,而当我们“移动”节点(通过调整l)时,接触点可能会发生变化。幸运的是,(i)效果并不十分明显,只有在宽节点上才能看到;(ii)当我们使用固定的父/子锚点时,问题就会消失;(iii)手动解决方法非常简单。有关详细信息,请参阅第一棵树的代码中的注释 (2)。

最后,请注意代码(l_for_my_edge_label特别是)假设树水平生长。好吧,对于向西生长,它开箱即用;对于向东生长,在定义中交换{above}和以翻转边缘标签的位置。{below}my edge label a

\documentclass[border=10pt, tikz, multi, varwidth]{standalone}
\usepackage{forest}
\standaloneenv{forest}

% This macro draws (and discards) a tree fragment: the current node, its parent
% and the edge between them.  After drawing the edge (which is assumed to
%   consist of a single line segment), it measures the bounding box of the edge
% path to get the "width" and the "height" of the edge.  The macro expects the
% x and y coordinates of the nodes to be computed.
\def\measureedge{%
  \begingroup
  \forestset{
    % Put the tree (fragment) into box 0, which we will not use.
    draw tree box=0,
    % Remember the current node.
    alias=l_for_my_edge_label,
    % Draw just this node and its parent.
    draw tree processing order/.nodewalk style={name=l_for_my_edge_label,parent},
    % Just in case, for generality, don't draw any associated tikz code.
    draw tree tikz processing order/.nodewalk style={},
    % After constructing the edge path, this code gets the dimensions of the
    % edge; see TikZ manual 15.6 for info on "path picture".  As this is
    % executed deep inside TeX groups, we need to assign "\edgewidth" and
    % "\edgeheight" globally.
    edge={
      path picture={%
        \pgfpointdiff
          {\pgfpointanchor{path picture bounding box}{south west}}%
          {\pgfpointanchor{path picture bounding box}{north east}}%
        \pgfgetlastxy\measureedgex\measureedgey
        \global\let\edgewidth\measureedgex
        \global\let\edgeheight\measureedgey
      }
    },
    % Draw the tree fragment. The '-variant preserves the forest node boxes, so
    % these nodes can be later typeset for real.
    draw tree',
  }%
  \endgroup
}

% We define a pgfmath function which returns the "l" needed at the current node
% to make the edge label fit; if the edge is already long enough, the current
% "l" is returned.
% 
% This function assumes that "grow'=east": The l-dimension corresponds to the
% x-dimension and to edge "width"; the s-dimension corresponds to y and edge
% "height". For further info on Forest's ls-coordinate system, see manual 2.4
% 
% Hmm, this function does not require any arguments, but if I use {0} instead
% of {1} below, then using this function in a \pgfmathparse expression returns
% an empty value. Is this a bug in pgfmath?
\pgfmathdeclarefunction{l_for_my_edge_label}{1}{%
  \begingroup
  % \measureedge sets \edgewidth and \edgeheight
  \measureedge 
  % Stores the length of the edge to \pgfmathresult
  \pgfmathparse{sqrt(\edgewidth^2 + \edgeheight^2)}%
  \ifdim\forestoption{my edge label width}>\pgfmathresult pt\relax
    \pgfmathparse{
      l() + % increase the current "l"
      % by the difference between
      sqrt(my_edge_label_width()^2 - \edgeheight^2) % the required edge width
      - \edgewidth % and the current one
    }%
  \else
    % The edge was long enough already, just return "l". To understand the
    % conspiracy leading to the definition of \pgfmathl, see Forest manual 3.18
    % and PGF manual section 97.
    \pgfmathl{}%
  \fi
  \pgfmathsmuggle\pgfmathresult
  \endgroup
}

 % Set the format of the label node here.
\newcommand\mylabelformat[1]{\scriptsize#1}

% Just a temporary register
\newlength{\mylabelwidth}

\forestset{
  % We declare a new option which will store the width of the label.
  declare dimen={my edge label width}{0pt},
  % The key assigns the label width to option "my edge label width" (via the
  %   temporary \mylabelwidth), and delays the construction of the tikz code
  % which will produce the label until we know the relative position of the
  % node wrt the parent.
  my edge label/.style={
    TeX={\settowidth{\mylabelwidth}{\mylabelformat{#1}}},
    my edge label width/.expanded=\the\mylabelwidth,
    before computing xy={my edge label a={#1}},
  },
  % We use the "s" coordinate to determine
  % whether the label should appear above or below the edge. This is more
  % reliable than using the child number, but needs to be done after packing.
  % 
  my edge label a/.style={
  % Some fancy .processing: "if s<0pt then ##1=below else ##1=above"
    edge label/.process = O_<?_w   {s}{0pt}{below}{above}{
      node [midway, ##1, sloped, inner ysep=0.1em] {\mylabelformat{#1}}
    },
  },
  fit edge labels children/.style= {
    for tree={grow'=east},
    before computing xy={
      % \measureedge needs x and y to be set. It is better to compute them
      % here, once for the entire tree, than to compute them for each node ---
      % "compute xy" computes x and y for the entire subtree.
      compute xy,
      % For each branching node ...
      where n children=0{}{%
        % Walk the children to compute the maximum "l" required by their
        % edge labels.
        tempdima/.max={l_for_my_edge_label()}{children},
        % Set this maximum "l" for all children.
        for children={l/.register=tempdima},
      },
    },
  },
  fit edge labels level/.style= {
    for tree={grow'=east},
    % This is not terribly efficient ...
    before computing xy={
      % How many levels are there in the tree?
      tempcounta/.max={level}{tree},
      compute xy,
      % Loop through the levels (in reverse, because this is easier to
      %   implement; below, tempcounta is interpreted as the current level)
      do until={tempcounta()==0}{
        tempdima/.max={l_for_my_edge_label()}{level/.register=tempcounta},
        % "for level" takes two arguments, the level and the code;
        % .process instruction "R{tempcounta}" feeds it the level and leaves
        % the code alone.
        for level/.process=R{tempcounta}{l/.register=tempdima},
        tempcounta-=1,
      },
    },
  },
  fit edge labels tree/.style= {
    for tree={grow'=east},
    before computing xy={
      compute xy,
      tempdima/.max={l_for_my_edge_label()}{descendants},
      for descendants={l/.register=tempdima},
    },
  },
}

\begin{document}

\begin{forest}
  fit edge labels children, 
  % (1) The minimum distance between a node and its parent is determined by the
  % node's "l" and the parent's "l sep". "my edge label" can only increase this
  % distance. Case in point: edges from B to E and F are longer than the edge
  % label. To make them match precisely, set the relevant "l"s and "l sep"s to
  % 0, like here (uncomment to see the effect):
  % for tree={l=0,l sep=0}
  % 
  % (*) See below
  % for tree={parent anchor=east, child anchor=west},
  [A, label={west:children:}
    [B, my edge label={very long label},
      % (2) Manually adjust the dimension of the label to overcome the
      % remaining problem which can occur when the parent/child anchor is
      % empty, i.e. when it is the "automatic border" anchor.  To see the
      % problem, change "A" to something longer, like "AAAAAAA"; to fix it,
      % uncomment this:
      % 
      % my edge label={\makebox[\dimexpr\width+1em]{very long label}},
      % 
      % The problem occurs because depending on the dimensions and positions of
      % the nodes, either end of the parent-child edge can "move" to a
      % different point on the node border.  So, the problem cannot occur if we
      % use fixed parent and child anchors; for example, the problem cannot
      % arise if you uncomment (*) above.
      [E, my edge label={sh}]
      [F, my edge label={s}]
    ]
    [C, my edge label= {short}
      [G, my edge label= {short}]
      [H, my edge label= {a very long label again}]
    ]
  ]
\end{forest}

\begin{forest}
  fit edge labels level,
  [A, label={west:level:}
    [B, my edge label={very long label},
      [E, my edge label={sh}]
      [F, my edge label={s}]
    ]
    [C, my edge label= {short}
      [G, my edge label= {short}]
      [H, my edge label= {a very long label again}]
    ]
  ]
\end{forest}


\begin{forest}
  fit edge labels tree,
  [A, label={west:tree:}
    [B, my edge label={very long label},
      [E, my edge label={sh}]
      [F, my edge label={s}]
    ]
    [C, my edge label= {short}
      [G, my edge label= {short}]
      [H, my edge label= {a very long label again}]
    ]
  ]
\end{forest}


\end{document}

相关内容