# Automatically positioning node shape on “to path” in Tikz (for outlined, transparent arrows)

It seems to be a bit tricky to draw outlined, transparent arrows between nodes in Tikz.

Here is my attempt at solving the simple case where the path is a straight, vertical line:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc,shapes.arrows}

\begin{document}

\begin{tikzpicture}[
fat arrow/.style={draw,red,
every to/.style={
to path={
let \p1 = ($(\tikztotarget)-(\tikztostart)$),
\n2 = {veclen(\x1,\y1)} in
-- (\tikztotarget)
node[draw, blue, pos=.5, inner sep=0, text width=.2cm,
minimum height=\n2, single arrow, shape border rotate=270]
{} \tikztonodes}
}}]
\fill[yellow] (-.2,.5) rectangle (2.2,1.5);
\path (0,2) node[draw] (A) {A}
(0,0) node[draw] (B) {B}
(2,2) node[draw] (C) {C}
(2,0) node[draw] (D) {D}
;
\path[fat arrow] (A) to (B);
\path[fat arrow] (C.south) to (D.north);
\end{tikzpicture}

\end{document}


It gives this result:

But I would like the blue arrows to be exactly between the node boxes.

The path in red from A to B goes between the edges of the two nodes, but the height of the blue node on the "to" path is calculated from B.center - A.center. Is there any "simple" way to fix this so that the arrow fits between the two nodes?

For the arrow between C and D, by adjusting the pos=.5, I can fit the arrow exactly between the two nodes. Why is this necessary? Can the correct offset be calculated automatically?

Is there a "simple" way to set the value of shape border rotate automatically?

