通过两个平行箭头连接 Tikz 节点

通过两个平行箭头连接 Tikz 节点

我刚刚开始学习使用 Tikz,对其看似无限的选项和可能性有点迷失。

我试图做的是可视化几个 Matlab 函数交互(哪个调用哪个,输入和输出是什么)。由于只有大约六个子程序,我将每个子程序手动放置为一个节点。这是一个例子:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shapes,positioning}

\begin{document}

\begin{tikzpicture}[auto]
\node           [rounded rectangle,draw]        (node1)     {Node 1};
\coordinate [above=of node1]                                (input);
\node           [rounded rectangle,draw]    at  (-10:6cm) (node2)       {Node 2};
\draw           [->,very thick]             ([xshift=0.1cm]  input.south)  -- 
                node {right}                ([xshift=0.1cm]  node1.north);
\draw           [->,very thick]             ([xshift=-0.1cm] node1.north) -- 
                node {left}                 ([xshift=-0.1cm] input.south);
\draw                                            (node1) -- (node2); 
\end{tikzpicture}

\end{document}

输出

现在我想将我使用 xshift 创建的这些双进/出箭头(可能做得很别扭)应用于所有节点连接,因此在此示例中也应用于节点 1 和节点 2 之间的连接。理想情况下,这两个箭头之间的空间应该相同,与它们的角度无关,并且它们可以很好地连接到节点的外线。有什么想法可以实现这一点吗?我发现类似的问题只使用了矩形和正交线。

答案1

最小代码

\documentclass[tikz]{standalone}
\usetikzlibrary{arrows.meta}

\begin{document}

\pgfkeys{
  /pgf/arrow keys/.cd,
  shear/.store in=\pgfarrowshear,
  US/.style={
    length = +1.05pt 1.925 1,
    shear = 1.7pt,
  },
  UK/.style={
    length = +1.05pt 1.925 1,
    shear = -1.7pt,
  },
}
\makeatletter
\newdimen\pgfarrowshear
\let\oldmacro\pgf@arrow@drawer@shift
\def\pgf@arrow@drawer@shift{\pgftransformyshift\pgfarrowshear\oldmacro}

\begin{tikzpicture}
    \path(1,3)node(A){A}(3,1)node(B){B};;
    \draw[double,double distance=3pt,{<[US]}-{>[US]},bend left](A)to(B){};
    \draw[double,double distance=3pt,{<[UK]}-{>[UK]},bend left](B)to(A){};
\end{tikzpicture}

\end{document}

一步步

不失一般性,假设箭头从左指向右。

pgfcorearrows.code.tex,钛z 使用移动箭头尖端\pgf@arrow@drawer@shift,定义为

\def\pgf@arrow@drawer@shift#1#2#3{% tip end, back end, line end, sep
  \pgf@xb#2\pgftransformxshift{-\pgf@xb}%
  \pgf@xc#1%
  \advance\pgf@xc by\pgfarrowsep%
  \advance\pgf@xc by-\pgf@xb%
}

这一步是必要的,因为有时我们有shorten >=sep=(以防有更多提示)。但是没有y shift应用,因为逻辑上箭头提示与主路径对齐。

因此,我们有一个肮脏的步骤,它将此宏重写如下

\def\pgf@arrow@drawer@shift#1#2#3{
  \pgftransformyshift\pgfarrowshear% add this line
  \pgf@xb#2\pgftransformxshift{-\pgf@xb}%
  \pgf@xc#1%
  \advance\pgf@xc by\pgfarrowsep%
  \advance\pgf@xc by-\pgf@xb%
}

接下来我们想让shear=它像 一样sep=。所以我修改了sep/.code

\pgfkeys{
  /pgf/arrow keys/.cd,
  shear/.code={
    \pgfarrowsthreeparameters{#1}%
    \expandafter\pgfarrowsaddtooptions\expandafter{\expandafter\pgfarrowslinewidthdependent\pgfarrowstheparameters\pgfarrowshear\pgf@x}%
  },
  shear/.default = +0pt -.5 -.5
}

因此,现在我们可以通过以下方式实现以下目标:[-{>[shear=0pt .5 .5]>[shear]>[shear=0pt .5 .5]>[shear]}]

线宽相关

然而,\pgfarrowslinewidthdependent永远不会产生正确数量的y shift。如果你仔细看手册,它会计算

wi = inner line width
wo = outer line width
wt = total line width = inner + 2*outer
w  = #3*wo + (1-#3)*wt = weighted line width
return #1 + #2*w

也就是说

#1 + #2 * [ (1-#3)*wi + (2-#3)*wo ]

我们想要的是

0 + .5*wi + .5*wo

这导致

#1 = 0
#2 = 0
#3 = ∞

我不知道为什么 TiZ 就这么做吧。无论如何,我们可以自己做。

\def\pgfarrowslinewidthdependentnew#1#2#3{%
  \pgf@x#1%
  \ifdim\pgfinnerlinewidth>0pt%
    \pgf@arrows@inner@line@width@depnew{#2}{#3}%
  \else%  
    \advance\pgf@x by#2\pgflinewidth%
  \fi%
}
\def\pgf@arrows@inner@line@width@depnew#1#2{%
  % #1 * outer line width + #2 * inner line width = our new one = the following
  % (#1/2) * full line width + (#2-#1/2) * inner line width)
  % Compute "real" line width
  \[email protected]\pgflinewidth%
  \pgf@xa#1\pgf@xa%
  \advance\pgf@x by\pgf@xa%
  \pgf@xa\pgfinnerlinewidth%
  \[email protected]\pgf@xa%
  \advance\pgf@x by#2\pgf@xa%
  \advance\pgf@x by-#1\pgf@xb%
}

为了方便

我们定义

\pgfkeys{
  /pgf/arrow keys/.cd,
  Bidirectional/.style={
    length = +1.05pt 1.925 1,
    shear
  }
}

所以我们可以

\begin{tikzpicture}
    \path(0,4)(4,0);
    \node(A)at(1,3){A};
    \node(B)at(3,1){B};
    \draw[double,double distance=3pt,{<[Bidirectional]}-{>[Bidirectional]},bend left]
        (A)to(B){};
\end{tikzpicture}

代码

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows.meta}

