10

I am just starting to learn using Tikz and am a little lost within its seemingly infinite amount of options and possibilities.

What I am trying to do is visualize a few Matlab function interaction (which one calls which one, what is the in- and output). Since there are only about six subroutines, I place each one manually as a node. This is an example:

\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}

Output

Now I would like to apply these double in/out arrows I (probably pretty awkwardly) created using xshift to all node connections, so in this example also for the connection between Node 1 and Node 2. Ideally, the space between these two arrows would be the same, independant of their angle and they would nicely connect to the outer line of the nodes. Any idea how I could achieve this? Similar questions I have found only used rectangle and orthogonal lines.

  • The last edit about the anchors in tex.stackexchange.com/a/32869/46716 might help you. – Maarten Dhondt Mar 11 '15 at 0:51
  • For really parallel lines, tikz draws a thick line then draws a while (or some other color) line down the center. All you need is to define a new arrowhead that is lopsided. – John Kormylo Mar 11 '15 at 1:47
  • @MaartenDhondt: I have looked up the edit but at a glance can't see the connection to my question. Could you elaborate? – Niclas Mar 11 '15 at 12:26
17

A Minimal Code

\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}

Step by Step

Without lost of generality, assume arrows are pointing from left to right.

In pgfcorearrows.code.tex, Tikz shifts arrow tips using \pgf@arrow@drawer@shift, defined as

\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%
}

This step is necessary because sometimes we have shorten >= or sep= (in case there are more tips). But no y shift is applied because logically arrow tips are aligned to the main path.

So we have a dirty step which rewrites this macro as follows

\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%
}

Next we want to make shear= acts like sep=. So I modified the code of 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
}

So now we can achieve the following by something like [-{>[shear=0pt .5 .5]>[shear]>[shear=0pt .5 .5]>[shear]}]

Line Width Dependent

However, \pgfarrowslinewidthdependent will never produce the right amount of y shift. If you look at the manual carefully, it calculates

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

Which is, equivalently

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

While what we want is

0 + .5*wi + .5*wo

It leads to

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

I do not know why TikZ do so. Anyway, we can do it ourself.

\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
  \pgf@xa.5\pgflinewidth%
  \pgf@xa#1\pgf@xa%
  \advance\pgf@x by\pgf@xa%
  \pgf@xa\pgfinnerlinewidth%
  \pgf@xb.5\pgf@xa%
  \advance\pgf@x by#2\pgf@xa%
  \advance\pgf@x by-#1\pgf@xb%
}

For Convenience

We define

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

So we can have

\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}

Code

\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
  \pgf@xa.5\pgflinewidth%
  \pgf@xa#1\pgf@xa%
  \advance\pgf@x by\pgf@xa%
  \pgf@xa\pgfinnerlinewidth%
  \pgf@xb.5\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}
  • Wow, I really appreciate your enormous effort! But as I said, I just started using Tikz (and would not call myself a latex expert either) and this is way over my head. I had (naively) hoped for a solution that would not require that amount of low level pgf coding. I don't feel very confident in just copying your code and for my few nodes it's just not worth it (although I might need it again one time). But thank you, others might profit more from this solution! – Niclas Mar 11 '15 at 12:32
  • 1
    @Niclas I did not know what did you expect at the beginning. But if you are asking for a minimal one, my first paragraph may fit your requirement. – Symbol 1 Mar 11 '15 at 13:44
1

As it happens, I have found a solution myself, using the calc and intersections libraries:

\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}

First, I create two paths which run parallel to the direct connection of the node centers. I do this by calculating the difference vector between the node centers using the let command, shift my starting point in the orthogonal direction (here I can define the amount of space between the arrows) and then move the path along the former calculated vector.

Next I calculate the intersection point of this path with node 1 and save its coordinates as a node (I tried to just use a coordinate, but for unknown reasons it seems to work only with a node...). To be able to to this I had to give the nodes and lines "path names" in the first place (see documentation of intersections lib). Using the node and calculating the intersection point with node 2, I can then draw the line, which nicely connects the edges of the nodes along parallel lines/arrows. For many nodes, it should not be too difficult to wrap the operations into a foreach loop.

enter image description here

This is probably, although reasonably short, a pretty "verbose" way to do it. Any hints on improvements are very appreciated!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.