[Tex/LaTex] tikz : how to refer to a node, in nested nodes

tikz-pgf

The following MWE displays 2 nested boxes. The alignment is done by refering to the inside box, through the name of the node "inbox"

\documentclass[12pt, a4paper]{scrartcl}
\usepackage{tikz}
\begin{document}
Before
\begin{tikzpicture}[baseline=(inbox.base)]%
   \node[draw,rounded corners](outbox) {%
   \begin{tikzpicture}[scale=1]%
        \node[draw,rectangle](inbox){\textcolor{gray}{in}};
   \end{tikzpicture}        
    };
\end{tikzpicture}
After
\end{document}

But replacing baseline=(inbox.base) by baseline=(outbox.base) produces the compilation error : Package pgf Error: No shape named outbox is known.
Can someone explain me this ?

By the way, is it the right way, as i did, to nest nodes inside nodes ?

Best Answer

To answer the more important question first: No, nesting tikzpictures is not the right way to do this. The simplest way to achieve the effect that you seem to want is to use the fit library. Here's an example:

\documentclass[12pt, a4paper]{scrartcl}
\usepackage{tikz}
\usetikzlibrary{fit}

\begin{document}
Before
\begin{tikzpicture}[baseline=(inbox.base)]%
\node[draw,rectangle](inbox){\textcolor{gray}{in}};
\node[draw,rounded corners,fit=(inbox)](outbox) {%
    };
\end{tikzpicture}
After
\end{document}

The general principle here is that TikZ/PGF does not need to know about the hierarchy that you wish to specify (namely that inbox is inside outbox), it just needs to know how they relate in terms of positioning. So you figure out inbox first and then put outbox around it. TikZ is happy as it knows where to put things. If you really, really need to have a hierarchy, us the matrix node type.

Now for the explanation of what goes wrong. It's all down to the fact that tikzpictures are not meant to be stacked inside each other so there is no regard for grouping or scoping. There should be just one tikzpicture (or \tikz) containing all the elements and that's what the code expects. This is a great example of this (and one I'll probably refer to many times in explaining why this is a Bad Idea as it comes up quite often).

Let's look at the key command: the baseline option on the outer tikzpicture. This says, "I want the following point to be on the baseline of the surrounding text.". The problem with that is that TikZ (or rather, PGF) doesn't know where that point is until it has drawn the picture. So it has to say "Okay, I'll remember that point and at the end of the picture I'll work it out.". So now it goes about its business, figuring out the picture, and at the end it computes the coordinates of the point that you specified.

Here's where the scoping issue comes in. That baseline option sets some macro which will get evaluated as part of the stuff that happens in \end{tikzpicture}. If the macro is empty, nothing happens. If it is not empty, PGF uses it to work out the baseline. At the start of the document, the macro is empty. Since \begin{tikzpicture} ... \end{tikzpicture} happens inside a group, setting it in the options to \begin{tikzpicture} doesn't change what happens with other pictures unless they are in the same group. So what it happening here is that the inner tikzpicture is inheriting the baseline command from the outer one, and complaining vociferously because it doesn't know where outer is!. By the time the inner picture finishes, the outer node hasn't been positioned yet.

You could say that the \begin{tikzpicture} should wipe that macro inside its group (which wouldn't affect the outer picture because it is in the outer group). But this would mean that you couldn't say \tikzset{baseline=0pt} at the start and have that apply to all your pictures in your document - something that is far more useful than nesting tikzpictures. So it's a feature, not a bug.

To see that this is the case, run the following code:

\documentclass[12pt, a4paper]{scrartcl}
\usepackage{tikz}
\makeatletter
\def\helpme#1{%
  \message{#1}%
  \show\pgf@sh@ns@outbox
  \show\pgf@baseline
}
\makeatother

\begin{document}
Before
\begin{tikzpicture}[baseline=(outbox.base)]%
  \helpme{Start of picture:}
   \node[draw,rounded corners](outbox) {%
  \helpme{Start of node:}
   \begin{tikzpicture}[scale=1]%
  \helpme{Start of inner picture:}
        \node[draw,rectangle](inbox){\textcolor{gray}{in}};
   \end{tikzpicture}        
   \helpme{After inner picture:}
    };
\helpme{End of picture:}
\end{tikzpicture}
After
\end{document}

The diagnostics are a bit crude, I'll admit, but they show what's going on. Actually, you don't need to run it as I'll put (a condensed version of) the output here:

 Start of picture:
> \pgf@sh@ns@outbox=undefined.
> \pgf@baseline=macro:
->\tikz@scan@one@point \pgfutil@firstofone (outbox.base).

Start of node:
> \pgf@sh@ns@outbox=undefined.
> \pgf@baseline=macro:
->\tikz@scan@one@point \pgfutil@firstofone (outbox.base).

Start of inner picture:
> \pgf@sh@ns@outbox=undefined.                                           
> \pgf@baseline=macro:
->\tikz@scan@one@point \pgfutil@firstofone (outbox.base).

! Package pgf Error: No shape named outbox is known.

l.20    \end{tikzpicture}

After inner picture:
> \pgf@sh@ns@outbox=undefined.
> \pgf@baseline=macro:
->\tikz@scan@one@point \pgfutil@firstofone (outbox.base).

End of picture:
> \pgf@sh@ns@outbox=macro:
->rectangle.
> \pgf@baseline=macro:
->\tikz@scan@one@point \pgfutil@firstofone (outbox.base).

Notice that \pgf@sh@ns@outbox is only set after the node has been fully defined (that's what is directly causing the error as it's used to test if a node has been declared). Notice that the error occurs when the inner picture finishes, not the outer one. Notice also that \pgf@baseline is the same throughout, including in the inner picture.

The moral of the story is: don't nest tikzpictures. They don't like it.