Проблема

Проблема

Проблема

В TikZ можно контролировать направление роста ветви (и ее подветвей) с помощью ключа grow=<direction>. В forest, однако, growключ контролирует только направление ростаподотраслино не их родительская ветвь.

Как сделать так, чтобы ветви на одном уровне дерева forestросли в разных направлениях?Например, как в следующем MWE можно child 2расширить его по горизонтали, как rootэто делается в дереве TikZ?

МВЭ

\documentclass{article}
\usepackage{tikz,forest}

\begin{document}
\texttt{grow} used in a Ti\textit{k}Z tree

\begin{tikzpicture}
  \node{root}
    child{node{child 1}}
    child[grow=east]{node{child 2}
      child child child
    }
  ;
\end{tikzpicture}

\vskip20pt
\texttt{grow} used in a Forest tree

\begin{forest}
  [root
    [child 1]
    [child 2,grow=east
      [][][]
    ]
  ]
\end{forest}
\end{document}

введите описание изображения здесь

решение1

Желаемого эффекта можно добиться, перемещая поддерево узла child 2вручную. Я вижу два способа сделать это.

  1. Измените относительное положение ( lи s) child 2непосредственно перед stage compute xy. Обратите внимание, что child 2' s lи sявляются координатами в rootсистеме координат ' ls (см. документацию по параметрам lи s). Поскольку эти координаты являются относительными к родителю, необходимо изменить их только для корня поддерева, child 2.

  2. Измените абсолютное положение ( xи y)все узлы в child 2поддеревенепосредственно перед сценой draw tree.

    Примечание. В примере yскорректирован так, что child 2выровнен по вертикали с root: поскольку это достигается путем вычисления разницы между root' и child 2' y, узел child 2должен быть перемещен после его потомков.

Обратите внимание, что в обоих подходах forestсначала дочерние элементы корня располагаются в направлении роста по умолчанию -90 градусов, что в принципе может влиять как на положение (обратите внимание, что оно child 1находится слева от корня), так и на внутреннюю структуру поддеревьев.

\documentclass{article}
\usepackage{forest}

\begin{document}

% the ls way
\begin{forest}
  [root
    [child 1
      [][][]
    ]
    [child 2, for tree={grow=0},
      before computing xy={l=0,s=2cm}
      [][][]
    ]
  ]
\end{forest}

% the xy way
\begin{forest}
  [root
    [child 1
      [][][]
    ]
    [child 2, for tree={grow=0},
      before drawing tree={for descendants=
        {x+=1cm, y+=y("!r")-y("!r2")},
        x+=1cm, y+=y("!r")-y("!r2")
      }
      [][][]
    ]
  ]
\end{forest}

\end{document}

Честно говоря, я не нахожу ни один из двух подходов элегантным. Моей первой мыслью было встроить две forestсреды, по одной для каждого поддерева, в tikzpictureсреду. Поскольку forestработает, выплевывая tikzкод, я рассудил, что должно быть возможно позиционировать корневые узлы вручную с помощью tikzmechanisims ( at, right of, и т. д.). (Обратите внимание, что begin drawи end drawдолжны быть пустыми, чтобы это имело хоть какой-то шанс на успех.) Однако результат оказался не таким, как ожидалось... Я выясню причины и попытаюсь исправить проблему в какой-нибудь будущей версии forest.

решение2

Это старый вопрос, но поскольку эта функциональность не встроена в forest, я решил предложить общее решение.

Новая опция multiple directionsопределена в \forestset. Основная идея заключается в том, что для каждого направления, в котором вы хотите, чтобы росло поддерево, создается дубликат корневого узла и размещается в месте расположения фактического корневого узла. Затем каждое поддерево может расти в своем собственном направлении, поскольку технически у него есть свой родитель.

То же самое \forestsetопределяет grow subtreeи , grow' subtreeкоторые требуют направления (угол или направление компаса). Они следуют тем же правилам, что grow=и grow'=, а именно, что grow' subtreeменяет порядок узлов в поддереве на противоположный. Это ничего особенного: grow subtree=это просто альтернативный синтаксис дляfor tree={grow=}

Единственное изменение форматирования по сравнению со стандартным синтаксисом леса заключается в том, что необходимо создать пустой узел для каждого направления. Вот пример:

введите описание изображения здесь

Код этого дерева:

\begin{forest}
    multiple directions
    [root 
      [ 
        [child 1 [a][b]]
      ]
      [, grow subtree=150
        [child 2 [c][d][e]]
      ]
      [, grow' subtree=30
        [child 3 [f][g]]
      ]
    ]
