在使用 TikZ 制作树形图时。假设我有以下节点
\node(a) {\(A\)};
\node(b1) at ($(a)+(4,1)$){\(B_1\)};
\node(b2) at ($(b1)+(0,-1)$){\(B_2\)};
\node(c) at ($(b1)+(1,-0.9)$){\(C\)};
\node(d) at ($(c)+(2,0)$){\(D\)};
\node(e) at ($(d)+(1,0.5)$){\(E\)};
我想建立以下联系
(a)
到(b1)
(a)
到(b2)
(b1)
到(d)
(b2)
到(e)
但我希望没有或尽可能少的连接与另一个节点或另一个连接重叠(没有改变节点的位置)。
如果我在节点之间画直线
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\node(a) {\(A\)};
\node(b1) at ($(a)+(4,1)$){\(B_1\)};
\node(b2) at ($(b1)+(0,-1)$){\(B_2\)};
\node(c) at ($(b1)+(1,-0.9)$){\(C\)};
\node(d) at ($(c)+(2,0)$){\(D\)};
\node(e) at ($(d)+(1,0.5)$){\(E\)};
\draw[->] (a) to (b1);
\draw[->] (a) to (b2);
\draw[->] (b1) to (d);
\draw[->,red] (b2) to (e);
\end{tikzpicture}
\end{document}
我最终得到了一个重叠C和线的重叠₁和德(红线):
另外,如果我把线条弄得有点弯曲
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\node(a) {\(A\)};
\node(b1) at ($(a)+(4,1)$){\(B_1\)};
\node(b2) at ($(b1)+(0,-1)$){\(B_2\)};
\node(c) at ($(b1)+(1,-0.9)$){\(C\)};
\node(d) at ($(c)+(2,0)$){\(D\)};
\node(e) at ($(d)+(1,0.5)$){\(E\)};
\draw[->] (a) to[out=20,in=180] (b1);
\draw[->] (a) to[out=0,in=180] (b2);
\draw[->] (b1) to[out=0,in=160] (d);
\draw[->,red] (b2) to[out=0,in=180] (e);
\draw[->,red,dashed] (b2) to[out=-40,in=240] (e);
\end{tikzpicture}
\end{document}
我得到了同样的重叠:
我所追求的是一个像虚线红线那样不重叠任何东西的结果但没有必须手动设置线路进入和退出节点的角度。
简而言之,除了手动改变线条的角度之外,有没有办法避免节点和其他线条的重叠,即我可以告诉 TikZ 避免在其他节点和线条上绘制而是在它们周围绘制?
答案1
我强烈怀疑一般问题无法通过 TeX/TikZ 解决。这是基于这样的事实,即有像 graphviz 这样的程序有非常复杂的例程来解决这类问题。此外,我认为一般来说这是不可能的,因为会出现没有解决方案且没有交叉的配置。
因此,我要提供的是以下代码。这定义了一条to
从起点到终点经过特定点的路径。目的是让您使用它来避开某个节点,方法是说您希望您的路径经过要避开的节点的特定角落;因此我们将该节点用作要避开的区域。
为了使问题更容易处理,我使用了与曲线所使用的曲线不同的曲线系列to[out=angle, in=angle]
。
以下是概念验证代码:
\documentclass{standalone}
%\url{http://tex.stackexchange.com/q/27899/86}
\usepackage{tikz}
\usetikzlibrary{calc}
\makeatletter
\tikzset{
through point/.style={
to path={%
\pgfextra{%
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax
\pgfmathsetmacro{\pt@sx}{\pgf@x * 0.03514598035}%
\pgfmathsetmacro{\pt@sy}{\pgf@y * 0.03514598035}%
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\pgfmathsetmacro{\pt@ax}{\pgf@x * 0.03514598035 - \pt@sx}%
\pgfmathsetmacro{\pt@ay}{\pgf@y * 0.03514598035 - \pt@sy}%
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
\pgfmathsetmacro{\pt@ex}{\pgf@x * 0.03514598035 - \pt@sx}%
\pgfmathsetmacro{\pt@ey}{\pgf@y * 0.03514598035 - \pt@sy}%
\pgfmathsetmacro{\pt@len}{\pt@ex * \pt@ex + \pt@ey * \pt@ey}%
\pgfmathsetmacro{\pt@t}{(\pt@ax * \pt@ex + \pt@ay * \pt@ey)/\pt@len}%
\pgfmathsetmacro{\pt@t}{(\pt@t > .5 ? 1 - \pt@t : \pt@t)}%
\pgfmathsetmacro{\pt@h}{(\pt@ax * \pt@ey - \pt@ay * \pt@ex)/\pt@len}%
\pgfmathsetmacro{\pt@y}{\pt@h/(3 * \pt@t * (1 - \pt@t))}%
}
(\tikztostart) .. controls +(\pt@t * \pt@ex + \pt@y * \pt@ey, \pt@t * \pt@ey - \pt@y * \pt@ex) and +(-\pt@t * \pt@ex + \pt@y * \pt@ey, -\pt@t * \pt@ey - \pt@y * \pt@ex) .. (\tikztotarget)
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\fill (0,0) circle[radius=2pt];
\fill (2,1) circle[radius=2pt];
\fill (5,5) circle[radius=2pt];
\draw
(0,0) to[through point={(2,1)}] (5,5);
\end{tikzpicture}
\end{document}
结果:
如果我们将其放入您的代码中,我们会得到以下内容,其中绿行是我的代码添加的:
代码如下:
\documentclass{standalone}
%\url{http://tex.stackexchange.com/q/27899/86}
\usepackage{tikz}
\usetikzlibrary{calc}
\makeatletter
\tikzset{
through point/.style={
to path={%
\pgfextra{%
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax
\pgfmathsetmacro{\pt@sx}{\pgf@x * 0.03514598035}%
\pgfmathsetmacro{\pt@sy}{\pgf@y * 0.03514598035}%
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\pgfmathsetmacro{\pt@ax}{\pgf@x * 0.03514598035 - \pt@sx}%
\pgfmathsetmacro{\pt@ay}{\pgf@y * 0.03514598035 - \pt@sy}%
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
\pgfmathsetmacro{\pt@ex}{\pgf@x * 0.03514598035 - \pt@sx}%
\pgfmathsetmacro{\pt@ey}{\pgf@y * 0.03514598035 - \pt@sy}%
\pgfmathsetmacro{\pt@len}{\pt@ex * \pt@ex + \pt@ey * \pt@ey}%
\pgfmathsetmacro{\pt@t}{(\pt@ax * \pt@ex + \pt@ay * \pt@ey)/\pt@len}%
\pgfmathsetmacro{\pt@t}{(\pt@t > .5 ? 1 - \pt@t : \pt@t)}%
\pgfmathsetmacro{\pt@h}{(\pt@ax * \pt@ey - \pt@ay * \pt@ex)/\pt@len}%
\pgfmathsetmacro{\pt@y}{\pt@h/(3 * \pt@t * (1 - \pt@t))}%
}
(\tikztostart) .. controls +(\pt@t * \pt@ex + \pt@y * \pt@ey, \pt@t * \pt@ey - \pt@y * \pt@ex) and +(-\pt@t * \pt@ex + \pt@y * \pt@ey, -\pt@t * \pt@ey - \pt@y * \pt@ex) .. (\tikztotarget)
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\node(a) {\(A\)};
\node(b1) at ($(a)+(4,1)$){\(B_1\)};
\node(b2) at ($(b1)+(0,-1)$){\(B_2\)};
\node(c) at ($(b1)+(1,-0.9)$){\(C\)};
\node(d) at ($(c)+(2,0)$){\(D\)};
\node(e) at ($(d)+(1,0.5)$){\(E\)};
\draw[->] (a) to[out=20,in=180] (b1);
\draw[->] (a) to[out=0,in=180] (b2);
\draw[->] (b1) to[out=0,in=160] (d);
\draw[->,red] (b2) to[out=0,in=180] (e);
\draw[->,red,dashed] (b2) to[out=-40,in=240] (e);
\draw[->,green] (b2) to[through point={(d.south east)}] (e);
\end{tikzpicture}
\end{document}
正如我在开始时所说的,我认为一般问题不会(轻易)解决(我很高兴被证明是错的......)。因此,为了解决这个问题,我需要您(用户)决定必须移动哪些路径,并决定将其移动到哪一侧。然后代码会计算出一条不错的(我希望!)路径。
代码解释:我们想要一条从起点到终点经过特定点的贝塞尔曲线。一般来说,这需要求解三次方程,虽然可行,但并不轻松。因此,我们简化了贝塞尔曲线。我们来看一条贝塞尔曲线,a (1 - t)^3 + 3 bt (1 - t)^2 + 3 ct^2 (1 - t) + dt^3并变换坐标,使得A位于原点,d位于(1,0)。然后如果我们选择X坐标b和C成为1/3, 这X-投影是线性的。这意味着很容易找出贝塞尔曲线经过特定垂直线的时间。
为了让它经过一个特定的点,比如说(p,q)和p∈(0,1),我们只需t=p。然后我们还假设是-坐标b和C是一样的(比如说H),由此我们得到公式h = q / (3p (1-p))。
上面的代码做到了这一点,只需要我们在不同的坐标系中工作,所以我们会进行大量的正交投影。