Сегодня мне снова нужна ваша помощь, чтобы разобраться с пакетом forest
(это действительно мощный пакет, но и довольно сложный). По сути, я уже потратил несколько часов, пытаясь нарисовать ультимативную игру в виде дерева, которая должна выглядеть так:
На данный момент мне удалось создать этот код, который каким-то образом работает (но работает не очень хорошо):
\documentclass{report}
\usepackage[T1]{fontenc}
\usepackage{amssymb}
\usepackage{mathtools}
\usepackage{forest}
\usepackage[labelfont=bf,skip=0pt,labelsep=period]{caption}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=1.6}
\usetikzlibrary{matrix,calc,positioning}
\pgfplotsset{soldot/.style={color=black,only marks,mark=*}}
\pgfplotsset{/pgfplots/xlabel near ticks/.style={/pgfplots/every axis x label/.style={at={(ticklabel cs:0.5)},anchor=near ticklabel}},/pgfplots/ylabel near ticks/.style={/pgfplots/every axis y label/.style={at={(ticklabel cs:0.5)},rotate=90,anchor=near ticklabel}}}
\begin{document}
\begin{figure}
\begin{forest}
for tree={l sep=4em, s sep=8em, anchor=center}
[$P_1$, circle, draw,
[,name=0, edge label={node[midway,left,outer sep=1.5mm,]{$x=0$}}]
[$P_2$, l*=2, before computing xy={s=(s("!p")+s("!n"))/2}, circle, draw, edge label={node[midway,right,]{$x$}}
[{$c-x,x$}, edge label={node[midway,left,outer sep=1.5mm,]{$Y$}}]
[{$0,0$}, edge label={node[midway,right,outer sep=1.5mm,]{$N$}}]]
[,name=1, edge label={node[midway,right,outer sep=1.5mm,]{$x=c$}}]]
\draw (0) to[bend right=45] (1);
\end{forest}
\end{figure}
\end{document}
Результат, который я получаю, следующий:
В идеале, я хотел бы изменить несколько вещей. Во-первых, я хотел бы, чтобы (a) кривая линия касалась ветвей дерева, избегая этого уродливого белого пространства. Во-вторых, (b) я хотел бы избежать наложения между кривой линией и узлом P2. В-третьих, (c) я хотел бы убедиться, что углы двух ветвей в двух узлах одинаковы. Другими словами, угол второго узла шире, чем у первого узла, и я хотел бы, чтобы они были одинаковыми. В-четвертых, (d) я хотел бы, чтобы ветви второго узла были короче, чем у первого, если это возможно. Все вопросы одинаково актуальны. Любая помощь по любой из четырех проблем будет высоко оценена.
PS: В идеале я хотел бы придерживаться своего текущего кода настолько, насколько это возможно (другими словами, я хотел бы применить к коду минимально необходимые изменения, чтобы получить нужные мне изменения, поскольку этот код я могу понять более или менее легко).
решение1
Вот решение, которое, как мне кажется, удовлетворяет всем заявленным требованиям. Я предположил, исходя из целевого изображения, что вы не хотите, чтобы дуга не доходила до самой верхней точки узла P_2
. Если это не требуется, вам не нужно беспокоиться о некоторых вычислениях, включенных в мой код.
\documentclass[tikz, border=10pt, multi]{standalone}
Мы загружаем through
библиотеки calc
TikZ, чтобы дуга была нарисована правильно.
\usetikzlibrary{through,calc}
\usepackage{forest}
Теперь мы настроим некоторые стили. Некоторые из них упрощают ваш существующий код. Если вы предпочитаете их не использовать, вы можете их пропустить.
\forestset{%
auto edge label
автоматизирует код для форматирования edge label
. Он создает узел, переводит содержимое в математический режим и решает, следует ли поместить метку слева или справа от края. Это означает, что
edge label=x^2, auto edge label
поступят правильно.
auto edge label/.style={%
before typesetting nodes={%
Ничего не делайте, если это корневой узел.
if level=0{}{
Если узел находится во второй половине дочерних узлов своего родителя или является средним дочерним узлом...
if={n()>(n_children("!u")/2)}{
Если узел является средним потомком...
if={n()==((n_children("!u")+1)/2)}{
edge label/.wrap value={
node[midway, right] {$##1$}
},
}{
Если узел находится во второй половине потомков своего родителя...
edge label/.wrap value={
node[midway, outer sep=1.5mm, right] {$##1$}
},
},
}{
Если узел находится в первой половине дочерних узлов своего родителя...
edge label/.wrap value={
node[midway, outer sep=1.5mm, left] {$##1$}
},
}
},
},
},
Это стиль для более хороших пустых узлов. Он со страницы 65 текущего руководства. Он является частью библиотеки linguistics
. Следовательно, если вы используете эту библиотеку, вы можете опустить это определение и просто применить стиль из коробки.
Обратите внимание, что я считаю, что объяснение в этой части руководства ошибочно относится к несуществующим опциям, но я в этом не уверен.
nice empty nodes/.style={% page 65 of the manual - this is from the linguistics library
for tree={
calign=fixed edge angles
},
delay={
where content={}{
shape=coordinate,
for parent={
for children={anchor=north}
}
}{}
}
},
Вот стиль рисования дуг. Вы передаете это как опцию родительскому элементу узлов, через которые должна быть нарисована дуга.
Никакие ребра не рисуются как часть самого дерева, за исключением второго потомка. Если может быть больше 3 потомков, это должно быть немного сложнее. Этот код предполагает 3 потомка.
Вместо этого дуга рисуется позже путем вычисления соответствующих точек на основе положения среднего узла и угла по умолчанию к узлам при использовании calign=fixed edge angles
. В это время рисуются ребра к первому и третьему потомку.
[Это действительно должно быть немного сложнее с точки зрения проверки различных возможностей, но это должно работать в случаях, подобных MWE.]
arc below/.style={
tikz+={%
\clip (.center) coordinate (o) -- (!1.north) coordinate (a) |- (!2.north) coordinate (b) -| (!3.north) coordinate (c) -- cycle;
\node [draw, circle through={(b)}] at (o) {};
\draw [\forestoption{edge}] () -- ($(o)!1!-35:(b)$) ($(o)!1!35:(b)$) -- ();
},
for children={
if n=2{}{no edge},
}
}
}
Простой стиль TikZ для удобства.
\tikzset{%
my circle/.style={draw, circle}
}
Затем мы можем просто применить все это к дереву следующим образом.
\begin{document}
\begin{forest}
for tree={
Из МВЭ.
l sep=4em,
s sep=8em,
Примените два новых стиля ко всему дереву.
auto edge label,
nice empty nodes,
Переведите все узлы в математический режим, чтобы сэкономить знаки доллара.
math content,
}
Укажите arc below
стиль для корня.
[P_1, my circle, arc below
[, edge label={x=0}]
[P_2, my circle, edge label=x
[{c-x,x}, edge label=Y]
[{0,0}, edge label=N]
]
[, edge label={x=c}]
]
\end{forest}
\end{document}
Полный код:
\documentclass[tikz, border=10pt, multi]{standalone}
\usetikzlibrary{through,calc}
\usepackage{forest}
\forestset{%
auto edge label/.style={%
before typesetting nodes={%
if level=0{}{
if={n()>(n_children("!u")/2)}{
if={n()==((n_children("!u")+1)/2)}{
edge label/.wrap value={
node[midway, right] {$##1$}
},
}{
edge label/.wrap value={
node[midway, outer sep=1.5mm, right] {$##1$}
},
},
}{
edge label/.wrap value={
node[midway, outer sep=1.5mm, left] {$##1$}
},
}
},
},
},
nice empty nodes/.style={% page 65 of the manual - this is from the linguistics library
for tree={
calign=fixed edge angles
},
delay={
where content={}{
shape=coordinate,
for parent={
for children={anchor=north}
}
}{}
}
},
arc below/.style={
tikz+={%
\clip (.center) coordinate (o) -- (!1.north) coordinate (a) |- (!2.north) coordinate (b) -| (!3.north) coordinate (c) -- cycle;
\node [draw, circle through={(b)}] at (o) {};
\draw [\forestoption{edge}] () -- ($(o)!1!-35:(b)$) ($(o)!1!35:(b)$) -- ();
},
for children={
if n=2{}{no edge},
}
}
}
\tikzset{%
my circle/.style={draw, circle}
}
\begin{document}
\begin{forest}
for tree={
l sep=4em,
s sep=8em,
auto edge label,
nice empty nodes,
math content,
}
[P_1, my circle, arc below
[, edge label={x=0}]
[P_2, my circle, edge label=x
[{c-x,x}, edge label=Y]
[{0,0}, edge label=N]
]
[, edge label={x=c}]
]
\end{forest}
\end{document}
РЕДАКТИРОВАТЬ
Если вы хотите еще больше увеличить разделение между первыми двумя уровнями дерева, самый простой способ — просто увеличить значение l sep
для корневого узла. Вот намеренно преувеличенный пример:
\begin{forest}
for tree={
l sep=4em,
s sep=8em,
auto edge label,
nice empty nodes,
math content,
}
[P_1, my circle, arc below, l sep*=6
[, edge label={x=0}]
[P_2, my circle, edge label=x
[{c-x,x}, edge label=Y]
[{0,0}, edge label=N]
]
[, edge label={x=c}]
]
\end{forest}
Здесь минимальное расстояние между корнем и его потомками установлено в шесть раз больше обычного минимального расстояния между уровнями с l sep*=6
. Если вы предпочитаете добавить абсолютную величину, вы можете указать l sep+=<dimension>
. Или, если вы просто хотите переопределить значение по умолчанию, l sep=<dimension>
точно указывает минимальное расстояние.
Важно, что l sep
гарантируетминимумрасстояние. Таким образом, если l sep
установлено очень мало для одного уровня и немного больше для другого, вы можете получить одинаковое расстояние в каждом случае, потому что другие факторы будут означать, чтолеснеобходимо, чтобы узлы располагались на большем расстоянии, чем любой из указанных минимумов.
Я отмечаю, что ваше фактическое целевое дерево на самом деле не похоже на то, что вы показали в вопросе. На самом деле, самая сложная часть моего кода выше вообще не требуется для вашего конечного дерева.
Вот автоматизированная версия этого дерева для справки. Эта версия обходится без библиотек TikZ, поскольку они нужны только для arc below
. arc through
— это новый стиль, который соединяется с западным и восточным якорями, а не проходит через северный якорь. my arc
определяет стиль дуги. Это можно задать в дереве с помощью my arcs={<key list>}
для определения стиля. По умолчанию он пуст, и дуга рисуется со стилем текущей edge
опции. Указание стиля с помощью my arcs
позволяет дополнять или переопределять edge
стиль. Например, дуга может быть densely dashed
ровной, если края нарисованы сплошным цветом.
\forestset{%
arc through/.style={
tikz+={%
\path [\forestoption{edge}, my arc] (!1) [out=-35, in=180] to (!2.west) (!2.east) [out=0, in=-145] to (!3);
}
},
my arcs/.code={%
\tikzset{%
my arc/.style={#1},
}
},
}
\tikzset{%
my arc/.style={},
}
\documentclass[tikz, border=10pt, multi]{standalone}
\usepackage{forest}
\forestset{%
auto edge label/.style={%
before typesetting nodes={%
if level=0{}{
if={n()>(n_children("!u")/2)}{
if={n()==((n_children("!u")+1)/2)}{
edge label/.wrap value={
node[midway, right] {$##1$}
},
}{
edge label/.wrap value={
node[midway, outer sep=1.5mm, right] {$##1$}
},
},
}{
edge label/.wrap value={
node[midway, outer sep=1.5mm, left] {$##1$}
},
}
},
},
},
nice empty nodes/.style={% page 65 of the manual - this is from the linguistics library
for tree={
calign=fixed edge angles
},
delay={
where content={}{
shape=coordinate,
for parent={
for children={anchor=north}
}
}{}
}
},
arc through/.style={
tikz+={%
\path [\forestoption{edge}, my arc] (!1) [out=-35, in=180] to (!2.west) (!2.east) [out=0, in=-145] to (!3);
}
},
my arcs/.code={%
\tikzset{%
my arc/.style={#1},
}
},
}
\tikzset{%
my circle/.style={draw, circle},
my arc/.style={},
}
\begin{document}
\begin{forest}
for tree={
l sep=4em,
s sep=8em,
auto edge label,
nice empty nodes,
math content,
my arcs={densely dashed},
}
[P_1, my circle, arc through
[, edge label={x=0}]
[P_2, my circle, edge label=x
[{c-x,x}, edge label=Y]
[{0,0}, edge label=N]
]
[, edge label={x=c}]
]
\end{forest}
\end{document}
ПРАВКА В СТОРОНУ
Эта версия только дляКлеман. Хотя это больше, чем 546 символов, это все еще только 644. И Кайл делает код TikZ-only 563 символами, так что, вероятно, моя статистика считает символы по-другому.
Лично я не считаю это преимуществом, но что поделать.
Это не очень прозрачно, поэтому я не рекомендую его использовать.
Однако способ рисования дуги гораздо аккуратнее, чем мой предыдущий код. Что я, вероятно, сделаю, так это буду основываться arc below
на этом методе, а не использовать библиотеку through
.
Основная экономия символов достигается за счет устранения автоматизации. Метки краев больше не размещаются автоматически в соответствии с положением дочернего элемента относительно его братьев и сестер. Поэтому, если вы добавляете узел, вам нужно проверить, left
должны ли какие-либо s стать right
s или наоборот. Более того, для кругов не используется стиль, что снижает гибкость и удобство поддержки кода. Наконец, для содержимого узлов используются знаки доллара, а не math content
потому, что math content,
содержит больше символов, чем число, необходимое для назначения пары знаков доллара каждому узлу, которому они нужны.
По иронии судьбы, рисунок дуги на самом деле теперь используетлесфункции более значимы, а функции TikZ — менее значимы. ( y()
используется с оболочкой pgfmath для получения информации, необходимой для дуги, вместо того, чтобы полагаться на библиотеку through
.)
\documentclass{standalone}
\usepackage{forest}
\usetikzlibrary{calc}
\begin{document}
\begin{forest}
ey/.style={shape=coordinate,no edge},
elr/.style 2 args={edge label={node[midway,outer sep=1.5mm,#1]{$#2$}}},
el/.style={elr={left}{#1}},
er/.style={elr={right}{#1}},
for tree={l sep=4em,s sep=8em,calign=fixed edge angles}
[$P_1$,draw,circle
[,el={x=0},ey]
[$P_2$,draw,circle,er=x,anchor=north,before drawing tree={TeX/.wrap pgfmath arg={\gdef\rs{#1}}{y("!u")-y()}},tikz={\draw(!u)--($(!u)!1!-35:(.north)$)arc(235:305:\rs pt)--(!u);}
[${c-x,x}$,el=Y]
[${0,0}$,er=N]]
[,er={x=c},ey]]
\end{forest}
\end{document}
решение2
Я знаю, что вы хотите придерживаться, forest
но пока вы ждете ответа, вы можете попытаться понять способ бедняка рисовать вашу схему. Это не так уж и сложно:
Давайте нарисуем P1
где-нибудь
\node[circle,draw] (P1) {$P_1$};
Давайте нарисуем P2
на известном расстоянии внизуP1
\node[circle, draw, on grid, below = 2cm of P1, anchor=north] (P2) {$P_2$};
с on grid
и anchor=north
мы заставляем 2cm
расстояние между центром P1 и севером P2. Нам это понадобится для идеальной дуги.
Теперь мы можем провести границу между P1
иP2
\draw (P1)-- node[right]{$x$} (P2);
Далее, решите, какие углы вы хотите для левого и правого потомков. Длина руки будет 2cm
начинаться от P1
центра. В конце правого потомка мы рисуем дугу, потому что мы знаем начальный угол, конечный угол и радиус.
\draw (P1) -- node[right] {$x=c$} ++ (-60:2cm) arc (-60:-120:2cm);
\draw (P1) -- node[left] {$x=0$} ++ (240:2cm);
И заканчиваем с P2
детьми, используя похожие команды:
\draw (P2) -- node[right] {$N$} ++ (-60:2cm) node[below] {$0,0$};
\draw (P2) -- node[left] {$Y$} ++ (240:2cm) node[below] {$c-x,x$};
Вот и все. Результат:
и полный код:
\documentclass[tikz,border=2mm]{standalone}
\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}
\draw (-2,-2) grid (2,2);
\node[circle,draw] (P1) {$P_1$};
\node[circle, draw, on grid, below = 2cm of P1, anchor=north] (P2) {$P_2$};
\draw (P1)-- node[right]{$x$} (P2);
\draw (P1) -- node[right] {$x=c$} ++ (-60:2cm) arc (-60:-120:2cm);
\draw (P1) -- node[left] {$x=0$} ++ (240:2cm);
\draw (P2) -- node[right] {$N$} ++ (-60:2cm) node[below] {$0,0$};
\draw (P2) -- node[left] {$Y$} ++ (240:2cm) node[below] {$c-x,x$};
\end{tikzpicture}
\end{document}
решение3
- "Белое пространство" - это не более чем пустые узлы. Даже если вы не добавили текст, узлы создаются, тем самым, в свою очередь, создавая белое пространство. Чтобы решить эту проблему, просто добавьте
coordinate
к опциям для этих узлов, напримерname=1, coordinate,...
- Решено следующим пунктом.
- Что касается углов, добавьте
calign=fixed edge angles
своиfor tree={}
варианты. - Я не могу найти способ сделать это, даже в руководстве. Мне кажется, я упускаю что-то очевидное.
В любом случае, вот текущий результат:
решение4
Редактировать: istgame
версия 2.0
Сistgame
В версии 2.0 вы можете иметь больше элементов управления для континуума ветвей типа дуги:
\documentclass{standalone}
\usepackage{istgame}
\begin{document}
\begin{istgame}[->,font=\footnotesize]
\cntmdistance{15mm}{30mm}
\cntmAistb[->]{x=0}[al]{x=1}[ar]
\cntmApreset[ultra thin]<1.5>
\istrootcntmA(0)[null node]{1}
\istbA{x}[r]
\endist
\xtdistance{10mm}{20mm}
\istroot(1)(0-1)[null node]<45>{2}
\istb{Y}[l]{c-x,x}
\istb{N}[r]{0,0}
\endist
\end{istgame}
\end{document}
Оригинальный ответ
Это еще одно решение, позволяющее получить желаемую форму игры ultimatum
, используяistgame
пакет. (Еще один способ нарисовать игру ультиматум вы найдете в документе пакета.) Поскольку среда istgame
почти такая же, как tikzpicture
, вы можете использовать tikz macros
внутри istgame
среды.
\documentclass{standalone}
\usepackage{istgame}
\begin{document}
\begin{istgame}[->,font=\footnotesize]
\istroot(0)[null node]{1}+15mm..15mm+
\istb{x=0}[l]
\istb<level distance=1.42*15mm>{x}[r]
\istb{x=c}[r]
\endist
\xtdistance{10mm}{20mm}
\istroot(1)(0-2)[null node]<45>{2}
\istb{Y}[l]{c-x,x}
\istb{N}[r]{0,0}
\endist
\draw[-,ultra thin,tension=1] plot [smooth] coordinates {(0-1)(0-2)(0-3)};
\end{istgame}
\end{document}