[Tex/LaTex] pgfplots intersection with path

intersectionspgfplotstikz-pgf

I am trying to find the intersections of a path with plots from pgfplots via the intersection library. The intersections found clearly do not match the visable intersections. It seems as if they are transformed to a different coordinate system. Is it possible to correctly calculate these intersections?

example

MWE

\documentclass{article}
\usepackage{pgfplots, tikz}
\usetikzlibrary{calc, intersections}    
\begin{document}
\begin{tikzpicture}

\begin{axis}[name=firstaxis]
\addplot[name path global=firstfunction]{e^x};
\end{axis}    
\begin{axis}[name=secondaxis, at={($(firstaxis.south)-(0,1cm)$)}, anchor=north]
\addplot[name path global=secondfunction]{x^3};
\end{axis}

% Draw intersection line 
\draw[color=black, opacity=1, name path global=intersectionline] (firstaxis.north) -- (secondaxis.south);

% Draw intersections points
\fill 
    [name intersections={of=intersectionline and firstfunction}] 
    (intersection-1) circle (2pt);          
\fill 
    [name intersections={of=intersectionline and secondfunction}] 
    (intersection-1) circle (2pt);

\end{tikzpicture}
\end{document}

Best Answer

I hope there is an easier way of doing this but...

Firstly, as I understand it PGFPlots calculates the points for the plot, puts the plotted path in an axis box and then positions the axis box in the picture. The shifting of the axis box to the required position does not change the coordinates of the underlying low level PGF path representation obtained using the name path keys from the intersection library.

You can see this if the saved plotted path is manually drawn (in red) directly after the second axis environment:

\documentclass[border=0.125cm]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{calc,intersections}  

\tikzset{
    name path global/.append code={%
        \csname tikz@addmode\endcsname{%
            \pgfgetpath\tmp%
            \expandafter\global\expandafter\let\csname tikz@intersect@path@name@#1\endcsname=\tmp%
        }%
    }%
}  
\begin{document}


\begin{tikzpicture}


\begin{axis}
[
    name=firstaxis, 
    domain=-5:5, 
    xmin=-6, xmax=6, 
    ymin=-10, ymax=160, 
    enlargelimits=false
]
    \addplot[name path global=firstfunction]{e^x};

\end{axis}    

\begin{axis}
[
    name=secondaxis, 
    domain=-5:5,
    xmin=-6, xmax=6, 
    ymin=-150, ymax=150, 
    enlargelimits=false, 
    at={($(firstaxis.south)-(0,1cm)$)}, 
    anchor=north
]
    \addplot[name path global=secondfunction]{x^3};

\end{axis}

\expandafter\pgfsetpath\csname tikz@intersect@path@name@secondfunction\endcsname
\pgfsetstrokecolor{red}
\pgfusepath{stroke}

\end{tikzpicture}
\end{document}

enter image description here

The second problem is that the name path global key doesn't currently work properly with PGFPLots and has to hacked (as shown in the code above).

This means trying to do what you want is possible but a real nuisance. The most conceptually simple way would be to shift the underlying plotted path, but this would be time consuming for complex plots and involve a lot of low level stuff.

In the method shown below, coordinates of the intersectionline have to be shifted to the 'coordinate system' of any axis which is not positioned at the origin. Then the intersection points also have to be shifted as well.

This is (sort of easy) but then you also have to use enlargelimits=false (which acts like a kind of 'margin') and then manually set the limits for the |x| and |y| axes for both axes.

In addition the name path global key needs to be hacked a bit. It is a fair bit work, and I suspect some other kind of workaround not involving intersections would be much easier:

\documentclass[border=0.125cm]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{calc,intersections}  

\tikzset{
    name path global/.append code={%
        \csname tikz@addmode\endcsname{%
            \pgfgetpath\tmp%
            \expandafter\global\expandafter\let\csname tikz@intersect@path@name@#1\endcsname=\tmp%
        }%
    }%
}  
\begin{document}


\begin{tikzpicture}


\begin{axis}
[
    name=firstaxis, 
    domain=-5:5, 
    xmin=-6, xmax=6, 
    ymin=-10, ymax=160, 
    enlargelimits=false
]
    \addplot[name path global=firstfunction]{e^x};

\end{axis}    

\begin{axis}
[
    name=secondaxis, 
    domain=-5:5,
    xmin=-6, xmax=6, 
    ymin=-150, ymax=150, 
    enlargelimits=false, 
    at={($(firstaxis.south)-(0,1cm)$)}, 
    anchor=north
]
    \addplot[name path global=secondfunction]{x^3};

\end{axis}


\foreach \col/\from/\to in {red/west/east, green/north/south, blue/north west/south east}{
    % Draw intersection line 
    \draw 
        [color=\col, opacity=1, name path global=intersectionline] 
        (firstaxis.\from) -- (secondaxis.\to);


    % Draw intersections points
    \fill 
        [\col, name intersections={of=intersectionline and firstfunction}] 
        (intersection-1) circle [radius=2pt];    

    % Create the intersectionline in the secondaxis coordinate system.
    \path [overlay] let \p1=(secondaxis.south west) in
        [name path global=intersectionline] 
        ([shift={(-\x1,-\y1)}]firstaxis.\from) -- ([shift={(-\x1,-\y1)}]secondaxis.\to);

    % Draw the intersection point
    \fill 
        [\col, name intersections={of=intersectionline and secondfunction}] 
        ($(secondaxis.south west)+(intersection-1)$) circle [radius=2pt];
}
\end{tikzpicture}
\end{document}

