Detecting if inside a tikzpicture

I would like to define the \DrawLine macro below so that it can be invoked from within a {tikzpicture} environment or from outside:

\newcommand*{\DrawLine}[1]{%
\IfInTikzPic{}{\tikzpicture[remember picture]}
\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};
\IfInTikzPic{}{\endtikzpicture}%
}


This works fine when invoked from within a {tikzpicture}, but not when it is invoked from outside (line is commented in the MWE). Invoking it from outside yields the error:

Missing \endgroup inserted.

The MWE below uses the first reference below. The other two references also fail on the commented out test case.

References:

• Detecting if inside a tikzpicture node. Even though this solution is for detecting within a node, it appears to work to detect if you are within a \tikzpicture (as the commented out code in the MWE shows).

Code:

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newcommand{\IfInTikzPic}[2]{% https://tex.stackexchange.com/a/121309/4301
\ifx\pgfpictureid\@undefined#2\else#1\fi%
}
\makeatother

\newcommand*{\DrawLine}[1]{%
\IfInTikzPic{}{\tikzpicture[remember picture]}
\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};
\IfInTikzPic{}{\endtikzpicture}%
}

\begin{document}
%%% The commented out code here is to show that \IfInTikzPic works as desired
%%% (in a tikzpicture, outside of a \node).
%%%

%\textbf{IfInTikzPic}\par
%\IfInTikzPic{inside}{outside}
%
%\begin{tikzpicture}
%  \IfInTikzPic{\draw [red, ultra thick]}{\draw [blue, ultra thick]} (0,0) -- (1,0);
%\end{tikzpicture}
%
%\medskip% --------------------------
\textbf{DrawLine}: Actual Output\par

%\DrawLine{blue}% <---- How do I get this case to work?

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}

\medskip% --------------------------
\textbf{DrawLine}: Desired Output\par

\begin{tikzpicture}
\DrawLine{blue}
\end{tikzpicture}

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}
\end{document}

• – Torbjørn T. Nov 7 '18 at 19:57
• – marmot Nov 7 '18 at 20:02
• @TorbjørnT.: That works for all but the commented out case below (yields a different error message though: Undefined control sequence). – Peter Grill Nov 7 '18 at 20:06
• @marmot: Yep, that also works for all but the commented out case below and produces the same error message. – Peter Grill Nov 7 '18 at 20:07

2 Answers

The following example defines \IfInTikzPic to check the current environment against tikzpicture and define a \tikzstart...\tikzend pair accordingly:

\documentclass{article}

\usepackage{tikz}

\makeatletter
\def\@tikzenvironment{tikzpicture}

\newcommand{\IfInTikzPic}{%
\edef\tikzstart{%
\ifx\@currenvir\@tikzenvironment\else
\noexpand\tikzpicture[remember picture]%
\fi
}%
\edef\tikzend{%
\ifx\@currenvir\@tikzenvironment\else
\noexpand\endtikzpicture%
\fi
}%
}

\newcommand*{\DrawLine}[1]{%
\IfInTikzPic
\tikzstart
\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};
\tikzend%
}
\makeatother

\begin{document}

\textbf{DrawLine}: Actual Output\par

\DrawLine{blue}

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}

\medskip% --------------------------

\textbf{DrawLine}: Desired Output\par

\begin{tikzpicture}
\DrawLine{blue}
\end{tikzpicture}

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}

\end{document}


You could also group the tikzpicture if needed:

\newcommand{\IfInTikzPic}{%
\edef\tikzstart{%
\ifx\@currenvir\@tikzenvironment\else
\noexpand\begin{tikzpicture}[remember picture]%
\fi
}%
\edef\tikzend{%
\ifx\@currenvir\@tikzenvironment\else
\noexpand\end{tikzpicture}%
\fi
}%
}

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newcommand{\IfInTikzPic}{% https://tex.stackexchange.com/a/121309/4301
\ifx\pgfpictureid\@undefined
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\makeatother

\newcommand*{\DrawLine}[1]{%
\IfInTikzPic
{\begin{tikzpicture}[remember picture]
\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};
\end{tikzpicture}}%
{\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};}%
}

\begin{document}
%%% The commented out code here is to show that \IfInTikzPic works as desired
%%% (in a tikzpicture, outside of a \node).
%%%

\textbf{IfInTikzPic}\par
\IfInTikzPic{inside}{outside}

\begin{tikzpicture}
\IfInTikzPic{\draw [red, ultra thick]}{\draw [blue, ultra thick]} (0,0) -- (1,0);
\end{tikzpicture}

\medskip% --------------------------
\textbf{DrawLine}: Actual Output\par

\DrawLine{blue}% <---- How do I get this case to work?

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}

\medskip% --------------------------
\textbf{DrawLine}: Desired Output\par

\begin{tikzpicture}
\DrawLine{blue}
\end{tikzpicture}

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}
\end{document}


Or this version which directly forces tikz rather than having an if-then-else construct

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newcommand{\ensuretikz}{% https://tex.stackexchange.com/a/121309/4301
\ifx\pgfpictureid\@undefined
\expandafter\tikzify
\else
\expandafter\@firstofone
\fi
}
\makeatother

\def\tikzify#1{\begin{tikzpicture}#1\end{tikzpicture}}

\newcommand*{\DrawLine}[1]{%
\ensuretikz
{\draw[ultra thick, ->, #1] (0,) -- (1,1) node [right] {output of DrawLine};}%
}

\begin{document}
%%% The commented out code here is to show that \IfInTikzPic works as desired
%%% (in a tikzpicture, outside of a \node).
%%%

\medskip% --------------------------
\textbf{DrawLine}: Actual Output\par

\DrawLine{blue}% <---- How do I get this case to work?

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}

\medskip% --------------------------
\textbf{DrawLine}: Desired Output\par

\begin{tikzpicture}
\DrawLine{blue}
\end{tikzpicture}

\begin{tikzpicture}
\DrawLine{orange}
\end{tikzpicture}
\end{document}

• I thought it might have something to dot with the dreaded expansion issues! – Peter Grill Nov 7 '18 at 20:11
• @PeterGrill I think the main error was \IfInTikzPic{}{\endtikzpicture}% as by that point you are always in a tikz picture, you need to know if you started it or if you were already in one. – David Carlisle Nov 7 '18 at 20:17
• Opsss..., thats a bit embarrassing. I'd prefer to not duplicate the drawing code though. – Peter Grill Nov 7 '18 at 20:33
• @PeterGrill duplicating it isn't needed, I could post a variant without, i just started by simplifying it – David Carlisle Nov 7 '18 at 21:13
• @PeterGrill plan b posted – David Carlisle Nov 7 '18 at 21:18