\end{forest}

Параметр multiple directionsтакже действует как for treeи принимает параметры, как показано в следующем примере.

Примечание: forked edgeтребуется \useforestlibrary{edges}.

введите описание изображения здесь

Код:

\begin{forest}
    multiple directions={minimum height=4ex, anchor=center, forked edge}
    [R 
      [, grow' subtree=east
        [1 [a][b]]
        [2 [c][d]]
        [3 [e][f]]]
      [, grow subtree=west
        [4 [g][h]]
        [5 [i][j]]
        [6 [k][l]]
      ]
    ]
\end{forest}

Дополнительный пример с нарисованными узлами:

введите описание изображения здесь

\begin{forest}
    multiple directions={minimum width=2.5em, anchor=center, circle, draw}
    [c
      [, grow' subtree=north
        [a[a1][a2]]
        [b[b1][b2]]]
      [ 
        [d[d1][d2]]
        [e[e1][e2]]]
    ]
\end{forest}

Обратите внимание, что исходный корневой узел — это phantom, поэтому, если вы хотите изменить внешний вид узла по уровню, вам нужно увеличить уровень на 1. Таким образом, (видимый) корневой узел (который на самом деле является копией) находится на уровне 1, а его дочерние узлы — на уровне 2. Например:

введите описание изображения здесь

\begin{forest}
    multiple directions={
        text width=20mm,
        if level=1{fill=gray!80}{fill=gray!10},
        if level=2{fill=gray!40}{},
        forked edge,
        s sep=5mm, l sep=5mm,
        fork sep=2.5mm
    }
    [Root
      [, grow subtree=west
        [West of root[Far west]]
      ]
      [
        [A[B]]
        [C[D[E]]]
        [F[G]]
      ]
      [, grow subtree=east
        [East of root[Far east]]
      ]
    ]
\end{forest}

Также возможно использование multiple directionsна поддереве:

введите описание изображения здесь