\begin{document}

\makeatletter

\pgfkeys{
  /pgf/arrow keys/.cd,
  Bidirectional/.style={
    length = +1.05pt 1.925 1,
    shear
  },
  shear/.code={
    \pgfarrowsthreeparameters{#1}%
    \expandafter\pgfarrowsaddtooptions\expandafter{\expandafter\pgfarrowslinewidthdependentnew\pgfarrowstheparameters\pgfarrowshear\pgf@x}%
  },
  shear/.default = +0pt -.5 -.5
}
\newdimen\pgfarrowshear
\pgfarrowshear0pt
\def\pgfarrowslinewidthdependentnew#1#2#3{%
  \pgf@x#1%
  \ifdim\pgfinnerlinewidth>0pt%
    \pgf@arrows@inner@line@width@depnew{#2}{#3}%
  \else%  
    \advance\pgf@x by#2\pgflinewidth%
  \fi%
}
\def\pgf@arrows@inner@line@width@depnew#1#2{%
  % #1 * outer line width + #2 * inner line width = our new one = the following
  % (#1/2) * full line width + (#2-#1/2) * inner line width)
  % Compute "real" line width
  \[email protected]\pgflinewidth%
  \pgf@xa#1\pgf@xa%
  \advance\pgf@x by\pgf@xa%
  \pgf@xa\pgfinnerlinewidth%
  \[email protected]\pgf@xa%
  \advance\pgf@x by#2\pgf@xa%
  \advance\pgf@x by-#1\pgf@xb%
}
\def\pgf@arrow@drawer@shift#1#2#3{
  \pgftransformyshift\pgfarrowshear%
  \pgf@xb#2\pgftransformxshift{-\pgf@xb}%
  \pgf@xc#1%
  \advance\pgf@xc by\pgfarrowsep%
  \advance\pgf@xc by-\pgf@xb%
}

\begin{tikzpicture}
    \draw[-{>[sep=1pt]>}](1,1)->(3,1);
\end{tikzpicture}

\begin{tikzpicture}[>={Classical TikZ Rightarrow[length = +1.05pt 1.925 1]}]
    \draw[double distance=3pt,-{>[shear=0pt .5 .5]>[shear]>[shear=0pt .5 .5]>[shear]}]
        (1,1)->(3,1);
\end{tikzpicture}

\begin{tikzpicture}
    \path(0,4)(4,0);
    \node(A)at(1,3){A};
    \node(B)at(3,1){B};
    \draw[double,double distance=3pt,{<[Bidirectional]}-{>[Bidirectional]},bend left]
        (A)to(B){};
\end{tikzpicture}

\end{document}

答案2

碰巧的是,我自己找到了一个解决方案,使用 calc 和 intersections 库:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shapes,positioning,calc,intersections}

\begin{document}

\begin{tikzpicture}[auto,>=stealth]
\node           [rounded rectangle,draw,name path=node1](node1)     {Node 1};
\coordinate     [above=of node1]                                    (input);
\node           [rounded rectangle,draw,name path=node2]    at  (-10:6cm) (node2)   {Node 2};
\draw           [->,very thick]                         ([xshift=2pt]  input.south)  -- 
                node {right}                            ([xshift=2pt]  node1.north);
\draw           [->,very thick]                         ([xshift=-2pt] node1.north) -- 
                node {left}                             ([xshift=-2pt] input.south);

\path       [name path=node12]                      let \p1 = ($(node2)-(node1)$) 
             in       (node1)       ($(node1)!2pt!($(node1)+(-\y1,\x1)$)$)       to       +(\p1);
\path       [name path=node21]                      let \p1 = ($(node2)-(node1)$) 
             in       (node1)       ($(node1)!-2pt!($(node1)+(-\y1,\x1)$)$)      to       +(\p1);

\node       [name intersections={of=node1 and node12}]     (start) at (intersection-1){};
\draw       [->,very thick,name intersections={of=node2 and node12}] 
            (start.center) -- node[sloped] {in}  (intersection-1);
\node       [name intersections={of=node2 and node21}]     (start) at (intersection-1){};
\draw       [->,very thick,name intersections={of=node1 and node21}] 
            (start.center) -- node[sloped] {out} (intersection-1);

\end{tikzpicture}

\end{document}

首先,我创建两条与节点中心直接连接平行的路径。我通过使用 let 命令计算节点中心之间的差异向量,将起点沿正交方向移动(在这里我可以定义箭头之间的空间量),然后沿着之前计算的向量移动路径。

接下来,我计算该路径与节点 1 的交点并将其坐标保存为节点(我尝试仅使用坐标,但由于未知原因,它似乎只适用于节点......)。为了能够做到这一点,我必须首先为节点和线提供“路径名称”(请参阅​​交点库的文档)。使用该节点并计算与节点 2 的交点,然后我可以绘制一条线,该线很好地连接了沿平行线/箭头的节点边缘。对于许多节点,将操作包装到 foreach 循环中应该不会太难。

在此处输入图片描述

虽然这个方法相当简短,但可能是一种相当“冗长”的方法。任何有关改进的建议都非常感谢!

相关内容