[Tex/LaTex] Node position with \addplot

pgfplotstikz-pgf

I don't understand why the blue "Below" label does not appear right below the "Above" label (near the expected position showed by the red "Below" label). Any idea?

\documentclass{standalone}
\usepackage{pgfplots}
\begin{document}
\begin{tikzpicture}
  \begin{axis}
    \addplot coordinates {(0,0) (1,1)}
             node[draw, pos = .5] (A) {}
             node[above, sloped, pos = .5] {Above}
             node[below, sloped, pos = .5] {Below};
    \node[rotate = 45, anchor = north, font = \color{red}] at (A.south east) {Below};
  \end{axis}
\end{tikzpicture}
\end{document}

enter image description here

Best Answer

Here is why:

In TikZ, the pos=.5 option is handled by \tikz@timer with the argument stored in \tikz@time. The pgfplots package redefined this macro by

pgfplotscoordprocessing.code.tex

8178    \let\tikz@timer=\pgfplots@plot@timer%

7809    \def\pgfplots@plot@timer{%
7810        \pgfplotstransformplotattime{\tikz@time}%
7811    }

7819    \def\pgfplotstransformplotattime#1{%
7820      \pgftransformshift{\pgfplotspointplotattime{#1}}%
7821      \ifpgfresetnontranslationattime%
7822        \pgftransformresetnontranslations%
7823      \fi%
7824      \ifpgfslopedattime%
7825     \pgfplotsplothandlertransformslopedattime{#1}{\pgfplotspointplotattimefirst}{\pgfplotspointplotattimesecond}%
7826     \fi%
7827    }

In this question, nodes are not positioned properly. So we now focus on line 7820.

Recall that \pgftransformshift is a macro with one argument. The argument will be executed first and (usually) it will calculate \pgf@x and \pgf@y. And then PGF will shift (\pgf@x,\pgf@y). (see also the manual and pgfcoretransformations.code.tex)

In other words, \pgfplotspointplotattime{#1} should setup \pgf@x and \pgf@y properly. Here is (ideally) how \pgfplotspointplotattime should work:

  • if this \tikz@time is used before, reuse the calculated values.
  • If not, calculate values and cache them.

And here is ideally how your code will be processed

  • node[draw, pos = .5] (A) {} --> new \tikz@time, calculate everything and cache them.
  • node[above, sloped, pos = .5] {Above} --> same \tikz@time, reuse.
  • node[below, sloped, pos = .5] {Below} --> same \tikz@time, reuse.

It turns out that when \tikz@time is not new, pgfplots does not setup \pgf@x and \pgf@y properly. The fact that the Above node is placed correctly is pure luck --- no one changes the values of \pgf@x and \pgf@y so it is placed in the same place. However the values are changed when the Above node is typeset, therefore the Before node is not placed correctly.

The following example back-ups the observation above.

\documentclass[tikz]{standalone}
    \usepackage{pgfplots}
    \pgfplotsset{compat=1.14}
\begin{document}
    \begin{tikzpicture}
        \begin{axis}
            \addplot coordinates {(0,0) (1,1)}
                node[pos=.3]{.3 new value}
                node[pos=.7]{.7 new value}
                node[pos=.3]{.3 should reuse}
            ;
        \end{axis}
    \end{tikzpicture}
\end{document}

The following example, on the other hand, shows that pgfplots does cache other information such as slope.

\documentclass[tikz]{standalone}
    \usepackage{pgfplots}
    \pgfplotsset{compat=1.14}
\begin{document}
    \begin{tikzpicture}
        \begin{axis}
            \addplot coordinates {(0,0)(1.2,-.2)(1,1)}
                node[pos=.3,sloped]{.3 new value}
                node[pos=.7,sloped]{.7 new value}
                node[pos=.3,sloped]{.3 should reuse}
            ;
        \end{axis}
    \end{tikzpicture}
\end{document}



To fix this problem, one may try pos=.49999 to force pgfplots to recalculate everything. Or one might change the definition of \pgfplotspointplotattimeaddtocache to

\def\pgfplotspointplotattimeaddtocache#1{%
    \pgfplotsutil@advancestringcounter@global\pgfplotspointplotattime@cachesize
    \ifnum\pgfplotspointplotattime@cachesize=\pgfplotspointplotattime@cachesize@max
        \pgfplotspointplotattimeclearcache
    \fi
    \pgf@xa=#1pt %
    \edef\pgfplots@loc@TMPa{%
        \noexpand\gdef\noexpand\pgfplotspointplotattimefirst{\pgfplotspointplotattimefirst}%
        \noexpand\gdef\noexpand\pgfplotspointplotattimesecond{\pgfplotspointplotattimesecond}%
        \noexpand\gdef\noexpand\pgfplotspointplotattimecoords{\pgfplotspointplotattimecoords}%
        \pgf@x\the\pgf@x % ADDED NEW LINES
        \pgf@y\the\pgf@y % ADDED NEW LINES
    }%
    \t@pgfplots@toka=\expandafter{\pgfplotspointplotattime@cache}%
    \t@pgfplots@tokb=\expandafter{\pgfplots@loc@TMPa}%
    \xdef\pgfplotspointplotattime@cache{%
        \the\t@pgfplots@toka
        \noexpand\pgfkeyssetvalue{/data point/@pos \the\pgf@xa/segment \pgfkeysvalueof{/tikz/pos segment}}{\the\t@pgfplots@tokb}%
    }%
}

so that it will remember the position.


MWE

\documentclass[tikz]{standalone}
    \usepackage{pgfplots}
    \pgfplotsset{compat=1.14}
\begin{document}

    \makeatletter
    \def\pgfplotspointplotattimeaddtocache#1{%
        \pgfplotsutil@advancestringcounter@global\pgfplotspointplotattime@cachesize
        \ifnum\pgfplotspointplotattime@cachesize=\pgfplotspointplotattime@cachesize@max
            \pgfplotspointplotattimeclearcache
        \fi
        \pgf@xa=#1pt %
        \edef\pgfplots@loc@TMPa{%
            \noexpand\gdef\noexpand\pgfplotspointplotattimefirst{\pgfplotspointplotattimefirst}%
            \noexpand\gdef\noexpand\pgfplotspointplotattimesecond{\pgfplotspointplotattimesecond}%
            \noexpand\gdef\noexpand\pgfplotspointplotattimecoords{\pgfplotspointplotattimecoords}%
            \pgf@x\the\pgf@x % ADDED NEW LINES
            \pgf@y\the\pgf@y % ADDED NEW LINES
        }%
        \t@pgfplots@toka=\expandafter{\pgfplotspointplotattime@cache}%
        \t@pgfplots@tokb=\expandafter{\pgfplots@loc@TMPa}%
        \xdef\pgfplotspointplotattime@cache{%
            \the\t@pgfplots@toka
            \noexpand\pgfkeyssetvalue{/data point/@pos \the\pgf@xa/segment \pgfkeysvalueof{/tikz/pos segment}}{\the\t@pgfplots@tokb}%
        }%
    }
    \begin{tikzpicture}
        \begin{axis}
            \addplot coordinates {(0,0)(1.2,-.2)(1,1)}
                node[pos=.3,sloped]{.3 new value}
                node[pos=.7,sloped]{.7 new value}
                node[pos=.3,sloped]{.3 should reuse}
            ;
        \end{axis}
    \end{tikzpicture}
\end{document}