今天,我再次需要你的帮助来处理这个包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}
我们加载TikZthrough
库calc
以正确绘制圆弧。
\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={
来自 MWE。
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 个。而 Kile 使 TikZ 专用代码有 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$};
并且我们强制 P1 的中心和 P2 的北面之间有一个on grid
距离。我们需要它来形成一个完美的弧线。anchor=north
2cm
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}