TikZ: define arrow starting position based on style and format node label

I use following code, as I once showed here, to put descriptive labels on a picture:

\documentclass[margin=1cm]{standalone}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\node[anchor=south west,inner sep=0] (image) at (0,0) {\includegraphics[width=5cm]{airplane.png}};
\begin{scope}[x={(image.south east)},y={(image.north west)}]
% \draw[help lines, very thin, step=0.02] (0,0) grid (1,1);
% \draw[help lines,thin,xstep=.1,ystep=.1] (0,0) grid (1,1);
% \foreach \x in {0,1,...,9} { \node [anchor=north] at (\x/10,0) {0.\x}; }
% \foreach \y in {0,1,...,9} { \node [anchor=east] at (0,\y/10) {0.\y}; }

\draw[<-] (0.5,0.6) -- (0.7,1.1) node[above] {label above};
\draw[<-] (0.3,0.5) -- (0.4,-0.1) node[below] {label below};
\draw[<-] (0.7,0.6) -- (1.1,0.7) node[right] {label right};
\draw[<-] (0.1,0.7) -- (-0.1,0.8) node[left] {label left};

\end{scope}
\end{tikzpicture}
\end{document}


Result:

(I got the airplane picture from here)

As one can see, I want the labels positioned such that the label text is above the arrow if the arrow ends above the image, and I want the label text left of the arrow if the arrow ends left of the image, and so on. It is somewhat redundant to specify the end coorindate of x=-0.1, which is left of the image, and also the label position as node[left]. So I wonder whether there is a possibility to automate this such that only the starting coordinate needs to be specified and one of the end coordinates, as well as the orientation (left, right, above, below), e.g. like the following

\draw[<-] (0.1,0.7) node[left, y=0.8] {label left};


which, of course, does not work as I wrote it here, this is just an example. This should place the label left of the arrow, which would have its endpoint at x=-0.1 and y=0.8.

N.B.: the section I commented out in my MWE is just to display some coordinate grid to find the starting positions of the arrows.

Further, there is another problem with the labels. Consider the following picture

where one label has the letter p in it (which goes a little bit further down than the baseline), and the other node doesn't. The result is that the labels are not on the same height, which is ugly. It can be fixed by using a strut{} in the label text, e.g.

\draw[<-] (0.4,0.6) -- (0.7,1.1) node[above] {\strut{} aaa};
\draw[<-] (0.3,0.6) -- (0.4,1.1) node[above] {\strut{} paaaaa};


which gives following result:

How can the strut be automatically added to the label text? Or should the baseline of the TikZ labels be shifted?

• I am just trying to express how I think about this question, please do not get mad at me. It is an extremely well written and clear question, +1 for that. However, if I understand correctly, you want to get rid of the necessity of writing some coordinates and some relative position information. Yes, one could write a style that does that, I believe. I can really speak only for myself, but I guess that many may feel that it is not worth the effort. Do you really want a sophisticated style that saves you typing a few characters? – user121799 Dec 11 '18 at 18:03
• BTW, the second issue can be solved by adding an appropriate text depth to the nodes, e.g. \tikzset{every node/.append style={text depth=0.25ex}}. – user121799 Dec 11 '18 at 18:04
• @marmot, yes, if it is possible to write such a style, why not ;-) it would ensure that all labels are rock solid 10% outside the image, for instance. – T. Pluess Dec 11 '18 at 21:11

This is not a real answer but just to show what can be done easily. You can define a style that connects a point in the pic frame with one outside and a label, where the outside point and the label position are computed. The style works like this:

\draw[<-,lazy label={(0.3,0.4) with auto}];


where you only specify the point and the label.

