# Add decoration to set of arrows with pgfplots

I have this code for a set of joined arrows. How can I add a wave effect to them so I can control the length and amplitude of the wave?

\documentclass{standalone}

\usepackage{pgfplots}

\pgfplotsset{compat=1.15}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{calc}

\tikzset{
myarrow/.style = {-{Triangle[length = 1.5mm, width = 1.5mm]}}
}

\begin{document}
\begin{tikzpicture}
\begin{axis}[
every axis plot post/.style = {
mark = -,
thick,
mark size = 10mm
},
xtick style = {
draw = none
},
xticklabels = {,,},
xmin = -0.9,
xmax = 1.1,
ymin = 15,
ymax = 20,
cycle list name = black white,
only marks,
x = 2cm,
y = 1cm,
every node near coord/.append style = {
font = \small,
yshift = -2.5mm, xshift = \myshift
}
]

nodes near coords = {\labelz},
visualization depends on = {%
value \thisrowno{2}\as\labelz%
},
visualization depends on = {%
value \thisrowno{3}\as\myshift%
}
0 15.183 a 15mm
0 18.300 b 15mm
0 19.011 c 15mm
};

\pgfmathsetmacro{\smallArrowParts}{3}
\pgfmathsetmacro{\largeArrowParts}{6}

\pgfplotsinvokeforeach{1,...,\largeArrowParts}{
\pgfmathsetmacro{\largeInc}{(18.300-15.183)/\largeArrowParts}
\draw [
draw = blue,
myarrow,
very thick
] (0.25, 18.300) -- (0.25, {18.300 - #1 * \largeInc});
}

\pgfplotsinvokeforeach{1,...,\smallArrowParts}{
\pgfmathsetmacro{\smallInc}{(19.011-18.300)/\smallArrowParts}
\draw [
draw = blue,
myarrow,
very thick
] (-0.25, 18.300) -- (-0.25, {18.300 + #1 * \smallInc});
}

\end{axis}
\end{tikzpicture}
\end{document}


Output:

Expected result:

You can draw arrows precisely following a path at a specific place using path decorations based on this answer. I define the blue curves using parametric representation in \addplot inside the axis environment. The sine function makes it easy to obtain the wave shape (\addplot[domain=...] ({0.25 - 0.2*sin(120*x)}, 18.300 - x)). The arrows are placed according to positions in the [0,1] range defined by the following two lists:

> \mylist=macro:
->0.16666,0.33333,0.5,0.66666,0.83333,1.0.

> \mylist=macro:
->0.33333,0.66666,1.0.


which are automatically built from your \smallArrowParts (3) and \largeArrowParts (6) by my \myBuildFractionList macro called twice:

1. \myBuildFractionList{\mylist}{\smallArrowParts}

2. \myBuildFractionList{\mylist}{\largeArrowParts}

I had to adapt the attach arrow style because we want to use position 1 (end of path) for the last arrow, as per your sketch, and the original style didn't support this. I also had to reduce a little bit the arrow sizes for the “small” part because the first arrow is very close to the start of the curve, which causes problems due to the way the attach arrow style works. I moved the mark = - and only marks options to apply only to the particular \addplot call that needs them, otherwise the blue curves would look very strange. Finally, I had to use a well-known \edef\x{...}\x trick to expand \mylist early enough, due to the rather peculiar way the axis environment of pgfplots works.

Note: there is a second version below with more knobs.

\documentclass[tikz,border=1mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usetikzlibrary{arrows.meta, decorations.markings, bending}

\newcommand*{\myBuildFractionList}[2]{%
\def#1{}%
\pgfplotsinvokeforeach{1,...,#2}{%
\pgfmathparse{##1 / (#2)}%
\edef#1{\ifx#1\empty\else#1,\fi \pgfmathresult}%
}%
}

\tikzset{
myarrow/size/.initial=1.5mm,
myarrow/.style = {-{Triangle[length = \pgfkeysvalueof{/tikz/myarrow/size},
width = \pgfkeysvalueof{/tikz/myarrow/size},
bend]}},
attach arrow/.style={
decoration={
markings,
mark=at position 0 with {\pgfextra{%
\pgfmathsetmacro{\tmpArrowTime}{%
\pgfkeysvalueof{/tikz/arc arrow/length}/(\pgfdecoratedpathlength)}%
\xdef\tmpArrowTime{\tmpArrowTime}}},
mark=at position {#1-3*\tmpArrowTime} with {\coordinate(@1);},
mark=at position {#1-2*\tmpArrowTime} with {\coordinate(@2);},
mark=at position {#1-1*\tmpArrowTime} with {\coordinate(@3);},
mark=at position {#1} with {\coordinate(@4);
\draw[myarrow] plot[smooth] coordinates {(@1) (@2) (@3) (@4)};},
},
postaction=decorate,
},
attach arrow/.default=0.5,
arc arrow/length/.initial=1.5mm,
}

\begin{document}
\begin{tikzpicture}
\begin{axis}[
xtick style = {
draw = none
},
xticklabels = {,,},
xmin = -0.9,
xmax = 1.1,
ymin = 15,
ymax = 20,
cycle list name = black white,
x = 2cm,
y = 1cm,
every node near coord/.append style = {
font = \small,
yshift = -2.5mm, xshift = \myshift
}
]

only marks,
mark = -,
thick,
mark size = 10mm,
nodes near coords = {\labelz},
visualization depends on = {%
value \thisrowno{2}\as\labelz%
},
visualization depends on = {%
value \thisrowno{3}\as\myshift%
}
0 15.183 a 15mm
0 18.300 b 15mm
0 19.011 c 15mm
};

\def\smallArrowParts{3}
\def\largeArrowParts{6}

\myBuildFractionList{\mylist}{\largeArrowParts}
\pgfmathsetmacro{\myYextent}{18.300-15.183}
\edef\temp{%
[draw=blue, very thick, domain=0:\noexpand\myYextent,
attach arrow/.list={\mylist}]
({0.25 - 0.2*sin(120*x)}, 18.300 - x);
}\temp

\myBuildFractionList{\mylist}{\smallArrowParts}
\pgfmathsetmacro{\myYextent}{19.011-18.300}
\edef\temp{%
[draw=blue, very thick, domain=0:\noexpand\myYextent,
/tikz/myarrow/size=1.2mm, % we need smaller arrows here
attach arrow/.list={\mylist}]
(-0.25, 18.300 + x);
}\temp
\end{axis}
\end{tikzpicture}
\end{document}


Update following the (now deleted?) comment:

If you also want to add waves to the upper part, a few things need to be scaled down:

• the attach arrow style uses 4 sample points along the path in order to determine how each arrow is oriented and bended; the length of the corresponding part of the path is determined by /tikz/arc arrow/length (this was the name in the answer I took the original style from). This needs to be small enough to provide a nice, regular curve that each arrow can follow, when the wave length is short.

• arrow sizes need to be adapted too, IMHO;

• I provide another list-building macro, \myBuildCenteredFractionList, in order to offset the arrows along the path so that the whole set of arrows is now centered in the upper curve (replace with \myBuildFractionList if you don't understand what I mean, and you should see).

I also added an argument to the myarrow style to make it more flexible (I use it in order to reverse the arrow directions in the upper curve). In short, the following code is similar to what precedes, but has more knobs. Also, we seem to be very close to the limits of pgfmath in terms of precision when drawing the small arrows. I had to fiddle with some of the parameters in order to avoid artifacts (in particular, keep the number of samples no greater than 23 for the upper curve).

Just thinking about it: maybe quiver plots could provide another way to draw the little arrows.

\documentclass[tikz,border=1mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usetikzlibrary{arrows.meta, decorations.markings, bending}

\makeatletter
\newcommand*{\myBuildFractionList}[2]{%
\def#1{}%
\pgfplotsinvokeforeach{1,...,#2}{%
\pgfmathparse{##1 / (#2)}%
\edef#1{\ifx#1\empty\else#1,\fi \pgfmathresult}%
}%
}

\newcommand*{\myBuildCenteredFractionList}[2]{%
\def#1{}%
\pgfplotsinvokeforeach{1,...,#2}{%
\pgfmathparse{(##1 - 0.5)/ (#2)}%
\edef#1{\ifx#1\empty\else#1,\fi \pgfmathresult}%
}%
}

\newcommand*{\my@attach@arrow@arrow@opts}{}

\tikzset{
myarrow/length/.initial=1.5mm,
myarrow/width/.initial=1.5mm,
myarrow/.style = {-{Triangle[length = \pgfkeysvalueof{/tikz/myarrow/length},
width = \pgfkeysvalueof{/tikz/myarrow/width},
bend, #1]}},
myarrow/.default={},
attach arrow/.style={
decoration={
markings,
mark=at position 0 with {\pgfextra{%
\pgfmathsetmacro{\tmpArrowTime}{%
\pgfkeysvalueof{/tikz/arc arrow/length}/(\pgfdecoratedpathlength)}%
\xdef\tmpArrowTime{\tmpArrowTime}}},
mark=at position {#1-3*\tmpArrowTime} with {\coordinate(@1);},
mark=at position {#1-2*\tmpArrowTime} with {\coordinate(@2);},
mark=at position {#1-1*\tmpArrowTime} with {\coordinate(@3);},
mark=at position {#1} with {\coordinate(@4);
\draw[myarrow/.expand once={\my@attach@arrow@arrow@opts}]
plot[smooth] coordinates {(@1) (@2) (@3) (@4)};},
},
postaction=decorate,
},
attach arrow/.default=0.5,
attach arrow opts/.store in=\my@attach@arrow@arrow@opts,
arc arrow/length/.initial=1.5mm,
}
\makeatother

\begin{document}
\begin{tikzpicture}
\begin{axis}[
xtick style = {
draw = none
},
xticklabels = {},
xmin = -0.9,
xmax = 1.1,
ymin = 15,
ymax = 20,
x = 2cm,
y = 1cm,
every node near coord/.append style = {
font = \small,
yshift = -2.5mm, xshift = \myshift
}
]

only marks,
mark = -,
thick,
mark size = 10mm,
nodes near coords = {\labelz},
visualization depends on = {%
value \thisrowno{2}\as\labelz%
},
visualization depends on = {%
value \thisrowno{3}\as\myshift%
}
0 15.183 a 15mm
0 18.300 b 15mm
0 19.011 c 15mm
};

\def\smallArrowParts{3}
\def\largeArrowParts{6}

\myBuildFractionList{\mylist}{\largeArrowParts}
\pgfmathsetmacro{\myYextent}{18.300-15.183}
\edef\temp{%
[draw=blue, thick, domain=0:\noexpand\myYextent, samples=40,
attach arrow/.list={\mylist}]
({0.25 - 0.2*sin(120*x)}, 18.300 - x);
}\temp

\myBuildCenteredFractionList{\mylist}{\smallArrowParts}
\pgfmathsetmacro{\myYextent}{19.011-18.300}
\edef\temp{%
[draw=blue, thick, domain=0:\noexpand\myYextent,
samples=23,
/tikz/myarrow/width=0.6mm, % we need narrower arrows here
% Shorten the region used to determine the direction and
% bending for each arrow.
/tikz/arc arrow/length=0.2mm,
attach arrow opts={reversed},
attach arrow/.list={\mylist},
]
(-0.25 - 0.02*sin(1000*x), 18.300 + x);
}\temp
\end{axis}
\end{tikzpicture}
\end{document}