\begin{forest}
    multiple directions={anchor=center}, forked edges
    [0
      [, grow subtree=west
        [1[1a][1b]][2[2a][2b]]]
      [, grow' subtree=east
        [3[3a][3b]][4[4a][4b, multiple directions, phantom=false
            [, grow' subtree=east[x[x1][x2]]]
            [, grow subtree=south[y[y1][y2]]]
        ]]]
    ]
\end{forest}

Обратите внимание, что поскольку способ multiple directionsскрывает корневой узел, необходимо настроить его phantom=falseдля этого использования.

Вот полный код, включая некоторые примеры:

\documentclass{article}

\usepackage{forest}
\useforestlibrary{edges}

\forestset{multiple directions/.style={for tree={#1}, phantom, for relative level=1{no edge, delay={!c.content/.pgfmath=content("!u")}, before computing xy={l=0,s=0}}},
    multiple directions/.default={},
    grow subtree/.style={for tree={grow=#1}}, 
    grow' subtree/.style={for tree={grow'=#1}}}

\begin{document}

\begin{forest}
    multiple directions
    [root 
      [ 
        [child 1 [a][b]]
      ]
      [, grow subtree=150
        [child 2 [c][d][e]]
      ]
      [, grow' subtree=30
        [child 3 [f][g]]
      ]
    ]
\end{forest}

\vspace{1cm}
\begin{forest}
    multiple directions={minimum height=4ex, anchor=center, forked edge}
    [R 
      [, grow' subtree=east
        [1 [a][b]]
        [2 [c][d]]
        [3 [e][f]]]
      [, grow subtree=west
        [4 [g][h]]
        [5 [i][j]]
        [6 [k][l]]
      ]
    ]
\end{forest}

\vspace{1cm}
\begin{forest}
    multiple directions={minimum width=2.5em, anchor=center, circle, draw}
    [c
      [, grow' subtree=north
        [a[a1][a2]]
        [b[b1][b2]]]
      [ 
        [d[d1][d2]]
        [e[e1][e2]]]
    ]
\end{forest}

\vspace{1cm}
\begin{forest}
    multiple directions={anchor=center}, forked edges
    [0
      [, grow subtree=west
        [1[1a][1b]][2[2a][2b]]]
      [, grow' subtree=east
        [3[3a][3b]][4[4a][4b, multiple directions, phantom=false
            [, grow' subtree=east[x[x1][x2]]]
            [, grow subtree=south[y[y1][y2]]]
        ]]]
    ]
\end{forest}

\end{document}

решение3

Никакое решение этой проблемы не может быть абсолютно общим. Автоматизация стиля не обязательно лучше (или хуже), чемПревосходная ручная структура деревьев Сэнди Джи. Этот ответ призван быть более автоматическим, немного более надежным и избегать незначительных проблем с форматированием, но нет никакой гарантии, что он не съест ваш шоколад или не украдет ваш носок.

Пусть покупатель будет бдителен


Это модификацияОтличный ответ Сэнди ДжиОсновная идея та же самая, со следующими изменениями.

  1. Стиль более автоматизирован.
  2. Это позволяет избежать набора нескольких копий одного и того же содержимого узла, что приводит к эффекту поддельного жирного шрифта в узлах, от которых отходят поддеревья в разных направлениях. (Но есть гораздо более простые способы, если это вас беспокоит только).
  3. Он пытается стать потенциально немного более надежным, интегрируя одно из изменений вЕще более превосходный ответ Сашо Живановича. (Это не значит, что это будет работать со folderстилем - я даже непыталсяэто!)

Вывод должен быть таким же, как при использовании ответа Сэнди Г. (по модулю видимой толщины шрифта). Отличается только спецификация деревьев.

выход без фальшивого жирного шрифта в расходящихся точках роста

Только два ключа действительно представляют интерес для спецификации деревьев.

subtree grow=<growth direction>
subtree grow'=<growth direction>

Они должны быть полностью аналогичны grow subtreeи grow subtree'.

Главное отличие в том, что это все, что нам нужно сделать. Нет необходимости вставлять дополнительные узлы или указывать какой-либо стиль для дерева в целом. subtree growи subtree grow'активировать требуемый стиль, который вставляет любые требуемые дополнительные узлы.

Взяв примеры Сэнди Г. для демонстрационных целей, первый можно определить просто как

\begin{forest}
  [root
    [child 1 
      [a][b]
    ]
    [child 2, subtree grow=150
      [c][d][e]
    ]
    [child 3, subtree grow'=30
      [f][g]
    ]
  ]
\end{forest}

Конечно, у этой простоты есть и обратная сторона. В некоторых деревьях нам приходится вводить данные точнее, поскольку у нас нет дополнительного фиктивного узла для задания направления роста для всего поддерева. Вместо этого мы должны указать направление роста для каждого потомка, который не должен расти в направлении по умолчанию, определенном родителем. Следовательно, второе дерево Сэнди Джи требуетшестьиспользования subtree grow/ subtree grow', тогда как для этого требовалось толькодваиз grow subtree/ grow subtree'.

[Если предыдущий пример был про качели, то я предполагаю, что мы уже на кольцевой развязке.]

\begin{forest}
  for tree={minimum height=4ex, anchor=center},
  forked edges,
  [R 
    [1, subtree grow'=east 
      [a][b]
    ]
    [2, subtree grow'=east 
      [c][d]
    ]
    [3, subtree grow'=east 
      [e][f]
    ]
    [4, subtree grow=west 
      [g][h]
    ]
    [5, subtree grow=west 
      [i][j]
    ]
    [6, subtree grow=west 
      [k][l]
    ]
  ]
\end{forest}

Для третьего примера нам понадобятся два использования subtree grow', но поскольку нам не нужно вставлять дополнительные узлы или добавлять что-либо в преамбулу, это все равно немного более кратко.

\begin{forest}
  for tree={minimum width=2.5em, anchor=center, circle, draw},
  [c
    [a, subtree grow'=north
      [a1][a2]
    ]
    [b, subtree grow'=north
      [b1][b2]
    ]
    [d
      [d1][d2]
    ]
    [e
      [e1][e2]
    ]
  ]
\end{forest}

Аналогично, четвертый пример подразумевает хороший баланс поворотов и круговых развязок, но вознаграждением за множественные спецификации роста является более четкая структура (по крайней мере, на уровне входного синтаксиса — само дерево — это другое дело).

\begin{forest}
  for tree={anchor=center}, 
  forked edges,
  [0
    [1, subtree grow=west
      [1a][1b]
    ]
    [2, subtree grow=west
      [2a][2b]
    ]
    [3, subtree grow'=east
      [3a][3b]
    ]
    [4, subtree grow'=east
      [4a]
      [4b
        [x, subtree grow'=east[x1][x2]]
        [y, subtree grow=south[y1][y2]]
      ]
    ]
  ]
\end{forest}

Под капотом оба subtree growи subtree grow'вызывают стиль под названием wild branching.

  subtree grow/.style={%
    wild branching={grow=#1},
  }, 
  subtree grow'/.style={%
    wild branching={grow'=#1},
  },

wild branchingне делает ничего, что вы не могли бы сделать сами с кодом Сэнди Г. и парой настроек. Он использует множество пользовательских опций и регистров Forest (для тех, кто не знаком с этой терминологией, «опция» — это настройка для узла в дереве, тогда как «регистр» применяется ко всему дереву).

Параметры:

  declare boolean={wild children}{0},
  declare boolean={wild child}{0},
  declare keylist={tame ones}{},
  declare keylist={wild siblings}{},
  declare boolean={wild leader}{0},

Последний аргумент — это начальное значение для всех узлов во всех деревьях.

Регистры:

  declare boolean register={wild tree},
  wild tree=0,

Нам также понадобится несколько простых стилей:

  append me/.style={append={!id=#1},do dynamics},
  prepend me/.style={prepend={!id=#1},do dynamics},
  wild phantom/.style={%
    node options/.option=!u.node options,
    content/.process={Ow{!u.content}{\phantom{##1}}},
    child anchor/.option=!1.child anchor,
    anchor/.option=!1.anchor,
    parent anchor/.option=!1.parent anchor,
    opacity=0,
    no edge,
  },

и я использовал специальный шаг

  define long step={wild children by growth}{}{%
    sort by={>O{grow}},sort'={filter={children}{>O{wild child}}}
  },

Я разделил стилистику на части, так как она становилась немного длинной. wild branchingзвонки do tamenessи do wildnessпо мере необходимости.

do tamenessвставляет дополнительный узел для поддеревьев, растущих в направлении по умолчанию, когда один из их братьев и сестер использует subtree growили subtree grow'.

  do tameness/.style={%
    where wild children={%
      tempboola=0,
      for children={%
        if wild child={}{%
          if tempboola={%
            !u.tame ones+/.option=id,
          }{%
            tempboola,
            replace by={%
              [,
                append,
                delay={%
                  if={>O_={!u.tame ones}{}}{}{%
                    split option={!u.tame ones}{,}{append me},
                  },
                  wild phantom,
                },
              ]%                  
            },
          },
        },
      },            
    }{},  
  },

do wildnessопределяет, когда создавать новый дополнительный узел для нового направления роста, а когда поддерево следует добавить к существующему дополнению. Здесь используется определенный выше пользовательский шаг: он гарантирует, что мы посещаем родственные узлы в порядке их направлений роста, что упрощает составление списков тех, которые принадлежат друг другу.

  do wildness/.style={%
    where wild children={%
      tempcounta'=9999,
      for wild children by growth={%
        if={>OR= {grow}{tempcounta} }
        {%
          tempkeylista'=,
          for children={%
            tempkeylista+/.option=id,
          },
          for nodewalk={%
            until={>O{wild leader}}{next},
            if wild siblings={}{%
              wild siblings/.register=tempkeylista
            }{%
              wild siblings+/.register=tempkeylista
            }%
          }{%
          },
          before packing={remove},
        }{%
          wild leader,
          tempcounta/.option=grow,
          before packing={%
            if wild siblings={}{}{%
              split option={wild siblings}{,}{prepend me},
            },
          },
        },
      },
    }{},  
  },

Этот метод создает больше узлов, чем метод Sandy G, но лишние удаляются до упаковки дерева. Тем не менее, структура конечного дерева отличается из-за wild branchingизменений lи sдо упаковки родительского узла. Теоретически это должно привести к уменьшению количества ситуаций, требующих ручного вмешательства. Но теория, как мы все знаем, не является практикой.

  wild branching/.style={% 
    if id=1{for tree={#1}}{%
      !u.wild children,
      delay={%
        replace by={%
          [,
            wild child,
            append,
            delay={%
              wild phantom,
              for tree={#1},          
            },
          ]%
        },
      },
      if wild tree={}{%
        wild tree,
        !root.before typesetting nodes={%
          do tameness,
          do wildness,
        },
        !root.before packing={%
          delay={
            where wild children={%
              after packing node={%
                for children={l'=0pt,s'=0pt},
              },
            }{},
          },
        },
      },
    },
  },
  wild branching/.default={},

Обратите внимание, что subtree grow/ subtree grow'не предназначены для установки для корневого узла. Если они предназначены, wild branchingпросто примените growили grow'к дереву, не вызывая ничего специального.

wild branchingВероятно, не должно быть .defaultнастройки, но по какой-то причине, которую я сейчас не могу вспомнить, я ее определил.

Код:

\documentclass[a4paper,landscape]{article}
\usepackage[scale=.8]{geometry}
% ateb: https://tex.stackexchange.com/a/705635/ addaswyd o ateb Sandy G: https://tex.stackexchange.com/a/643061/
\usepackage{forest}
\useforestlibrary{edges}

\forestset{% https://tex.stackexchange.com/a/705635/
  declare boolean={wild children}{0},
  declare boolean={wild child}{0},
  declare keylist={tame ones}{},
  declare keylist={wild siblings}{},
  declare boolean={wild leader}{0},
  declare boolean register={wild tree},
  wild tree=0,
  append me/.style={append={!id=#1},do dynamics},
  prepend me/.style={prepend={!id=#1},do dynamics},
  wild phantom/.style={%
    node options/.option=!u.node options,
    content/.process={Ow{!u.content}{\phantom{##1}}},
    child anchor/.option=!1.child anchor,
    anchor/.option=!1.anchor,
    parent anchor/.option=!1.parent anchor,
    opacity=0,
    no edge,
  },
  define long step={wild children by growth}{}{%
    sort by={>O{grow}},sort'={filter={children}{>O{wild child}}}
  },
  do tameness/.style={%
    where wild children={%
      tempboola=0,
      for children={%
        if wild child={}{%
          if tempboola={%
            !u.tame ones+/.option=id,
          }{%
            tempboola,
            replace by={%
              [,
                append,
                delay={%
                  if={>O_={!u.tame ones}{}}{}{%
                    split option={!u.tame ones}{,}{append me},
                  },
                  wild phantom,
                },
              ]%                  
            },
          },
        },
      },            
    }{},  
  },
  do wildness/.style={%
    where wild children={%
      tempcounta'=9999,
      for wild children by growth={%
        if={>OR= {grow}{tempcounta} }
        {%
          tempkeylista'=,
          for children={%
            tempkeylista+/.option=id,
          },
          for nodewalk={%
            until={>O{wild leader}}{next},
            if wild siblings={}{%
              wild siblings/.register=tempkeylista
            }{%
              wild siblings+/.register=tempkeylista
            }%
          }{%
          },
          before packing={remove},
        }{%
          wild leader,
          tempcounta/.option=grow,
          before packing={%
            if wild siblings={}{}{%
              split option={wild siblings}{,}{prepend me},
            },
          },
        },
      },
    }{},  
  },
  wild branching/.style={% 
    if id=1{for tree={#1}}{%
      !u.wild children,
      delay={%
        replace by={%
          [,
            wild child,
            append,
            delay={%
              wild phantom,
              for tree={#1},          
            },
          ]%
        },
      },
      if wild tree={}{%
        wild tree,
        !root.before typesetting nodes={%
          do tameness,
          do wildness,
        },
        !root.before packing={%
          delay={
            where wild children={%
              after packing node={%
                for children={l'=0pt,s'=0pt},
              },
            }{},
          },
        },
      },
    },
  },
  wild branching/.default={},
  subtree grow/.style={%
    wild branching={grow=#1},
  }, 
  subtree grow'/.style={%
    wild branching={grow'=#1},
  },
}
\pagestyle{empty}
\begin{document}
\centering
\begin{forest}
  [root
    [child 1 
      [a][b]
    ]
    [child 2, subtree grow=150
      [c][d][e]
    ]
    [child 3, subtree grow'=30
      [f][g]
    ]
  ]
\end{forest}
\begin{forest}
  for tree={minimum height=4ex, anchor=center},
  forked edges,
  [R 
    [1, subtree grow'=east 
      [a][b]
    ]
    [2, subtree grow'=east 
      [c][d]
    ]
    [3, subtree grow'=east 
      [e][f]
    ]
    [4, subtree grow=west 
      [g][h]
    ]
    [5, subtree grow=west 
      [i][j]
    ]
    [6, subtree grow=west 
      [k][l]
    ]
  ]
\end{forest}
\begin{forest}
  for tree={minimum width=2.5em, anchor=center, circle, draw},
  [c
    [a, subtree grow'=north
      [a1][a2]
    ]
    [b, subtree grow'=north
      [b1][b2]
    ]
    [d
      [d1][d2]
    ]
    [e
      [e1][e2]
    ]
  ]
\end{forest}
\begin{forest}
  for tree={anchor=center}, 
  forked edges,
  [0
    [1, subtree grow=west
      [1a][1b]
    ]
    [2, subtree grow=west
      [2a][2b]
    ]
    [3, subtree grow'=east
      [3a][3b]
    ]
    [4, subtree grow'=east
      [4a]
      [4b
        [x, subtree grow'=east[x1][x2]]
        [y, subtree grow=south[y1][y2]]
      ]
    ]
  ]
\end{forest}

\end{document}

Связанный контент