4

There is already quite some discussion about pic mysteries in How to give a name to \pic and How to give a name to \pic (part II). Here is one more issue/observation, which may or may not be related. When answering Shifted edge labels in tikzpictures in nodes side by side with arrow between I learned the nice local bounding box trick from Torbjørn T.'s answer. So I tried to combine this with the pic syntax to make the whole picture referable like a node. (Yes, I know about path pictures, but the problem I see when using these for this purpose is that as far as I know I'd know the size of the thingy before I add all the elements.) My attempt of incorporating this into the definition of the pic is:

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\tikzset{vertex/.style={circle, minimum size=4pt, inner sep=0pt, fill=orange}}
\tikzset{mygraph/.pic={\xdef\myname{\pgfkeysvalueof{/tikz/name prefix}}
        \typeout{\myname}
            \begin{scope}[font=\footnotesize, thick,local bounding box=\myname]
                \node[vertex] (root)    at (4,  5) {};
                \node[vertex] (o)       at (4,  4) {};
                \node[vertex] (oc)      at (4,  3) {};
                \node[vertex] (oco)     at (4,  2) {};
                \node[vertex] (a)       at (5,  4) {};

                \foreach \xfrom/\xto/\xlabel in {
                    root/o/o, o/oc/c, oc/oco/o,
                    root/a/a} {
                    \draw (\xfrom) to node[pos=0.5,fill=white]{\xlabel} (\xto);
                };
            \end{scope}
        }
    }

    \begin{tikzpicture}
        \pic (graph1) at (0,0) {mygraph};
        \pic (graph2) at (3,0) {mygraph};
    \draw[-latex] (graph1) to (graph2);
    % just for fun 
    \draw[-latex,shorten >=1mm,shorten <=1mm] (graph1a) to[out=-10,in=160] (graph2root);
    \end{tikzpicture}
\end{document}

enter image description here

As you can see, the arrow starts where it ends. On the other hand, I'd naively expect that the output be the same as the one produced by a code where I put the scope outside the pic:

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\tikzset{vertex/.style={circle, minimum size=4pt, inner sep=0pt, fill=orange}}
\tikzset{mygraph/.pic={\begin{scope}[font=\footnotesize, thick]
                \node[vertex] (root)    at (4,  5) {};
                \node[vertex] (o)       at (4,  4) {};
                \node[vertex] (oc)      at (4,  3) {};
                \node[vertex] (oco)     at (4,  2) {};
                \node[vertex] (a)       at (5,  4) {};

                \foreach \xfrom/\xto/\xlabel in {
                    root/o/o, o/oc/c, oc/oco/o,
                    root/a/a} {
                    \draw (\xfrom) to node[pos=0.5,fill=white]{\xlabel} (\xto);
                };
            \end{scope}
        }
    }

    \begin{tikzpicture}
      \begin{scope}[local bounding box=graph1]
        \pic (graph1) at (0,0) {mygraph};
      \end{scope}
      \begin{scope}[local bounding box=graph2]
        \pic (graph2) at (3,0) {mygraph};
      \end{scope}
    \draw[-latex] (graph1) to (graph2);
    % just for fun 
    \draw[-latex,shorten >=1mm,shorten <=1mm] (graph1a) to[out=-10,in=160] (graph2root);
    \end{tikzpicture}
\end{document}

enter image description here

QUESTION(S): Why is that? How can this be fixed?

Ignasi
  • 136,588
  • Can't say what happens exactly, for some reason both local bbs refer to the second pic, try \draw[red,ultra thick] (graph1.south east) rectangle (graph1.north west); \draw[blue] (graph2.south east) rectangle (graph2.north west); to see that. So it works if you have \begin{scope}[font=\footnotesize, thick,local bounding box=#1] in the pic, and say e.g. mygraph=a to give the local bb the name a, but that is less convenient. Another method is what Mark Wibrow describes in https://tex.stackexchange.com/a/241737/ – Torbjørn T. May 04 '18 at 08:47
  • @TorbjørnT. Thanks so much! Yes, Mark Wibrow's answer is a much more elegant version of what I tried to achieve. –  May 04 '18 at 10:43

1 Answers1

8

I think it is a timing issue because of the global definition of macro \myname. The result is that both local bounding boxes are the same, the second one.

It can be fixed by expanding \myname in the optional arguments of the scope:

\begingroup
\edef\x{\endgroup
  \noexpand\begin{scope}[
    font=\noexpand\footnotesize,
    thick,
    local bounding box=\myname,
  ]
}\x

