TikZ/Forest:如何根据节点的级别来格式化/样式化节点?

TikZ/Forest:如何根据节点的级别来格式化/样式化节点?

跟进之前的问题 [12],我想知道如何根据每个节点的级别(包括框样式、文本格式以及与父节点/子节点的分离)来对其进行样式化/格式化?

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage[edges]{forest}
\usepackage{calc}
\usetikzlibrary{arrows.meta}
\forestset{%
  declare dimen register={gap},
  gap'=20mm,
  declare dimen register={lbox width},
  lbox width=(\textwidth-2*\forestregister{gap})/3,
  rbox/.style = {draw=blue!80!black, fill=blue!20, rounded corners},
  lbox/.style = {align/.wrap pgfmath arg={@{}p{##1 pt}@{}}{(lbox_width)}},
}
\begin{document}
\begin{forest}
  forked edges,
  for tree={%
    font = \sffamily,
    edge = {draw, -{Latex}},
  },
  where n children=0{%
    lbox,
    no edge,
  }{%
    rbox,
  }
  [Some Text, name=Start, l sep+=5pt
    [Text Text, name=Block1
      [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
      ]
    ]
    [Text, name=Block2
      [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
      ]
    ]
    [Text Text Text, name=Block3
      [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
      ]
    ]
  ]
\end{forest}
\end{document}

在此处输入图片描述

答案1

实际上,根据节点的级别,有多种样式。在某些情况下,一种更改的方式可能会改变其他更改的方式。

关于 Forest 如何运作的一些有用背景信息。

  • Forest 有一个概念阶段在每个阶段中,Forest 都会进行一个或多个周期默认情况下,Forest 会通过以下阶段进行工作。但是,可以重新定义、删除或添加阶段,尽管这通常不是必要的。

    1. 最初,解析树。但是,只有一些键会被立即考虑。每个节点的内容都会被保存,并且会应用任何初始选项。例如,prooftrees定义插入到适当的默认阶段之间的附加阶段,并执行一些其他有趣的操作以使事情正常进行。

    2. 节点已排版。这些是单独的节点。它们实际上并未排版,不会产生任何输出。相反,排版后的节点保存在一个框中。(毕竟,一切都是框。)

    3. 节点被打包,考虑到任何相关的定制。这涉及 Forest 确定每个节点相对于其父节点的位置。(就 Forest 的l/s坐标系而言。)

    4. 现在以绝对 Ti 计算每个节点的位置Z 项,即常规x/y坐标。

    5. 绘制树或可选地保存到盒子中以供日后使用。

对于大多数目的来说,细节并不重要,但了解一般情况是有帮助的,因为有时需要延迟应用选项直到某个阶段的后续周期或直到后续阶段。

例如,假设我们要绘制下面的树。

最终树

我们可以label对每个节点都使用。但是,将标签放入节点本身并让 Forest 将其转换为标签会更方便。

因此,我们可以尝试这样的事情。

\begin{forest}
  for tree={%
    label/.option=content,
    content=,
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}

这些是威尔士字母表的首字母。(艾伦·芒恩注:‘字母’的重要意义。)

但这并不会给我们带来我们所期望的结果。

意外

问题是label在 Forest 设置节点内容之前,就将其设置为节点内容。需要延迟到 Forest 遍历树一次以保存内容。只有这样,才应该设置标签并处理内容。

有多种方法可以实现这一点。最简单的方法(尽管并不总是最可靠的)是delay,它告诉 Forest 将执行延迟一个周期。

\begin{forest}
  for tree={%
    delay={%
      label/.option=content,
      content=,
    },
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}

这会产生更好的结果。

好多了

我们希望底层的标签位于节点下方。这些节点没有子节点,因此我们可以测试节点是否有子节点,如果没有,则将标签放在下面。将我们的更改delay={}

    delay={%
      if n children=0{%
        label/.wrap pgfmath arg={-90:#1}{content},
      }{%
        label/.option=content,
      },
      content=,
    },

我们得到了一个不太混乱的结果。

不那么杂乱

为了更好地分隔节点,我们需要增加底层和中层兄弟节点之间的距离。为此,我们增加s sep这些节点的父母。我们可以对整棵树都这样做,但假设我们想将10pt中间层和5pt底层的分离度增加。

然后,对于最后一层节点(没有子节点),我们可以添加以下行

        !u.s sep+=5pt,
        !uu.s sep+=10pt,

告诉!Forest 在应用该选项之前,它需要“行走”到一个(可能不同的)节点。u是当前节点的父节点。uu是当前节点的父节点的父节点。告诉+Forest 将其添加到现有值。

\begin{forest}
  for tree={%
    delay={%
      if n children=0{%
        label/.wrap pgfmath arg={-90:#1}{content},
        !u.s sep'+=5pt,
        !uu.s sep'+=10pt,
      }{%
        label/.option=content,
      },
      content=,
    },
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}

然后生成所需的树

最终树

好的。假设我们想画这棵树。

目标树

基本上,这棵树是按照级别格式化的,如问题中所述。为了避免不必要的重复,我们可以将常见的格式放入 中for tree。这种格式并不多。

  for tree={%
    double,
    rounded corners,
  },

步骤 1 针对目标 2

当然,这些选项没有任何明显的效果,因为我们没有填充或绘制任何东西。

它们都在使用double,并且rounded corners不会伤害底层的圆圈。

现在我们开始按级别指定格式。包含节点的第一个级别root是 level 0。下一个是 level ,1依此类推。因此,蓝色的东西需要应用于零级。

  where level=0{% for the root node
    minimum size=20pt,
    draw=magenta,
    inner color=magenta!25,
    outer color=magenta,
    ultra thick,
  }{%
  },

请注意,这里几乎没有任何格式键是 Forest 特有的。但 Forest 会将其无法识别的任何内容传递给 TiZ,所以一切都会正常进行。上面的代码告诉 Forest 将给定的选项应用于级别0(即root)的节点,而不对其余节点执行任何操作。

步骤 2 目标 2

现在,我们要将所有不在零级的节点分成一级节点和二级节点。

where level=0{%
  <options for root>
}{%
  if level=1{%
    <options for level 1>
  }{%
    <options for level 2>
  },
},

填写此表

  where level=0{% for the root node
    minimum size=20pt,
    draw=magenta,
    inner color=magenta!25,
    outer color=magenta,
    ultra thick,
  }{%
    if level=1{% middle nodes
      minimum size=15pt,
      semithick,
      draw=blue!50!cyan,
      inner color=blue!50!cyan!25,
      outer color=blue!50!cyan,
    }{% remaining nodes
      delay={%
        if n children=0{%
          label/.wrap pgfmath arg={-90:#1}{content},
          !u.s sep'+=5pt,
          !uu.s sep'+=10pt,
          content=,
        }{},
      },
      green!75!black,
      circle,
      fill,
      fill opacity=.5,
      draw opacity=1,
      draw,
      minimum size=5pt,
      inner sep=0pt,
    },
  },

我们使用delay与之前相同的策略来格式化底层节点,即将节点内容推送到节点下方的标签中,并且仅在 Forest 注意到内容后才丢弃节点内容。

目标树

这正是我们想要的,但代码重复性比需要的要多。我们在级别 0 和级别 1 中重复了三次颜色,并且我们对这些级别也使用了非常相似的样式,只是颜色和大小不同。

为了简化,我们可以使用 TiZ 或森林风格。例如,

\begin{forest}
  roundish filling/.style={%
    draw=#1,
    inner color=#1!25,
    outer color=#1,
  },
  for tree={%
    <options for tree>
  },
  where level=0{% for the root node
    <additional options for level 0>,
    roundish filling=magenta,
  }{%
    if level=1{% middle nodes
      <additional options for level 1>,
      roundish filling=blue!50!cyan,
    }{% remaining nodes
      <options for level 2>,
    },
  },

做同样的事情,以便我们可以使用下面的代码绘制上面的树。

\begin{forest}
  roundish filling/.style={%
    draw=#1,
    inner color=#1!25,
    outer color=#1,
  },
  for tree={%
    double,
    rounded corners,
  },
  where level=0{% for the root node
    minimum size=20pt,
    roundish filling=magenta,
    ultra thick,
  }{%
    if level=1{% middle nodes
      minimum size=15pt,
      semithick,
      roundish filling=blue!50!cyan,
    }{% remaining nodes
      delay={%
        if n children=0{%
          label/.wrap pgfmath arg={-90:#1}{content},
          !u.s sep'+=5pt,
          !uu.s sep'+=10pt,
          content=,
        }{},
      },
      green!75!black,
      circle,
      fill,
      fill opacity=.5,
      draw opacity=1,
      draw,
      minimum size=5pt,
      inner sep=0pt,
    },
  },
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}

好的。让我们集中精力按级别设置间距和颜色。假设我们想排版一棵树,如下所示。

目标树 3

所以基本树可以像这样排版。

\begin{forest}
  for tree={%
    inner sep=0pt,
    minimum size=5pt,
    circle,
    fill,
  },
  [[[[][][]][[][[][]]]][[[[[][[[][][]][[][]][[[][]][[[][][][][]]][[][]]][]][[[[[][]]]]]]][[][]][[][]]][][]]]
\end{forest}

步骤 1 目标 3

作为参考,这里有一个带有编号的版本。

注释步骤 1 目标 3

这是带注释的目标。

注释目标 3

这棵树的间距有点奇怪,所以我们需要相当复杂的条件设置才能使其正确。根(级别 0)的子节点距离根节点稍远,彼此之间的距离也比标准距离稍远。所以我们需要类似这种模式的东西。

  where level=0{%
    <increase distance to children>,
    <increase distance between children>,
  }{}

4 级的子节点似乎比标准节点间隔更大,而 6 级的节点比标准节点更靠近它们的父节点。

  where level=0{%
    <increase distance to children>,
    <increase distance between children>,
  }{%
    if level=4{%
      <increase distance between children>,
    }{%
      if level=6{%
        <decrease distance to parents>
      }{},
    },
  }

第 7 级的情况有些不同。fit这里的子树间距更大。不允许孩子侵占其堂兄弟下面的空间。band子树下方保留了垂直空间,这显然对树木的间距有重大影响。

所以我们的模式将会是这样的。

  where level=0{%
    <increase distance to children>,
    <increase distance between children>,
  }{%
    if level=4{%
      <increase distance between children>,
    }{%
      if level=6{%
        <decrease distance to parents>
      }{%
        if level=7{%
          <change fit of nodes' sub-trees>
        }{}
      },
    },
  }

例如,

  where level=0{%
    l sep+=10pt,
    s sep+=15pt,
  }{%
    if level=4{%
      s sep+=20pt
    }{%
      if level=6{%
        l-=20pt,
      }{%
        if level=7{%
          fit=band,
        }{}
      },
    },
  }

步骤 2 目标 3:间距

要查看差异fit=band,请将此部分树的输出与fit=band

使用 <code>fit=band</code>

有那个 没有那个

没有 <code>fit=band</code>

对于着色,我们希望节点从蓝色变为洋红色,然后再变回蓝色。我们可以使用节点的级别来设置颜色混合中蓝色和洋红色的比例。我们可以通过将树分为 0-5 级和 6-9 级来实现这一点。(我们可以一次性完成,但为什么要让事情变得复杂呢?)

  where={level()<6}{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{level()*100/5},
  }{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{(9-(level()))*100/5},
  },

把它放在一起。

\begin{forest}
  for tree={%
    inner sep=0pt,
    minimum size=5pt,
    circle,
    fill,
  },
  where={level()<6}{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{level()*100/5},
  }{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{(9-(level()))*100/5},
  },
  where level=0{%
    l sep+=10pt,
    s sep+=15pt,
  }{%
    if level=4{%
      s sep+=20pt
    }{%
      if level=6{%
        l-=20pt,
      }{%
        if level=7{%
          fit=band,
        }{}
      },
    },
  }
  [[[[][][]][[][[][]]]][[[[[][[[][][]][[][]][[[][]][[[][][][][]]][[][]]][]][[[[[][]]]]]]][[][]][[][]]][][]]]
\end{forest}

生成我们的目标树。

最终树 3

完整代码:

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
  for tree={%
    label/.option=content,
    content=,
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    delay={%
      label/.option=content,
      content=,
    },
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    delay={%
      if n children=0{%
        label/.wrap pgfmath arg={-90:#1}{content},
      }{%
        label/.option=content,
      },
      content=,
    },
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    delay={%
      if n children=0{%
        label/.wrap pgfmath arg={-90:#1}{content},
        !u.s sep'+=5pt,
        !uu.s sep'+=10pt,
      }{%
        label/.option=content,
      },
      content=,
    },
    circle,
    fill,
    minimum size=5pt,
    inner sep=0pt,
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    double,
    rounded corners,
  },
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    double,
    rounded corners,
  },
  where level=0{% for the root node
    minimum size=20pt,
    draw=magenta,
    inner color=magenta!25,
    outer color=magenta,
    ultra thick,
  }{%
  },
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  for tree={%
    double,
    rounded corners,
  },
  where level=0{% for the root node
    minimum size=20pt,
    draw=magenta,
    inner color=magenta!25,
    outer color=magenta,
    ultra thick,
  }{%
    if level=1{% middle nodes
      minimum size=15pt,
      semithick,
      draw=blue!50!cyan,
      inner color=blue!50!cyan!25,
      outer color=blue!50!cyan,
    }{% remaining nodes
      delay={%
        if n children=0{%
          label/.wrap pgfmath arg={-90:#1}{content},
          !u.s sep'+=5pt,
          !uu.s sep'+=10pt,
          content=,
        }{},
      },
      green!75!black,
      circle,
      fill,
      fill opacity=.5,
      draw opacity=1,
      draw,
      minimum size=5pt,
      inner sep=0pt,
    },
  },
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}
\begin{forest}
  roundish filling/.style={%
    draw=#1,
    inner color=#1!25,
    outer color=#1,
  },
  for tree={%
    double,
    rounded corners,
  },
  where level=0{% for the root node
    minimum size=20pt,
    roundish filling=magenta,
    ultra thick,
  }{%
    if level=1{% middle nodes
      minimum size=15pt,
      semithick,
      roundish filling=blue!50!cyan,
    }{% remaining nodes
      delay={%
        if n children=0{%
          label/.wrap pgfmath arg={-90:#1}{content},
          !u.s sep'+=5pt,
          !uu.s sep'+=10pt,
          content=,
        }{},
      },
      green!75!black,
      circle,
      fill,
      fill opacity=.5,
      draw opacity=1,
      draw,
      minimum size=5pt,
      inner sep=0pt,
    }
  }
  [A
    [B [C][Ch]]
    [D [Dd][E][F][Ff]]
  ]
\end{forest}

\begin{forest}
  for tree={%
    inner sep=0pt,
    minimum size=5pt,
    circle,
    fill,
  },
  [[[[][][]][[][[][]]]][[[[[][[[][][]][[][]][[[][]][[[][][][][]]][[][]]][]][[[[[][]]]]]]][[][]][[][]]][][]]]
\end{forest}
\begin{forest}
  for tree={%
    inner sep=0pt,
    minimum size=5pt,
    circle,
    fill,
  },
  where level=0{%
    l sep+=10pt,
    s sep+=15pt,
  }{%
    if level=4{%
      s sep+=20pt
    }{%
      if level=6{%
        l-=20pt,
      }{%
        if level=7{%
          fit=band,
        }{}
      },
    },
  }
  [[[[][][]][[][[][]]]][[[[[][[[][][]][[][]][[[][]][[[][][][][]]][[][]]][]][[[[[][]]]]]]][[][]][[][]]][][]]]
\end{forest}
\begin{forest}
  for tree={%
    inner sep=0pt,
    minimum size=5pt,
    circle,
    fill,
  },
  where={level()<6}{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{level()*100/5},
  }{%
    fill/.wrap pgfmath arg={magenta!#1!blue}{(9-(level()))*100/5},
  },
  where level=0{%
    l sep+=10pt,
    s sep+=15pt,
  }{%
    if level=4{%
      s sep+=20pt
    }{%
      if level=6{%
        l-=20pt,
      }{%
        if level=7{%
          fit=band,
        }{}
      },
    },
  }
  [[[[][][]][[][[][]]]][[[[[][[[][][]][[][]][[[][]][[[][][][][]]][[][]]][]][[[[[][]]]]]]][[][]][[][]]][][]]]
\end{forest}
\end{document}

相关内容