具有功能级别的 Tikz 树形图分类

具有功能级别的 Tikz 树形图分类

我正在尝试使用 Tikz/forest 包为我的文献报告实现具有功能级别方案的以下树形图:

功能级别

我仍在学习如何使用这些软件包,但我被以下事情困扰:

  1. 如何让右侧的框上方显示文字
  2. 如何跳过实例/块,以便框中的块始终对齐(可能使用标签实现,如下所示:森林 - 水平树中的标记级别

我正在使用 Overleaf。任何帮助都非常感谢。提前谢谢您。

以下是我迄今为止尝试过的 MWE:

\documentclass{article}

\usepackage[edges]{forest}
\tikzset{%
    parent/.style =          {align=center,text width=3.5cm,rounded corners=3pt},
    child/.style =           {align=center,text width=2.5cm,rounded corners=3pt},
    grandchild/.style =      {align=center,text width=2cm,rounded corners=3pt},
    greatgrandchild/.style = {align=center,text width=1.5cm,rounded corners=3pt},
    referenceblock/.style =  {align=center,text width=1.5cm,rounded corners=2pt}
}

\begin{document}
    \begin{center}
            \begin{forest}
                for tree={
                    forked edges,
                    grow'=0,
                    draw,
                    rounded corners,
                    node options={align=center,},
                    text width=2.7cm,
                },
                [Parallel closing mechanisms, fill=gray!45, parent
                %
                    [Compliant mechanisms citation, for tree={fill=brown!45, child}
                        [Lumped, fill=brown!30, grandchild]
                        [Distributed, fill=brown!30, grandchild]
                        [Hybrid, fill=brown!30, grandchild
                            [MEMS, fill=brown!30, greatgrandchild
                                [citation, fill=brown!15, referenceblock]
                            ]
                        ]
                    ]
                    %
                    [Linkage mechanisms citation, for tree={fill=red!45,child}
                        [Reverse motion, fill=red!30, grandchild]
                        [Parallel motion, fill=red!30, grandchild]
                        [Crank and slider, fill=red!30, grandchild]
                        [Bell crank, fill=red!30, grandchild]
                        [Straight line, fill=red!30, grandchild]
                    ]
                    %
                    [Mechanical meta materials, for tree={fill=blue!45, child}
                        [Bending dominated, fill=blue!30, grandchild]
                        [Stretching dominated, fill=blue!30, grandchild]
                        [Origami/ kirigami inspired, fill=blue!30, grandchild]
                        [Auxetic (v$<$0), fill=blue!30, grandchild]
                    ]
                ]
            \end{forest}
    \end{center}
\end{document}

结果如下: MWE图像

编辑

根据评论的一些额外信息:

  1. 蓝色框中带有红色轮廓的引文块将包含不同的引文,以对论文进行分类。引文块可能包含以下内容:[1-4][7][9][12-13][18]
  2. 引用块必须与其父块对齐。如果引用块的高度超过父块的高度,则它们应该垂直对齐。连接父块和引用块的线用于帮助可视化这种对齐。这也可以是从左到右水平延伸的填充/背景颜色,但我认为前者更清晰。
  3. 区块的高度取决于区块内的文本量。如有必要,可以减小文本大小。我假设该图不会超过一页。

答案1

这是非常具有挑战性的。

实现细节在代码注释中给出,这里我仅概述问题和解决方案。

如果我们知道右侧框中的引文节点永远不会超过左侧树中相应类别节点的高度,情况就会简单得多。在这种情况下,我会选择在 TikZ 中“手动”创建引文节点。(y从类别节点获取坐标很容易;我们可以在 中做到这一点before drawing tree。)

由于情况并非如此,因此在 Forest 中创建所有节点是有意义的,因为这将确保高引用节点会导致相关类别节点扩散。但是,我们现在面临着一个“双树”问题。“文件夹”类别节点(如OP 帖子中的HybridMEMS)有两种子节点:“文件”(分别为MEMS、 和citationcit)和一些引用节点。但folderForestedges图书馆的风格是全部子级...这里的想法是暂时将引用节点从文件夹父级移至虚拟父级,打包文件夹节点,将引用节点单独打包到虚拟父级下,然后将引用节点移回文件夹。(两个实施细节。我们实际上并不使用库folder中的样式edges,而是使用代码中包含的更新版本。样式的开发还需要“do dynamics”补丁。)

由于使用 Forest 的对齐方式对齐引文框中的节点是有意义的tier,我们使用此选项的值来确定节点是类别节点还是引文节点。因此,通过具有形式为 的子节点来指定引文[42, tier=1]

该算法产生了非常灵活的代码。甚至可以将类别节点与同一层上的多个引文相关联。我还将一些随机的好东西放入解决方案中,例如引文的自动格式化、对任意数量的类别级别的支持、一种为类别树的各个部分着色的简便方法。此外,我还尝试将逻辑和格式分开。因此,下面的第一个代码块(第一个\forestset)完全是关于逻辑的;视觉定制是在第二个\forestset块中完成的。应用样式本身就像放入categorized citations={A,B,C}树序言一样简单 --- 当然,还要指定引文tier=1/2/3并应用任何特定于树的格式。

阅读我的实施说明可能并不适合所有人,但为了使用该风格,至少请阅读树中的说明,也许还请阅读自定义块中的说明。

\documentclass{standalone}
\usepackage[edges]{forest}

\forestset{
  % This is the style that should be applied in the tree preamble. See the tree
  % for what argument it takes.
  categorized citations/.style={
    delay={
      for tree={
        % The assumption is that all the nodes without a "tier" option belong
        % to the tree on the left. The names of the tiers that citation nodes
        % live on will end up to be consecutive numbers.
        if tier={}{
          % These styles can be adjusted by the user.
          common node options,
          tree node options,
        }{% We assume that anything on a tier is a citation node.
          citation format,
          % Split the content of the node (separator is ",") and format the
          % parts --- if desired.
          if citation node autoformat={
            split option={content}{,}{format first citation entry, format citation entry},
          }{},
          % The tier situation is atypical in these trees. There are multiple
          % tiers, but there is no hierarchical relation between them --- all
          % nodes on tiers are just children of the nodes in the tree.  So we
          % need to give forest a nudge to get tiers right. This has two parts.
          % 
          % Tiers, part 1. Here we solve the problem of a node in the
          % tree with several tier nodes (i.e. citations).  The citation nodes
          % are siblings, so forest will push them apart in the s (y) dimension
          % --- this is so because tier alignment happens later.  But if we
          % make the l (x) coordinates of the nodes different enough, the nodes
          % will not "bump" and will happily end up with the same s (y) coordinate.
          % We try to make them minimally different (and thus small), so that
          % we don't accidentally push them too much to the right.
          l=\forestregister{citation boxes offset}+(tier()-1)*(\forestregister{citation node text width}+2*\pgfkeysvalueof{/pgf/inner xsep}+2*\pgfkeysvalueof{/pgf/outer xsep}+0.1pt),
        },
      },
      % Tiers, part 2.
      % Here we tell forest about the relative order of tiers (and thus boxes
      % on the right). We need to do this because when we write down the tree,
      % we just put the citation nodes next to each other (as siblings).  The
      % idea here is to create a dummy node on each tier, with structure
      % [1[2[3...]]], and remove them all once they do their job. This "tier
      % header" is created automatically from the argument to "folders with
      % citation blocks" style.
      temptoksa={}, % This will hold the argument to "prepend".
      % This will hold a call of "draw tier box" for each tier box (in reverse
      % order):
      draw tier boxes/.style={},
      draw content of tier boxes/.style={},
      tempcounta'=0, % The current tier number.
      temptoksb={}, % The closing brackets to append to temptoksa.
      split={#1}{,}{add first citation tier, add citation tier},
      % Insert the closing brackets.
      temptoksa+/.register=temptoksb,
      % The tier header specification is constructed, let's put it in the tree!
      prepend/.register=temptoksa,
    },
    % After all nodes are positioned, we define style "draw tier box" which
    % draws a citation box and its contents.
    before drawing tree={% #1 = tier name
      % Remember the topmost and the bottommost position in the entire tree
      % (folders and citations):
      tempdimya/.max={y()+max_y()}{tree},
      tempdimyb/.min={y()+min_y()}{tree},
      % The first argument of this style is a tier name; in the code, it is
      % referred to by ####1, due to being embedded in .process
      draw tier box/.style/.process=R2w2{tempdimya}{tempdimyb}{
        % ##1, ##2 = tempdimya, tempdimyb
        % Get the leftmost and the right position in the citation box (i.e the
        % given tier):
        tempdimxa/.max={x()+max_x()}{filter={tree}{strequal(tier(),"####1")}},
        tempdimxb/.min={x()+min_x()}{filter={tree}{strequal(tier(),"####1")}},
        % "draw tier box" style is called from the "draw tree method", so we
        % just draw the citation box directly.
        TeX/.process=R2ORw4
          {tempdimxa}{tempdimxb}{####1.content}{citation box label position}{
          % ########1, ########2 = tempdimxa, tempdimxb
          % ########3 = the content of the dummy tier node
          % ########4 = "citation box label position" register
          \node[
            fit={(########1,##1) (########2,##2)},
            citation box options,
            % The box is labeled by the content of the dummy tier header node.
            label={[citation box label options]########4:########3}]{};
        },
        % Draw the citation nodes of this box (i.e. on this tier), and their edges.
        % 
        % Option 1. Edges go below the citation box. (Also see "citation node options".)
        % for filter={tree}{strequal(tier(),"####1")}{draw tree node, draw tree edge},
      },
      draw content of tier box/.style/.process=R2w2{tempdimya}{tempdimyb}{
        % Option 2: Edges go below the citation nodes but above the citation box:
        for filter={tree}{strequal(tier(),"####1")}{draw tree node, draw tree edge},
      },
    },
    % We need to draw stuff in a very particular order, so that the edges from
    % citation nodes on tier 2 go behind citation box 1, etc.
    draw tree method/.style={
      % Draw the tree on the left (nodes and edges).
      for filter={tree}{strequal(tier(),"")}{draw tree node, draw tree edge},
      % Draw the citation boxes (and their contents) --- in reverse order!
      draw tier boxes,
      draw content of tier boxes,
      % Finally, draw any decorations.
      for tree=draw tree tikz,
    },
  },
  % How should the nodes in the blocks on the right look like?
  citation format/.style={
    % Don't change "text width" directly. We need to remember it for later (see
    % Tiers, part 1), so use "citation node text width" register.
    text width/.register=citation node text width,
    % Adjust these styles for other options.
    common node options,
    citation node options,
  },
  % We use these two styles when splitting the citation box specification (the
  % argument of "categorized citations").
  add first citation tier/.style={
    add citation tier={#1},
    % Get rid of the dummy citation nodes after they have done their job.
    temptoksa+={, before computing xy=remove},
  },
  add citation tier/.style={
    tempcounta'+=1, % Increase the tier number.
    % Add the dummy citation specification. We put it on tier 1/2/3..., give it
    % the same name (just in case we want to format them independently), and
    % apply the standard citation node format.
    temptoksa+/.process=Rw{tempcounta}{[#1, tier=##1, name=##1, citation format%]
      },
    % Well need an extra closing bracket at the end:
    temptoksb+=%[
    ],
    % Prepend the command to draw this particular citation box to "draw tier
    % boxes".  We *pre*pend because it is crucial that citation boxes are drawn
    % in reverse order: the idea is that a citation box gets drawn on top of
    % any edges coming from citation boxes further away from the tree; in
    % effect, the edges to citations will go below intermediate citation boxes.
    draw tier boxes/.prefix style/.process=Rw{tempcounta}{draw tier box=##1},
    draw content of tier boxes/.prefix style/.process=Rw{tempcounta}{draw content of tier box=##1},
  },
  % An update to the "folder" style from the "edges" library. It (or something
  % like it) will be included in the next release of forest.
  folder v2/.style={
    calign=child,
    calign primary child=1,
    % This is necessary for "tempdims" calculation below to work properly.
    anchor=parent,
    after packing node={
      if n children=0{}{
        tempdiml=l_sep()-l("!1"),  % l-shift
        tempdims={abs(max_s("",""))+abs(min_s("!1",""))+s_sep()}, % s-shift
        for children={
          l+=tempdiml,
          s+=tempdims()*(0.5-reversed())*2,
          edge={rotate/.option=!parent.grow},
          % We don't use the values of "parent anchor" and "child anchor" here
          % (we use ".-children last" and ".parent" directly), because the user
          % might want to use those otherwise if the folder is embedded in a
          % larger tree.
          edge path'/.expanded={
            ([xshift=\forestregister{folder indent}]!u.-children last) |- (.parent)
          },
        },
        fit=band, % to avoid overlapping nodes with their (greatgreat...)uncles.
      },
    },
  },
  % In "folder with cites", we separate category and citation nodes, and pack
  % them separately. So this style really does all the work in "before/after
  % packing node": separate category and citation nodes just before packing, and
  % put them back together after packing.
  % 
  % This is just a temporary node we'll need to store a copy of the parent
  % folder node into. We need it because somebody didn't implement "create" to
  % accept a relative node name and thus act as a copying operation. To be done.-
  create'={[,name=folder@temp]},
  folder with cites/.style={
    before packing node={
      % Make a copy of the parent folder node (without the subtree).
      for name/.process=_Ow{folder@temp}{id}{
        append''={!{id=##1}},
      },
      % Here I don't follow my own advice (from the forest manual) and call the
      % experimental "do dynamics" from within "process keylist(')" (well, not
      % explicitly, effectively so, because "before/after packing node" are
      % internally called as if by "process keylist'"). The bad news is that
      % "do dynamics" in fact does not work as expected here ...
      do dynamics,
      % ... but the good news is that I now know at least one thing that is
      % wrong with it: it fails to set "last dynamic node". This will be
      % investigated ... until then, a workaround:
      for group={name=folder@temp,last}{alias=folder@temp@parent},
      % In fact, there was another problem with "do dynamics". It did not
      % update all the node options containing the hierarchical information
      % about the tree. So this key now grew (the new definition below; to be
      % included in the next release of forest) an argument: a relative node
      % name instructing it which nodes to update after doing the dynamic
      % thing. (Note that we can safely use "do dynamics" twice; no dynamic
      % operations are performed the second time, as the dynamic queue is
      % empty, but the info will get updated.)
      do dynamics=folder@temp@parent,
      % Move all citation nodes (i.e. a nodes with non-empty tier option) into
      % the copy of the parent.
      for children={
        if tier={}{}{
          for name/.process=_Ow{folder@temp@parent}{id}{
            append={!{id=##1}},
          },
        },
      },
      % Do the dynamic operations immediately --- ahh yes, we must do this
      % because we're in the middle of packing a node!
      do dynamics, do dynamics=folder@temp@parent,
    },
    % Apply the (updated) folder style to whatever children remained in the
    % original parent. 
    folder v2,
    after packing node={
      alias=folder@temp@current,
      for name={folder@temp@parent}{
        for children={
          % We do this because gdjgfjdfgsdj ... remove the line and see what
          % happens to the tall adjacent citations on tier 2 (children of
          % "Crank and slider" and "Bell crank").
          l+={abs(max_l("",""))+abs(min_l("!u",""))+l_sep("!u")},
        },
        % calign/.register=citation nodes calign,
        % How should we calign the citation nodes after packing them?
        citation nodes calign,
        % Pack the citation nodes within the copy of the parent ...
        pack',
        % ... and then put them back into the original parent. It does not
        % really matter where to put them, so we just append.
        for children={
          for name/.process=_Ow{folder@temp@current}{id}{
            append={!{id=##1}},
          },
        },
      },
      % For the final time ...
      do dynamics,
    },
  },
  declare dimen register=citation node text width,
  declare dimen register=citation boxes offset,
  declare boolean register=citation node autoformat,
  declare toks register=citation box label position,
}

\makeatletter
\forestset{
  % Let's patch up "do dynamics" --- to be included in the next release of forest.
  do dynamics/.code={%
    \the\forest@do@dynamics
    \forest@do@dynamics{}%
    \forest@forthis{%
      \forest@nameandgo{#1}%
      \forest@node@Compute@numeric@ts@info{\forest@cn}%
    }%
  },
  do dynamics/.default=!{root'},
}
\makeatother


% These are the formatting options and should be (reasonably) safe to adjust.
\forestset{
  common node options/.style={
    % "grow" will only work for 0 and 180. For other directions, the "draw tier
    % box" style would need to be generalized.
    grow'=0,
  },
  % This style is applied to all the category nodes, i.e. the nodes on the
  % left.
  tree node options/.style={
    % Some basic stuff ...
    draw, /tikz/align=center,
    % For single citation nodes connected to a tree nodes, it even works
    % without this (assuming that the default parent anchor is center). But for
    % the fancy "forked edge" calignment of citation nodes, this is necessary.
    parent anchor=children,
    % The shift of the parent anchor for the folder node (a register applying
    % to all folder nodes):
    folder indent=1em,
    % Nodes on levels 0, 1 and 2 are drawn with "forked edge"s.
    % Nodes on levels 2, 3, ... are folders. There is no limit on the
    % number of levels.
    % 
    % Feel free to change the level where the transition from forked edge to
    % folders occurs, or even to mix them. Note that key "forked edge" sets the
    % type of the edge towards the parent node; it must be set for each child.  Key
    % "folder" (either the original, v2, or "folder with cites"), on the other hand,
    % influences the positions and edges of its children; it must be only used on
    % the parent node.
    % 
    % We also set text widths, opacities etc. for each level here. The
    % color is set in the tree itself.
    if level=0{
      fill opacity=0.45, text width=3.5cm, rounded corners=3pt,
    }{if level=1{
        fill opacity=0.45, text width=2.5cm, rounded corners=3pt,
        forked edge,
      }{
        if level=2{
          fill opacity=0.30, text width=2.0cm, rounded corners=3pt,
          forked edge,
          % The "l sep" (here and below) influences both the folder--file
          % distance and the distance to citation nodes.
          folder with cites, l sep+=1em,
        }{
          if level=3{
            fill opacity=0.30, text width=1.5cm, rounded corners=2pt,
            folder with cites, l sep+=1em,
          }{% level >= 4
            fill opacity=0.15, text width=1.5cm, rounded corners=2pt,
            folder with cites, l sep+=1em,
          },
        },
      },
    },
  },
  % The width of the citation nodes.
  citation node text width=1.5cm,
  % Other options applying to citation nodes (don't change "text width" here!):
  citation node options/.style={
    draw, /tikz/align=center, rounded corners=2pt,
    % Option 1: Edges go below the citation box: (Also see "draw (content of) tier box".)
    % fill=brown, fill opacity=0.6,
    % Option 2: Edges go below the citation nodes but above the citation box:
    fill=brown!60, 
    % This sets the separation between the citation boxes:
    l sep=2em,
    % The "anchor" key specifies center vertical alignment to the parent.  The
    % "child anchor" says that's where the edge will start too. We
    % most probably want to keep these as they are.
    anchor=parent, child anchor=parent,
  },
  % How shall we align citations in the unlikely case that we have more than
  % one citation node belonging to a category node on a single tier (see the blue
  % "citation" in the example tree)? Let's have a very fancy setup, center
  % caligned with forked edges, by default.
  citation nodes calign/.style={
    calign=center, forked edges, for children={fork sep=2em},
  },
  % We can push the citation boxes a bit further away from the tree. By
  % default, the "l sep" of the parents of citations nodes is in effect.
  citation boxes offset=0em,
  % How should a citation box look like? Note that we want "fill opacity=1"
  % (the default) here, because we want the citation box to partially hide the
  % edges from citations to the folders.
  /tikz/citation box options/.style={
    fill=blue!20, draw=red, thick,
  },
  % Format the citation box labels:
  /tikz/citation box label options/.style={
  },
  % Where should the citation box labels appear?
  citation box label position=north,
  % These two keys are used to automatically format the list of references in a
  % citation node.
  format first citation entry/.style={content'=\mbox{[#1]}},
  format citation entry/.style={content+'=\discretionary{}{}{}\mbox{[#1]}},
  % A register saying whether we want to autoformat citations:
  citation node autoformat=true,
}

\begin{document}
\begin{forest}
  % Style "categorized citations" takes an argument specifying the
  % (comma-separated) labels of citation boxes.  Each citation block will reside
  % on its own tier, the tiers numbered 1,2,3... So to put a citation into
  % block A/B/C, write "tier=1/2/3" into the citation node.  The number of
  % labels given here must match the number of tiers used below, i.e. an empty
  % citation box will lead to an error.  If you list too few labels here, the
  % extra tiers will be ignored (without producing an error).
  categorized citations={A,B,C}
  [Parallel closing mechanisms, fill=gray
    [Compliant mechanisms citation, for tree={fill=brown}
      [Lumped
        % Specifying "tier=n" tells forest to put the node into the nth
        % citation block.
        [42, tier=1]
        [24, tier=2]
      ]
      [Distributed
        % The citations are auto-formatted, see "citation node autoformat" and
        % "format (first) citation entry" above.
        [{1--4,7,9,12--13,18}, tier=1]
      ]
      [Hybrid
        [MEMS
          % The citation nodes can be freely mixed with files.
          [citation
            [1,tier=1]
            [3,tier=3]
          ]
          [11,tier=1]
          [cit]
          [22,tier=2]
        ]
      ]
    ]
    % 
    [Linkage mechanisms citation,
      % An easy way to set the color of the entire subtree. The opacity is set,
      % per-level, in "tree node options".
      for tree={fill=red}, 
      [Reverse motion]
      [Parallel motion[dummy]]
      [Crank and slider
        [1,tier=1]
        [{22,33,44,55,66,77},tier=2]
        [3,tier=3]
      ]
      [Bell crank
        % We put two very tall nodes together here, and everything still works!
        [{11--13,15--17,20,22,101--105},tier=2]
      ]
      [Straight line]
    ]
    % 
    [Mechanical meta materials, for tree={fill=blue}
      [Bending dominated
        [cit]
        [citation
          % Yiihaa, we can even have multiple citation nodes per tree node! And
          % we can even control how they are caligned ("citation nodes calign").
          [1,tier=1]
          [4,tier=2]
          [5,tier=2]
          [3,tier=1]
          [1,tier=1]
        ]
        [cit
          [1,tier=1]
        ]
      ]
      [Stretching dominated
        % Any number of folder levels is supported.
        [folder 1
          [folder 2
            [folder 3
              [folder 4
                [folder 5
                  % Node "folder 5" sticks out the most. So if we remove this
                  % citation, something perhaps unexpected happens. The
                  % citation boxes will overlap the tree! This happens because
                  % citation nodes are pushed away by "l sep"s of their
                  % parents. Manual adjustment of the separation between the
                  % tree and the citation boxes is possible using "citation
                  % boxes offset".
                  [24--42,tier=1]
                ]
              ]
            ]
          ]
        ]
      ]
      [Origami/ kirigami inspired]
      [Auxetic (v$<$0)]
    ]
  ]
\end{forest}
\end{document}

编辑:为引用边缘实现了不同的 z 顺序。注释指示如何在两个 z 顺序之间切换。

一个例子

答案2

我发布了一个新答案(这是 OP 在评论中提出的要求,但还有很多其他更改),因为在之前答案的基础上添加内容会使答案太长,而且我觉得不应该彻底改变已接受的答案。事实上,代码太长了,我不得不压缩它,所以我在这里就不多说了。

\documentclass{standalone}
\usepackage[edges]{forest}
\forestset{
 % This is the style that should be applied in the tree preamble. The problem
 % of citation nodes is that we effectively want to maintain two related
 % trees: the category tree and a tier-aligned citation tree. The idea
 % behind the implementation is the following: while packing a node of the
 % category tree, take the citation node children out and pack them
 % separately, pack the category node, put the citation nodes back in.
 categorized citations/.style={
  % #1 = citation box specification; see the example for details. As the spec
  % is a keylist, we parse it by processing it by pgfkeys in "/citation
  % tiers" path.  This code does three things (assume we are given spec
  % "1=A,2=B,3=C"):
  % 
  % 1. An input style (actually, two) is defined for each box. For the
  % example spec, we'll get styles "1", "2" and "3" (and "1'", "2'",
  % "3'"). When applied (by the user), this style creates a citation node as
  % a child of the category node residing on the appropriate tier.
  % 
  % 2. After the nodes are typeset, we create a "tier header": a chain of
  % auxiliary nodes containing the widest citation from each box. So if node
  % "citation 1/2/3" is the widest citation in citation box 1/2/3, the
  % tier header looks like this: % [citation 1 [citation 2 [citation 3]]].
  % These nodes are crucial for tier alignment; they are how we order the
  % tiers and thus the citation boxes.
  % 
  % 3. A dummy parent node is created for each tier/box. Just before a
  % category node is packed, its citation nodes are moved away to these
  % parent nodes (and packed there, nodes on each tier separately); after the
  % category node is packed, we bring the citation nodes back in.
  % 
  tempcounta'=0, % Initialize the box/tier counter.
  TeX={\pgfqkeys{/citation tiers}{#1}}, % Process the argument.
  % 
  % The "/citation tiers" code creates the tier header as a part of "typeset
  % nodes stage", after the actual typesetting (of course, as we need to know
  % the citation node widths). So we have to typeset the tier header
  % separately. It resides in the last child of the current node.
  before packing={for nodewalk={fake=last,tree}{typeset node}},
  % After packing, we can get rid of the tier header.
  before computing xy={for nodewalk={last,remove}{}},
  % 
  % We need to draw stuff in a very particular order, so that the edges from
  % citation nodes on tier 2 go behind citation box 1, etc.  For details on
  % "draw tree method", see manual 3.4.3
  draw tree method/.style={
   % Draw the category tree (nodes and edges).
   for filter={tree}{!citation()}{draw tree node, draw tree edge},
   % Draw the citation boxes (and their contents).
   draw tier boxes,
   draw content of tier boxes,
   % Finally, draw any decorations.
   for tree=draw tree tikz,
  }
 },
 % The "categorized citations" argument processor.
 /citation tiers/.unknown/.code={%
  \let\citationtier\pgfkeyscurrentname
  \forestset{
   % Each part of the argument is a "box id=label".  "Box id" = \citationtier,
   % "label" = #1.  "add citation tier" will also need the current box ordinal
   % number (tempcounta) and the id of the root of the category tree (as
   % this is the current node, that's simply the value of "id").
   add citation tier/.process=x_RO{\citationtier}{#1}{tempcounta}{id},
   tempcounta'+=1, % Increment the box counter.
  }
 },
 % Setup stuff related to a particular citation box.
 add citation tier/.style n args=4{%
  % #1 = tier name, #2 = label, #3 = tier/box number, #4 = category subtree root id
  % 
  % Define the input styles. The only difference between #1 and #1' version
  % is that the content (##1) is braced in the former.  So ##1 will surely
  % provide only node content in #1, but can also provide node options in #1'.
  #1/.style={
   % The citation node is created as a child of the calling category
   % node. (It does not matter which child.)
   append={[{##1},
     % Calling "1=citation" will put "citation" on tier 1.
     tier=#1,
     % Ensure uniform tree growth direction (for tier alignment):
     grow/.option=!u.grow,
     % Mark as a citation node (we use this in "folder with cites").
     citation,
     citation node options % Apply user customization.
    ]
   }
  },
  #1'/.style={append={[##1,
     tier=#1, grow/.option=!u.grow, citation, citation node options,]}},
  % After we have typeset all the nodes, we can create the tier header
  % nodes. We dump all the calls of "create tier header node" into style
  % "typeset nodes stage", for two reasons. First, "create tier header node"
  % performs a dynamic operation, which is presumably safer to do when not
  % processing a keylist (such are "before ..."). Second, this must be done
  % before packing, because that's when we need the tier header.
  typeset nodes stage/.append style/.process=Rw{tempcounta}{
   create tier header node={#1}{#4}{##1}
  },
  % Prepend the command to draw this particular citation box to "draw tier
  % boxes".  We *pre*pend because it is crucial that citation boxes are drawn
  % in reverse order: the idea is that a citation box gets drawn on top of
  % any edges coming from citation boxes further away from the tree; in
  % effect, the edges to citations will go below intermediate citation boxes.
  draw tier boxes/.prefix style={draw tier box={#1}{#4}{#2}},
  draw content of tier boxes/.prefix style={draw content of tier box={#1}},
  % Create the dummy temporary parent (#1@temp) for citation nodes of this
  % tier (#1) --- use the bar version of "create" to set the name immediately
  % --- and append it to "citations@temp" (see below).
  for name={citations@temp}{%
   create'={[,name'={#1@temp}]}, append
  },
 },
 % This is just an auxiliary disociated node (i.e. a node outside the
 % hierarchy of the typeset tree) where the dummy parents (one for each
 % tier/box) are stored.  We could do without it, but it's helpful because we
 % can call "do dynamics" (in "folder with cites") on this node to process all
 % the dummy parents at once.
 create'={[,name=citations@temp]},
 % Here we find the widest citation node in the given box, and copy it into
 % the tier header.
 create tier header node/.style n args=3{
  % #1 = tier name
  % #2 = the root of the category (sub)tree
  % #3 = embedding level
  % 
  % This is the function computing the width:
  sort by={max_x()-min_x()},
  for nodewalk={
   % Visit all the nodes rooted in the category subtree. Filter out anything
   % not residing on the current tier. Step on the first node with the
   % maximal width (as specified by "sort by").
   max={filter={id=#2,descendants}{strequal(tier(),"#1")}}
  }{
   for group/.process=_ Ow
   % Move the node we have stepped on into the tier header. The tier header
   % starts at the last child of the category tree root.  The second node of
   % the header is the child of the first node; the third is the child of
   % the second, etc.  We are currently at the category tree root. So to get
   % to the final existing node of the header, we step n times to the last
   % child, where n is the ordinal number of the tier box - 1 (above, the
   % "tempcounta" counter, here #3).
    {id=#2,repeat={#3}{last}}
   % Grargh, forest should have an "append to" key! Well, this is how it
   % will be implemented:
    {id}{append''={!{id={##1}}}},
  },
  % Crucially, perform the dynamic operation right away. The next tier header
  % node we will create will be the child of the one we're creating right now!
  do dynamics,
 },
 % This style draws a citation box and its contents.
 draw tier box/.style n args=3{
  % #1 = citation box id
  % #2 = category tree root id
  % #3 = citation box label
  for id={#2}{% go to the root of the category tree
   % Get the top and bottom of the category tree and the citation box:
   tempdimya/.max={y()+max_y()}{tree},
   tempdimyb/.min={y()+min_y()}{tree},
   % Get the leftmost and the rightmost position in the citation box (i.e the
   % given tier):
   tempdimxa/.max={x()+max_x()}{filter={tree}{strequal(tier(),"#1")}},
   tempdimxb/.min={x()+min_x()}{filter={tree}{strequal(tier(),"#1")}},
   % "draw tier box" style is called from the "draw tree method", so we
   % just draw the citation box directly.
   TeX/.process=R4Rw5
   {tempdimxa}{tempdimya}{tempdimxb}{tempdimyb}{citation box label position}{
    % ##1 -- ##4 = tempdimxa, tempdimya, tempdimxb, tempdimyb
    % ##5 = "citation box label position" register
    \node[fit={(##1,##2) (##3,##4)}, citation box options,
     % The box is labeled by the content of the dummy tier header node.
     label={[citation box label options]##5:#3}
    ]{};
   },
  }
 },
 draw content of tier box/.style={
  % Draw the citation nodes of this box (i.e. on this tier), and their edges.
  for filter={tree}{strequal(tier(),"#1")}{draw tree node, draw tree edge},
 },
 % An update to the "folder" style from the "edges" library. It (or something
 % like it) will be included in the next release of forest.  The idea is to
 % pack the children normally (caligned to the first child) and then move them
 % into the appropriate position.
 folder v2/.style={
  % The following three parameters can be set immediately ...
  % Calign to the first child.
  calign=child,
  calign primary child=1,
  % This is necessary for "tempdims" calculation below to work properly.
  anchor=parent,
  % ... but the bulk of the work is done just after the node is packed.
  after packing node={
   % No children, no work.
   if n children=0{}{
    % Just to speed up things ...
    tempdiml=l_sep(),
    % s-shift =   the part of the child between the parent and the child
    % + "s sep"
    % + the part of the parent between the parent and the child
    % (Hmm, will this work in general? I have to double-check what
    % "max/min_s/l" does.)
    tempdims={abs(max_s("",""))+s_sep()+abs(min_s("!1",""))},
    for children={
     % The "l" of a child is fixed to the parent's "l sep".
     l'/.register=tempdiml,
     % The shift direction depends on the direction of the children.
     s+=tempdims()*(0.5-reversed())*2,
     edge={rotate/.option=!parent.grow},
     % We don't use the values of "parent anchor" and "child anchor" here
     % (we use ".-children last" and ".parent" directly), because the user
     % might want to use those otherwise if the folder is embedded in a
     % larger tree.
     edge path'/.expanded={
      ([xshift=\forestregister{folder indent}]!u.-children last) |- (.parent)
     },
    },
    fit=band, % to avoid overlapping nodes with their (greatgreat...)uncles.
   }
  }
 },
 % This is really the heart of the algorithm.  In "folder with cites", we
 % separate category and citation nodes, and pack them separately. So this
 % style really does all the work in "before/after packing node": separate
 % category and citation nodes just before packing, and put them back together
 % after packing.
 folder with cites/.style={
  before packing node={
   % Move all citation node children into the dummy parent (see the end of
   % "add citation tier" def).
   for children={% Visiting the "files" (the children of the folder)
    if citation={% but only if they are citation nodes
     % Umm.
     % 1. "O" gives us the "tier" name of the current node.
     % 2. "w" sticks this tier name in place of "##1" in "##1@temp". The
     % result becomes the first argument of "for name".
     % 3. "O" gives us the "id" of the node (a "file" in the folder).
     % 4. "w" sticks this id into the code as "##1".
     for name/.process=OwOw  {tier}{##1@temp}{id}{
      % We're now inside node <tiername>@temp
      % Move the citation node here.
      append={!{id=##1}},
      % Inherit the growth direction from the folder (the citation node
      % should have the same grow, but just in case.)
      grow/.option=!{id=##1,parent}.grow,
      reversed/.option=!{id=##1,parent}.reversed,
     },
    }{},
   },
   % We have to trigger the dynamic operations manually, because we need
   % the result immediately (we can't wait for a delay cycle) --- remember
   % we're in the middle of packing a node! (See the previous answer for
   % discussion on "do dynamics").
   do dynamics, do dynamics=citations@temp,
  },
  % Apply the (updated) folder style to whatever children remained in the
  % original parent.
  folder v2,
  % After the folder is packed, it's time to bring the kids back home.
  after packing node={
   % If all is right here, the <tiername>@temp nodes were empty of children
   % prior to "before packing node". So the current node's (temporarily
   % absent) children are all the nodes currently residing there. So we can
   % simply loop through the entire "citations@temp" to visit all the kids.
   % 
   % Below, we will refer to the current node's id by "##1".
   for name/.process=_Ow {citations@temp}{id}{
    for children={% loop through the tiers/boxes
     % But before we bring the children back, we will pack them.  And the
     % trick --- and one of the crucial improvements over the previous
     % version of "categorized citations" --- is that we will pack the
     % children of each tier separately!  This avoids some very strange
     % "l" computations from that version. See, our citation nodes from
     % different boxes should not interact in the "s" dimension at all,
     % but if we pack them all together (as in the previous version), they
     % potentially do.  And the "l" dimension is not a problem, as this is
     % handled by tier alignment while packing nodes further up the tree.
     for children={% loop through the moved children on this tier
      % We are packing the children under a dummy node, so we need to
      % set the l dimension manually. We want the children to be at least
      % "s sep" away from the parent, so (the same calculation as for
      % "tempdims" in "folder (v2)"):
      l={abs(max_l("",""))+l_sep("!u")+abs(min_l("!u",""))},
     },
     % How should we calign the citation nodes after packing them?
     citation nodes calign,
     % Pack (within the dummy parent) the citation nodes of a single
     % category node and a single citation box ...
     pack',
     % ... and then put them back into the original parent. It does not
     % really matter where to put them, so we just append.
     for children={
      for id/.process=_Ow{##1}{id}{
       append={!{id=####1}},
      },
     },
    },
   },
   % I'm not even sure we need to do this, but just in case.
   do dynamics, do dynamics=citations@temp,
  },
 },
 % This style will get filled by calls to "draw tier box". It does not get
 % emptied at the start of "categorized citations" to support multiple
 % category trees in a single forest tree --- so we rely on the forest
 % environment being a group.
 draw tier boxes/.style={},
 draw content of tier boxes/.style={},
 % As the same says ...
 declare toks register=citation box label position,
 % This is set to true for citation nodes (upon invocation of an input style).
 declare boolean={citation}{false},
}
\makeatletter
\forestset{
 % Let's patch up "do dynamics" --- to be included in the next release of forest.
 do dynamics/.code={%
  \the\forest@do@dynamics
  \forest@do@dynamics{}%
  \forest@forthis{%
   \forest@nameandgo{#1}%
   \forest@node@Compute@numeric@ts@info{\forest@cn}%
  }%
 },
 do dynamics/.default=!{root'},
}
\makeatother
% This is it, as far as the logic goes. Now the formatting.
\forestset{
 % A very simple generic style to superimpose two identical SIBLINGS. The user
 % must make sure that the siblings are of the same size.
 identify with/.style={% #1 = the relative node name of the sibling to identify with
  % Hide the current node
  opacity=0, text opacity=0,
  % And move it to the position of the given sibling.
  % 1. Movement can be done by setting "s" and "l" because they are siblings
  % ("s" and "l" are coordinates relative to the parent and its growth
  % direction).
  % 2.This must be done after "s" and "l" are set (pack stage) but before
  % computing the paper coordinates. So:
  before computing xy={
   s/.option=#1.s,
   l/.option=#1.l,
  },
 },
}
% These are the formatting options and should be (reasonably) safe to adjust.
\forestset{
 % This style should be applied (manually) to the root of the category
 % tree. So the definition will use "for tree" etc. to properly format all the
 % nodes in the category (sub)tree. Note that the logic of "categorized
 % citations" does not depend on this style --- with the exception of
 % expecting either "for tree={grow'=0}" or "for tree={grow=180}".
 category tree/.style={
  for tree={
   % Some basic stuff ...
   grow'=0, draw, /tikz/align=center,
   % For single citation nodes connected to a tree nodes, it even works
   % without this (assuming that the default parent anchor is center). But for
   % the fancy "forked edge" calignment of citation nodes, this is necessary.
   parent anchor=children,
  },
  % The shift of the parent anchor for the folder node (a register applying
  % to all folder nodes):
  folder indent=1em,
  % Nodes on levels 0, 1 and 2 are drawn with "forked edge"s.
  % Nodes on levels 2, 3, ... are folders. There is no limit on the
  % number of levels.
  % 
  % Feel free to change the level where the transition from forked edge to
  % folders occurs, or even to mix them. Note that key "forked edge" sets the
  % type of the edge towards the parent node; it must be set for each child.  Key
  % "folder" (either the original, v2, or "folder with cites"), on the other hand,
  % influences the positions and edges of its children; it must be only used on
  % the parent node.
  % 
  % We also set text widths, opacities etc. for each level here. The
  % color is set in the tree itself.
  % 
  % We use "relative level", because the root of the category tree might not
  % be the absolute root of the forest tree.
  for relative level=0{
   fill opacity=0.45, text width=3.5cm, rounded corners=3pt},
  for relative level=1{
   fill opacity=0.45, text width=2.5cm, rounded corners=3pt,
   forked edge},
  for relative level=2{
   fill opacity=0.30, text width=2.0cm, rounded corners=3pt,
   forked edge,
   % The "l sep" (here and below) influences both the folder--file
   % distance and the distance to citation nodes.
   folder with cites, l sep+=1em},
  for relative level=3{
   fill opacity=0.30, text width=1.5cm, rounded corners=2pt,
   folder with cites, l sep+=1em},
  for relative level>=4{
   fill opacity=0.15, text width=1.5cm, rounded corners=2pt,
   folder with cites, l sep+=1em},
 },
 % This style is automatically applied to every citation node.  Again: it is
 % applied to each citation node *separately*.  So don't use "for tree" and
 % such in here.
 citation node options/.style={
  draw, /tikz/align=center, rounded corners=2pt,
  fill=brown!60,
  % This sets the separation between the citation boxes:
  l sep=2em,
  % (How so? The citation nodes get copied into the auxiliary "tier header"
  % nodes, along with their "l sep" value. These nodes form a chain of
  % embedded nodes, [1[2[3]]], so setting "l sep" on them pushes them apart
  % in "l" (x") dimension. And because they live on the same tiers as the
  % real citation nodes, those feel this "l sep" as well.)
  % 
  % The "anchor=parent" key specifies center vertical alignment to the
  % parent.  The "child anchor=paren" says that's where the edge will start
  % too. We most probably want to keep these as they are.
  anchor=parent, child anchor=parent,
  % The default citation node text width:
  text width=1.5cm,
  % (Note that a fixed text width is not a requirement for the
  % "categorized citations" to work.  Try replacing the above
  % "text width=..." by:
  % anchor=center
  % 
  % Automatically format content of citation nodes. We offer two
  % mechanisms; you can also implement your own, or use none (just comment
  % this out).
  % 1. Wrap in \cite
  % content format=\cite{\forestoption{content}},
  % 2. Split the content of the node (separator is ",") and format the
  % parts according to "format (first) citation entry".
  split option={content}{,}{format first citation entry, format citation entry},
 },
 % These two keys are used (by the second content formatting mechanism above)
 % to build (new) content by concatenating the formatted parts (of the old
 % content).
 format first citation entry/.style={content'=\mbox{[#1]}},
 format citation entry/.style={content+'=\discretionary{}{}{}\mbox{[#1]}},
 % How shall we align citations in the unlikely case that we have more than
 % one citation node belonging to a category node on a single tier (see the
 % blue "citation" in the example tree)? Let's have a very fancy setup by
 % default: center calignment with forked edges, but no "s sep". The
 % following style is applied to a dummy parent where the packing of the
 % citation nodes actually takes place:
 citation nodes calign/.style={
  calign=center, forked edges, for children={fork sep=2em}, s sep=0,
  % To push the citation boxes a bit further away from the tree, adjust the
  % "l sep" of the dummy parent:
  l sep=2em,
 },
 % How should a citation box look like? Note that we want "fill opacity=1"
 % (the default) here, because we want the citation box to partially hide the
 % edges from citations to the folders.
 /tikz/citation box options/.style={
  fill=blue!20, draw=red, thick,
 },
 % Format the citation box labels:
 /tikz/citation box label options/.style={
 },
 % Where should the citation box labels appear?
 citation box label position=north,
}
\begin{document}
\begin{forest}
 [,phantom, reversed,
  % Style "categorized citations" takes an argument specifying a
  % comma-separated list of "box id=label" pairs.  "box id"s can be
  % anything. The boxes will appear next to the category tree, in the
  % specified order, with the given labels.
  % 
  % Each "box id" becomes a style that we use to put a citation node
  % into a box.  If we have "categorized citations={1=A,2=B,3=C}", then
  % "1=citation X" will put citation X into box 1, and "2=citation Y" will
  % put citation Y into box 2.
  % 
  % NOTE: Empty citation boxes are not allowed.  For example, if we have
  % "categorized citations={1=A,2=B,3=C}" and no "3=..." in the tree, we will
  % get an error.
  % 
  % "box id"s must be unique.  This is obvious for ids within the
  % "categorized citations" argument, but it is also required for the entire
  % tree (because under the hood, "box id"s function as tiers that citation
  % nodes live on). So if a tree contains multiple "categorized citations"
  % subtrees, you can say e.g. "categorized citations={L1=A,L2=B,L3=C}" for
  % the left category subtree and "categorized citations={R1=A,R2=B,R3=C}"
  % for the right category subtree (as below).
  [Parallel closing mechanisms, categorized citations={R1=A,R2=B,R3=C},
   % Note that in the new version, the category tree is formatted manually,
   % by applying "category tree".
   category tree, fill=gray,
   [Compliant mechanisms citation, for tree={fill=brown}
    [Lumped,
     % To link a citation to this node, say "box id=citation":
     R1=42,
     R2=24
    ]
    [Distributed,
     % The citations are auto-formatted, see "citation node autoformat" and
     % "format (first) citation entry" above.
     R1={1--4,7,9,12--13,18},
    ]
    [Hybrid
     [MEMS, R1=11, R2=22
      [citation, R1=1, R3=3]
      [cit]
     ]
    ]
   ]
   [Linkage mechanisms citation,
    % An easy way to set the color of the entire subtree. The opacity is set,
    % per-level, in "category tree".
    for tree={fill=red},
    [Crank and slider, R1=5, R2={22,33,44,55,66,77}, R3=1]
    [Bell crank,
     % We put two very tall nodes together here, and everything still works!
     R2={11--13,15--17,20,22,101--105},
    ]
    [Straight line]
   ]
   [Mechanical meta materials, for tree={fill=blue}
    [Bending dominated
     [cit]
     [citation,
      % Yiihaa, we can even have multiple citation nodes per tree node!
      % And we can even control how they are caligned ("citation nodes
      % calign"). (In this version, this works even better than before,
      % see the comments in "folder with cites".)
      R1=1,
      R2=4,
      R2=5,
      R1=3,
      R1=1,
     ]
     [cit, R1=1]
    ]
    [Stretching dominated
     % Any number of folder levels is supported.
     [folder 1
      [folder 2
       [folder 3
        [folder 4
         [folder 5,
          R3=24--42,
          % Without this phantom node on tier L1, "folder 5" would be
          % covered by citation boxes A and B. This is so because
          % "folder 5" sticks way out, and forest only looks at the
          % distance between a category node and its corresponding
          % citations. (This distance is controlled by "l sep" in
          % "citation nodes calign".)
          R1'={,phantom}
         ]
        ]
       ]
      ]
     ]
    ]
    [Origami/ kirigami inspired]
    [Auxetic (v$<$0)]
   ]
  ]
  [Parallel closing mechanisms, categorized citations={L1=A,L2=B},
   category tree,
   % Put this after "category tree", because that style sets grow'=0
   for tree={grow=180},
   fill=gray,
   % The root of the left subtree will be "identified" with the root of the
   % right subtree.
   identify with=!p,
   % Let's have some fun with citation box widths.  All the nodes in the
   % left B box reside on tier L2 (the "box id" of this box), so:
   before typesetting nodes={% a double "delay" would work as well
    where tier={L2}{text width=2.5cm}{}, % don't forget the empty braces!
   },
   [Mechanical meta materials, for tree={fill=blue}
    [Bending dominated
     [citation,
      % Using "L1" (etc.) we can only specify the content of the citation
      % node. To add some node options, use the bar-version, "L1'".
      L1'={1,very thick,red,font=\Large},
      % Why don't we only have the bar-version? Because we expect the
      % argument to frequently contain commas, and we *really* don't
      % want to be writing this all the time:
      L2'={{{{4,2}}}},
      % The point is that due to the implementation of pgfkeys, the
      % following does not work ...
      % L2'={{4,2}},
      % But thankfully, this is ok:
      L2'={{4,2},font=\Large},
      % "citation node options" is applied *after* any extra options we
      % provide using the bar-style, so if we want to override an option
      % specified there, we have to use "delay".
      L1'={3,delay={fill=red!50}},
      L1=1,
     ]
    ]
   ]
  ]
 ]
\end{forest}
\end{document}

插图

相关内容