• Better: remove pos=0.5 and set anchor=tip. – Torbjørn T. May 17 '18 at 20:11
• @Kpym: the observation that the center of the shape may not be the real center was a good one, though I'm not good enough at pgf to confirm it against the code. – tbrk May 17 '18 at 20:16
• You can use it not only for vertical arrows with -- node[pos=.999,draw, blue, inner sep=1mm, anchor=tip, minimum height=\n2, single arrow,sloped]{} (\tikztotarget) – Kpym May 17 '18 at 20:26
• Just remembered, another possibility might be something along the lines of tex.stackexchange.com/questions/406162/…. – Torbjørn T. May 17 '18 at 20:42
• I tried to adapt that solution before posting but I had some trouble using it with beamer and [french]babel (though, I can't reproduce them in a standalone example). – tbrk May 18 '18 at 4:12

This implements rotation (had to use rotate, not shape border rotate, don't know why) and sets the position correctly (by removing pos=0.5 and adding anchor=tip). I used inner sep to set the width of the arrow, because text width/minimum width didn't work (don't know why).

\documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{calc,shapes.arrows}

\begin{document}

\begin{tikzpicture}[
fat arrow/.style={draw,red,
every to/.style={
to path={
let \p1 = ($(\tikztotarget)-(\tikztostart)$),
\n1 = {veclen(\x1,\y1)},
\n2 = {mod(scalar(atan2(\y1,\x1))+360, 360)} % calculate angle in range [0,360)
in
-- (\tikztotarget)
node[draw, blue,
inner xsep=0pt,inner ysep=5pt, % use inner ysep to set width
minimum height=\n1-\pgflinewidth,
single arrow,
rotate=\n2, % not shape border rotate, because that for some reason didn't work
anchor=tip, % anchor=tip added, pos=0.5 removed
#1          % options passed to fat arrow style are added here
]
{} \tikztonodes}
}},
fat arrow/.default= % set empty default for argument to fat arrow
]
\fill[yellow] (-.2,.5) rectangle (2.2,1.5);
\path (0,2) node[draw] (A) {A}
(1,0) node[draw] (B) {B}
(2,2) node[draw] (C) {C}
(2,0) node[draw] (D) {D}
;
\path[fat arrow] (A.south east) to (B.north west);
\path[fat arrow] (C.south) to (D.north);
\end{tikzpicture}

\end{document}


Assuming you only use this between nodes, Kpym's suggestion in the comment below can be used to figure out the start/end anchors of the arrow. With the code below,

\path[fat arrow] (A) to (B);
\path[fat arrow] (C) to (D);


gives:

\documentclass[border=5mm]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc,shapes.arrows}

\begin{document}

\begin{tikzpicture}[
fat arrow/.style={draw,red,
every to/.style={
to path={
let \p1 = ($(\tikztotarget)-(\tikztostart)$),
\n1 = {int(mod(scalar(atan2(\y1,\x1))+360, 360))}, % calculate angle in range [0,360)
\p2 = ($(\tikztotarget.\n1+180)-(\tikztostart.\n1)$),
\n2 = {veclen(\x2,\y2)}
in
-- (\tikztotarget)
node[draw, blue,
inner xsep=0pt,inner ysep=5pt, % use inner ysep to set width
minimum height=\n2-\pgflinewidth,
single arrow,
rotate=\n1, % not shape border rotate, because that for some reason didn't work
anchor=tip, % anchor=tip added, pos=0.5 removed
#1          % arguments passed to fat arrow added here
]
at (\tikztotarget.\n1+180)
{} \tikztonodes}
}},
fat arrow/.default= % empty default for argument of fat arrow
]
\fill[yellow] (-.2,.5) rectangle (2.2,1.5);
\path (0,2) node[draw] (A) {A}
(1,0) node[draw] (B) {B}
(2,2) node[draw] (C) {C}
(2,0) node[draw] (D) {D}
;
\path[fat arrow] (A) to (B);
\path[fat arrow] (C) to (D);
\end{tikzpicture}

\end{document}


Following marmot's suggestion, I added the possibility of passing options to the arrow node, with an (optional) argument to fat arrow. I did this for both code blocks above. So for example, with

\path[fat arrow={densely dotted,thick,red}] (C) to (D);


you get

• You still have to explicitly state the start/end anchors though, I don't know how one can get around that. – Torbjørn T. May 17 '18 at 20:27
• As you have the angle you can use it to get the anchor positions of \tikztostart and \tikztotarget if you know that they are nodes. For example (\tikztostart.90) should work. – Kpym May 17 '18 at 20:30
• @Kpym Yes, good point. I edited my answer. – Torbjørn T. May 17 '18 at 20:39
• Super! It also works, for example, with double arrow (and pos=0.5), and cylinder (with anchor=top). Thank you @Kpym and @TorbjørnT. – tbrk May 18 '18 at 4:22

UPDATE: Completely switching gears and drawing the arrow as a decoration. (I also made the answer a bit more concise, I hope.)

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations,decorations.text} %  decorations.text just 4 fun
\pgfkeys{/tikz/.cd,
outlined arrow width/.store in=\OutlinedArrowWidth,
outlined arrow width=10pt,
outlined arrow step/.store in=\OutlinedArrowStep,
outlined arrow step=1pt,
outlined arrow length/.store in=\OutlinedArrowLength,
outlined arrow length=5pt,
}

\pgfdeclaredecoration{outlined arrow}{initial}
{% initial arrow butt
\state{initial}[width=\OutlinedArrowStep,next state=cont] {
\pgfmoveto{\pgfpoint{\OutlinedArrowStep}{\OutlinedArrowWidth/2}}
\pgfpathlineto{\pgfpoint{0.3\pgflinewidth}{\OutlinedArrowWidth/2}}
\pgfpathlineto{\pgfpoint{0.3\pgflinewidth}{-\OutlinedArrowWidth/2}}
\pgfpathlineto{\pgfpoint{1pt}{-\OutlinedArrowWidth/2}}
\pgfcoordinate{lastup}{\pgfpoint{1pt}{\OutlinedArrowWidth/2}}
\pgfcoordinate{lastdown}{\pgfpoint{1pt}{-\OutlinedArrowWidth/2}}
\xdef\marmotarrowstart{0}
}
\state{cont}[width=\OutlinedArrowStep]{
\ifdim\pgfdecoratedremainingdistance>\OutlinedArrowLength% continue the outlined path
\pgfmoveto{\pgfpointanchor{lastup}{center}}
\pgfpathlineto{\pgfpoint{\OutlinedArrowStep}{\OutlinedArrowWidth/2}}
\pgfcoordinate{lastup}{\pgfpoint{\OutlinedArrowStep}{\OutlinedArrowWidth/2}}
\pgfmoveto{\pgfpointanchor{lastdown}{center}}
\pgfpathlineto{\pgfpoint{\OutlinedArrowStep}{-\OutlinedArrowWidth/2}}
\pgfcoordinate{lastdown}{\pgfpoint{\OutlinedArrowStep}{-\OutlinedArrowWidth/2}}
\else
\pgflineto{\pgfpoint{-0.5\pgflinewidth}{\OutlinedArrowWidth}}
\pgflineto{\pgfpoint{-0.5\pgflinewidth}{-\OutlinedArrowWidth}}
\xdef\marmotarrowstart{1}
\else
\fi
\fi%
}
\state{final}[width=5pt]
{ % perhaps unnecessary but doesn't hurt either
\pgfmoveto{\pgfpointdecoratedpathlast}
}
}
\begin{document}

\begin{tikzpicture}[decoration=outlined arrow,font=\sffamily]
\path (0,0) node[draw] (A) {A}
(0,4) node[draw] (B) {B}
(4,0) node[draw] (C) {C}
(4,4) node[draw] (D) {D}
;
\fill[green] (-1,0.5) rectangle (2.5,1.5);
\draw(A.north west) -- (D.south east);
\draw[decorate,blue,opacity=0.5] (C) to (D);
\draw[decorate,red,opacity=0.5,line width=2pt,outlined arrow length=10pt] (A) to (B);
\draw[decorate,outlined arrow length=15pt] (A.east) to[out=0,in=-180] (D.west);
\fill[decoration={text along path, text={~here is some text inside an arrow},
raise=-2.5pt},decorate]
(A.east) to[out=0,in=-180] (D.west);
\end{tikzpicture}
\end{document}


This works for straight arrows and for curved arrows.

POSSIBLE IMPROVEMENTS: One may use the open arrow heads from the arrows.meta library and bending.

ORIGINAL ANSWER: There are already excellent posts on drawing outlined (or, more generally, two-colored) arrows, so you could just use them here. Then you can use opacity in the usual way.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc,shapes.arrows}
\usetikzlibrary{arrows.meta}
\tikzset{
my fat arrow/.style args={width #1 line width #2}{
-{Triangle[length=#1,width={3*#1}]},line width=#1,, % outer arrow
postaction={draw,-{Triangle[length={#1-2*#2},width={3*#1-4*sqrt(2)*#2}]},white,
line width={#1-2*#2},shorten <=#2,shorten >=#2,opacity=1}, % second arrow
}
}

\begin{document}

\begin{tikzpicture}
\path (0,0) node[draw] (A) {A}
(0,2) node[draw] (B) {B}
(2,0) node[draw] (C) {C}
(2,2) node[draw] (D) {D}
;
\draw[my fat arrow=width 3mm line width 1pt,blue,opacity=0.5] (C) to (D);
\draw[my fat arrow=width 3mm line width 0.9pt,red,opacity=0.5] (A) to (B);
\end{tikzpicture}

\end{document}


• This solution is great! It works with general paths and allows tricky effects (and it teaches a few things about custom decorations). In this case, I've accepted TorbjørnT.'s solution because it's closer to what I wanted to do with node shapes (and SO only lets me accept one), but I think this solution is a great reference for the "fat arrow problem" and one that I will use in other situations. Thank you. – tbrk May 18 '18 at 4:20
• +1 The bended arrow looks crazy. May you indicate which part of the code is needed for the bended arrow with the text inside? – Dr. Manuel Kuehner May 18 '18 at 4:44
• @Dr.ManuelKuehner The lines \draw[decorate,outlined arrow length=15pt] (A.east) to[out=0,in=-180] (D.west); \fill[decoration={text along path, text={~here is some text inside an arrow}, raise=-2.5pt},decorate] (A.east) to[out=0,in=-180] (D.west);. Most likely it's possible to write this in one command with some postaction but I did not try doing this. Why is it crazy? – user121799 May 18 '18 at 4:46
• It's at least crazy to me. I could not do it with MS Office for example and here it's just a couple of lines of code in a free software that some German math professor started on his own. And then there is you, using it like a magician. I really enjoy browsing this site :). – Dr. Manuel Kuehner May 18 '18 at 4:56