[Tex/LaTex] DVI compiling problem with TikZ

dvitikz-pgf

The following code compiles in both DVI and PDF format. However, if I draw anything after the \end{scope} command, it won't compile in DVI, and I get the following error message:

Latex> ! Undefined control sequence.
Latex> \tikz@intersect@namedpaths ...t@path@name@line \x 
                                                          \endcsname {\pgfsyssoftpat...

The PDF registers the same error but the compilation goes through and the image is rendered. What is causing this error? I have so much more to add to this figure!

UPDATE: As requested, I've added a random draw command \draw (0,0) circle [radius=1cm]; which then triggers the error. Using path name global=line \x does not solve the problem.

\documentclass{article}

\usepackage{tikz} 
\usetikzlibrary{intersections,backgrounds}

\begin{document}
\begin{tikzpicture} 
%axis 
\draw (-.5,0) -- (6.5,0);  

%curve  
\draw[yshift=1cm,name path=curve] (-.5,0) %vertically shiftable
   to[out=70,in=180] (.7,1.5)
   to[out=0,in=180] (2,.5)
   to[out=0,in=180] (4.5,2.5)
   to[out=0,in=160] (6.5,1);

%rectangles
\begin{scope}[on background layer]
\foreach \x in {0,1,2,5}{
    \path[name path=line \x] (\x,0) -- (\x,4);
    \path[name intersections={of=curve and line \x, by={isect \x}}];    
    \draw[fill=gray!50] (isect \x) rectangle (\x+1,0);
    \draw[fill] (isect \x) circle [radius=2pt];
    }
\end{scope}   

\draw (0,0) circle [radius=1cm]; % Won't compile with this added draw

\end{tikzpicture}
\end{document}

Best Answer

This is, I think, a bug in the intersections library in TikZ. Although it does not produce an error with the CVS version of TikZ, the underlying bug is still there.

When TikZ names a path then it has to save that path to a macro. Because the path might undergo considerable changes as it is being processed, the best time to save it is right at the end. So the intersection library hooks in to the macro \tikz@finish which is called last of all. So it stores up all the necessary intersection code into a macro, \tikz@intersect@namedpaths, which it then executes right at the end. The addition of code looks like this:

\def\tikz@intersect@finish{%    
        \ifx\tikz@intersect@namedpaths\pgfutil@empty%
        \else%
                \tikz@intersect@namedpaths%
                \let\tikz@intersect@namedpaths=\pgfutil@empty%
        \fi%
}

This is hooked into \tikz@finish to run after all the other stuff.

The problem with this is that \let. The macro \tikz@intersect@namedpaths is defined globally. This is because inside a path then there can be all manner of groupings involved and TikZ doesn't want to have to bother with them. So it defines this globally. However, it only clears it locally. Normally, this is alright because the clearing happens at the same level as the \path command. In this situation, though, that is inside the scope meaning that when the scope ends then \tikz@intersect@namedpaths is restored to its previous global value. This is the value it had during the \path[name path=line \x] (\x,0) -- (\x,4); when it saved the path with name line \x. So when the \draw command after the scope is executed, \tikz@intersect@namedpaths is not empty and so is processed.

This is definitely a bug, because it means that any paths named inside the scope are now available outside it - so if you use the same path name before the scope and inside it then the path after the scope is the one defined inside, not the previous one.

The reason for the complaint in the code is that in an earlier version of TikZ then the macro \tikz@intersect@namedpaths had the path names stored as they were specified. So it stored the path name (in your example) as line \x. This means that when \tikz@intersect@namedpaths is executed in the \draw command outside the scope, it tries to figure out the meaning of \x and fails, because it is no longer defined. Later versions of TikZ expand the path name first, meaning that line \x is stored as, for example, line 1. This is why there is no longer an error produced with later versions of TikZ, or when the path name is expanded before it is passed in to the code (this is what the .expanded does). However, the underlying bug is still present and still detectable by looking at what paths are saved.

The real fix is to make the reset of \tikz@intersect@namedpaths into a global reset. This is simple enough: prepend \global to that \let. Thus the correct solution is to add the following to your preamble:

\makeatletter
\def\tikz@intersect@finish{%    
        \ifx\tikz@intersect@namedpaths\pgfutil@empty%
        \else%
                \tikz@intersect@namedpaths%
                \global\let\tikz@intersect@namedpaths=\pgfutil@empty%
        \fi%
}
\makeatother

Here's an example showing the persistence of paths. The key is the blue circle. This ought to be at the intersection of the lower path, but due to using the same name inside the scope, it gets shifted to the upper path (after one extra path command since we need \tikz@intersect@namedpaths to be processed). Redefining \tikz@intersect@finish as I describe fixes this.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/150598/86}
\usepackage{tikz} 
\usetikzlibrary{intersections}

\begin{document}

\begin{tikzpicture}
\draw[name path=abc] (-3,-3) -- (3,3);
\draw[name path=def] (3,-3) -- (-3,3);
\draw[green, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=6pt];
\begin{scope}
\draw[name path=abc] (-3,-2) -- (2,3);
\draw[green, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=6pt];
\end{scope}
\path (0,0); % needed to invoke \tikz@intersect@namedpath from inside the scope
\draw[blue, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=10pt];
\end{tikzpicture}

\makeatletter
\def\tikz@intersect@finish{%    
        \ifx\tikz@intersect@namedpaths\pgfutil@empty%
        \else%
                \tikz@intersect@namedpaths%
                \global\let\tikz@intersect@namedpaths=\pgfutil@empty%
        \fi%
}
\makeatother

\begin{tikzpicture}
\draw[name path=abc] (-3,-3) -- (3,3);
\draw[name path=def] (3,-3) -- (-3,3);
\draw[green, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=6pt];
\begin{scope}
\draw[name path=abc] (-3,-2) -- (2,3);
\draw[green, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=6pt];
\end{scope}
\path (0,0); % needed to invoke \tikz@intersect@namedpath from inside the scope
\draw[blue, ultra thick, name intersections={of=abc and def}] (intersection-1) circle[radius=10pt];
\end{tikzpicture}
\end{document}

Intersection bug