enter image description here

Ok, so it can be done the hard way as well. The macro \pgfpathretransform defined below 're-transforms' a low level PGF path. The result can be used using the transform named path key so that the intersection stuff is now less messy.

\documentclass[border=0.125cm]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{calc,intersections}  
\makeatletter
\tikzset{
    name path global/.append code={%
        \tikz@addmode{%
            \pgfgetpath\tmp%
            \expandafter\global\expandafter\let\csname tikz@intersect@path@name@#1\endcsname=\tmp%
        }%
    },
    transform named path/.code args={#1 by #2}{
        \expandafter\let\expandafter\@tmp\csname tikz@intersect@path@name@#1\endcsname%
        \pgfpathretransform{\tikzset{#2}\tikz@transform}{\@tmp}{\@tmp}%
        \pgfinterruptpath%
            \path[name path=#1]\pgfextra{\pgfsetpath\@tmp};
        \endpgfinterruptpath%
    }
}  


% This macro 're-transforms' a soft-path
%
% #1 Pgf level transformation code
% #2 a macro containing the original soft path
% #3 a macro to store the transformed path
%

\def\pgfpathretransform#1#2#3{%
    % First get the transform...
    \begingroup%
        \pgftransformreset%
        #1%
        \pgfgettransform\pgfpath@retransform%
        \expandafter
    \endgroup%
    \expandafter\def\expandafter\pgfpath@retransform\expandafter{\pgfpath@retransform}%
    \global\let\pgfpath@retransform@path=\pgfutil@empty%
    \begingroup%
        \pgfprocessround{#2}{#2}%
        % Locally redefine the soft-path tokens so that they do most of the work.
        \def\pgfsyssoftpath@movetotoken{\pgfpath@retransform@point{moveto}}%
        \def\pgfsyssoftpath@linetotoken{\pgfpath@retransform@point{lineto}}%
        \def\pgfsyssoftpath@curvetosupportatoken{\pgfpath@retransform@point{curvetosupporta}}%
        \def\pgfsyssoftpath@curvetosupportbtoken{\pgfpath@retransform@point{curvetosupportblineto}}%
        \def\pgfsyssoftpath@curvetotoken{\pgfpath@retransform@point{curveto}}%
        \def\pgfsyssoftpath@rectcornertoken{\pgfpath@retransform@point{rectcorner}}%
        \def\pgfsyssoftpath@rectsizetoken{\pgfpath@retransform@point{rectsize}}%
        \def\pgfsyssoftpath@closepathtoken{\pgfpath@retransform@point{closepath}}%
        #2%
    \endgroup%
    % Now \pgfpath@retransform@path holds the transformed path
    \let#3=\pgfpath@retransform@path%
}

\def\pgfpath@retransform@point#1#2#3{%
    \pgf@process{%
        \pgf@x=#2\relax%
        \pgf@y=#3\relax%
        \pgfsettransform\pgfpath@retransform%
        \pgf@pos@transform{\pgf@x}{\pgf@y}%
    }%
    \edef\pgf@marshal{\expandafter\noexpand\csname pgfsyssoftpath@#1token\endcsname{\the\pgf@x}{\the\pgf@y}}%
    \expandafter\pgfutil@g@addto@macro\expandafter{\expandafter\pgfpath@retransform@path\expandafter}\expandafter{\pgf@marshal}%
}


\begin{document}


\begin{tikzpicture}
\begin{axis}
[
    name=firstaxis, 
    domain=-5:5, 
    xmin=-6, xmax=6, 
    ymin=-10, ymax=160, 
    enlargelimits=false
]
    \addplot[name path global=firstfunction]{e^x};

\end{axis}    

\begin{axis}
[
    name=secondaxis, 
    domain=-5:5,
    xmin=-6, xmax=6, 
    ymin=-150, ymax=150, 
    enlargelimits=false, 
    at={($(firstaxis.south)-(0,1cm)$)}, 
    anchor=north
]
    \addplot[name path global=secondfunction]{x^3};

\end{axis}

% Transform the named path.
\tikzset{transform named path=secondfunction by {shift=(secondaxis.south west)}}


\foreach \col/\from/\to in {red/west/east, green/north/south, blue/north west/south east}{
    % Draw intersection line 
    \draw 
        [color=\col, opacity=1, name path global=intersectionline] 
        (firstaxis.\from) -- (secondaxis.\to);


    % Draw intersections points
    \fill 
        [\col, name intersections={of=intersectionline and firstfunction}] 
        (intersection-1) circle [radius=2pt];    

    \fill 
        [\col, name intersections={of=intersectionline and secondfunction}] 
        (intersection-1) circle [radius=2pt];
}
\end{tikzpicture}
\end{document}

The result is the same as before.