Full example:

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\tikzset{
  vertex/.style={
    circle,
    minimum size=4pt,
    inner sep=0pt,
    fill=orange,
  },
  mygraph/.pic={%
    \xdef\myname{\pgfkeysvalueof{/tikz/name prefix}}
    \typeout{\myname}
    \begingroup
    \edef\x{\endgroup
      \noexpand\begin{scope}[
        font=\noexpand\footnotesize,
        thick,
        local bounding box=\myname,
      ]
    }\x
      \node[vertex] (root)    at (4,  5) {};
      \node[vertex] (o)       at (4,  4) {};
      \node[vertex] (oc)      at (4,  3) {};
      \node[vertex] (oco)     at (4,  2) {};
      \node[vertex] (a)       at (5,  4) {};

      \draw
        \foreach \xfrom/\xto/\xlabel in {root/o/o, o/oc/c, oc/oco/o, root/a/a} {
          (\xfrom) to node[pos=0.5,fill=white]{\xlabel} (\xto)
        }
      ;
    \end{scope}
  }
}

\begin{tikzpicture}
  \pic (graph1) at (0,0) {mygraph};
  \pic (graph2) at (3,0) {mygraph};
  \draw[-latex] (graph1) to (graph2);
  % just for fun
  \draw[-latex,shorten >=1mm,shorten <=1mm]
    (graph1a) to[out=-10,in=160] (graph2root)
  ;
  % more fun
  \draw[red] (graph1.south west) rectangle (graph1.north east);
  \draw[blue] (graph2.south west) rectangle (graph2.north east);
\end{tikzpicture}
\end{document}

Result

BTW, as can be seen by the transparent background, the nodes with white backgrounds in the middle of the lines aren't looking too good, especially the truncated line ends of sloped lines.

This can be fixed by:

  \draw
    \foreach \xfrom/\xto/\xlabel in {root/o/o, o/oc/c, oc/oco/o, root/a/a} {
      ($(\xfrom)!.5!(\xto)$) node (tmp) {\xlabel}
      (\xfrom) -- (tmp) (tmp) -- (\xto)
    }

Full example:

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{calc}
\begin{document}
\tikzset{
  vertex/.style={
    circle,
    minimum size=4pt,
    inner sep=0pt,
    fill=orange,
  },
  mygraph/.pic={%
    \xdef\myname{\pgfkeysvalueof{/tikz/name prefix}}
    \typeout{\myname}
    \begingroup
    \edef\x{\endgroup
      \noexpand\begin{scope}[
        font=\noexpand\footnotesize,
        thick,
        local bounding box=\myname,
      ]
    }\x
      \node[vertex] (root)    at (4,  5) {};
      \node[vertex] (o)       at (4,  4) {};
      \node[vertex] (oc)      at (4,  3) {};
      \node[vertex] (oco)     at (4,  2) {};
      \node[vertex] (a)       at (5,  4) {};

      \draw
        \foreach \xfrom/\xto/\xlabel in {root/o/o, o/oc/c, oc/oco/o, root/a/a} {
          ($(\xfrom)!.5!(\xto)$) node (tmp) {\xlabel}
          (\xfrom) -- (tmp) (tmp) -- (\xto)
        }
      ;
    \end{scope}
  }
}

\begin{tikzpicture}
  \pic (graph1) at (0,0) {mygraph};
  \pic (graph2) at (3,0) {mygraph};
  \draw[-latex] (graph1) to (graph2);
  % just for fun
  \draw[-latex,shorten >=1mm,shorten <=1mm]
    (graph1a) to[out=-10,in=160] (graph2root)
  ;
  % more fun
  \draw[red] (graph1.south west) rectangle (graph1.north east);
  \draw[blue] (graph2.south west) rectangle (graph2.north east);
\end{tikzpicture}
\end{document}

Refined result

Heiko Oberdiek
  • 271,626
  • Thanks a lot! But I had the typeout at the very moment when \myname was retrieved and used, and this gave the correct name. How is that a timing issue? IMHO the first name reused when the second pic is drawn. (And you are absolutely right about the fill=white thing, I just more or less copied the code from this question. –  May 04 '18 at 10:32
  • @marmot \typeout expands immediately, but the argument to local bounding box at a later time apparently. – Heiko Oberdiek May 04 '18 at 11:04
  • 1
    The \x tricks works but it is complex. You can more simply use local bounding box/.expanded=\myname... – Paul Gaborit Nov 14 '19 at 14:05