\documentclass[margin=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}[declare function={offset(\x)=sign(\x)*80pt/(abs(\x)+4pt);},
lazy label/.style args={#1 with #2}{
insert path={
let \p1=($#1-(0.5,0.5)$),\p2=#1,\n1={atan2(\y1,\x1)} in #1
\pgfextra{\pgfmathtruncatemacro{\ntest}{mod(8+\n1/90,4)}\xdef\ntest{\ntest}
%\typeout{#1:#2,\x1,\y1,\x2,\y2,\n1,\ntest}
}
\ifcase\ntest%
#1 -- (1.1,{\y2+offset(\y1)}) node[right] {#2}
\or
#1 -- ({\x2+offset(\x1)},1.1) node[above] {#2}
\or
#1 -- (-0.1,{\y2+offset(\y1)}) node[left] {#2}
\or
#1 -- ({\x2+offset(\x1)},-0.1) node[below] {#2}
\fi
}}]
\node[anchor=south west,inner sep=0] (image) at (0,0)
{\includegraphics[width=5cm]{example-image-duck}};
\begin{scope}[x={(image.south east)},y={(image.north west)}]
\draw[<-,lazy label={(0.3,0.4) with auto}];
\end{scope}
\end{tikzpicture}
\end{document}


I also want to mention that if you use this style in a loop you need to watch out for expansion issue. These are, however, not specific to the style but appear in other contexts, too.

\documentclass[margin=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}[declare function={offset(\x)=sign(\x)*80pt/(abs(\x)+4pt);},
lazy label/.style args={#1 with #2}{
insert path={
let \p1=($#1-(0.5,0.5)$),\p2=#1,\n1={atan2(\y1,\x1)} in #1
\pgfextra{\pgfmathtruncatemacro{\ntest}{mod(8+\n1/90,4)}\xdef\ntest{\ntest}
%\typeout{#1:#2,\x1,\y1,\x2,\y2,\n1,\ntest}
}
\ifcase\ntest%
#1 -- (1.1,{\y2+offset(\y1)}) node[right] {#2}
\or
#1 -- ({\x2+offset(\x1)},1.1) node[above] {#2}
\or
#1 -- (-0.1,{\y2+offset(\y1)}) node[left] {#2}
\or
#1 -- ({\x2+offset(\x1)},-0.1) node[below] {#2}
\fi
}}]
\node[anchor=south west,inner sep=0] (image) at (0,0)
{\includegraphics[width=5cm]{example-image-duck}};
\begin{scope}[x={(image.south east)},y={(image.north west)}]

\foreach \X in {(0.5,0.6),(0.3,0.4),(0.7,0.6),(0.1,0.7)}
{
\edef\temp{\noexpand\draw[<-,lazy label={{\X} with auto}];}
\temp
}
\end{scope}
\end{tikzpicture}
\end{document}


So far, so good. But this style won't check if there is already a label with which the new label will interfere, nor guess your thoughts, i.e. in this version your label below is not done in the way you suggest because the distance of this point to the upper border is shorter.

I managed to find an acceptable solution, with the aid of marmot's answer. I used the insert path style from TikZ as follows:

\documentclass[border=1cm]{standalone}
\usepackage{tikz}

\tikzset{
lazy label below/.style args = {#1 at #2}{
red,
->,
insert path = {
(#2,-0.1) node[below] {#1 \strut{}}
}
},
lazy label above/.style args = {#1 at #2}{
red,
->,
insert path = {
(#2,1.1) node[above] {#1 \strut{}}
}
},
}
\begin{document}
\begin{tikzpicture}
\node[anchor=south west,inner sep=0] (image) at (0,0) {\includegraphics[width=5cm]{example-image}};
\begin{scope}[x={(image.south east)},y={(image.north west)}]
\draw[lazy label below={label text at 0.3}] to (0.2,0.2);
\draw[lazy label above={other text at 0.6}] to (0.4,0.8);
\end{scope}
\end{tikzpicture}
\end{document}


result:

it allows, as I want, to place the labels above or below the image, and sets the coordinates of the arrow start point accordingly. Further it adds the \strut{} to the label text to ensure the height of all labels is the same. I will use this for my tikz-imagelabels package.