4

TiKz 3.0 introduce pics and I like them, but it seems that they are not as powerfull as I wish. For example I'm not able to use nested pics like this :

\tikzset{
  one/.pic = {\draw[pic actions] (0,0) -- (1,1);},
  two/.pic = {\path pic[pic actions]{one};}
}
\begin{tikzpicture}
  \path pic[ultra thick, red]{one};
  \path pic[thin]{two};
\end{tikzpicture}

Is this a bug, or is this simply how pics works?

There is no error message. Simply (Xe/PDF)LaTeX don't finish the job.

EDIT : Following Mark Wibrow's comment if we delete pic actions from pic{two} it works:

\tikzset{
  one/.pic = {\draw[pic actions] (0,0) -- (1,1);},
  two/.pic = {\path pic{one};}
}
\begin{tikzpicture}
  \path pic[ultra thick, red]{one};
  \path pic[thin, blue]{two};
\end{tikzpicture}

enter image description here

So the problem seems to be inside pic actions.

Kpym
  • 23,002
  • 1
    tikzpictures can't be nested, (TikZ) matrix can't be nested, (TikZ) pics can't be nested... – Paul Gaborit Dec 05 '14 at 16:40
  • You can put tikzpictures inside a node, and so make nastings. So in some sens you can ;) And why there is no error if it is by design ? – Kpym Dec 05 '14 at 17:05
  • You could try two/.pic={\pic {one};} – Mark Wibrow Dec 05 '14 at 17:23
  • @Mark Wibrow thanks. This means that we can nest pics. So the problem seems to be inside pic actions ... and it looks more like a bug in this case, no ? – Kpym Dec 05 '14 at 17:32
  • @Kpym my reading of the manual is that the pic actions key is intended to be used in path options inside a pic not in options to any pic. In fact it seems to make not much sense to say \path pic [pic actions] {...}. – Mark Wibrow Dec 05 '14 at 17:58
  • @Mark Wibrow my understanding is close to yours I think, but in what I understand if in a picture I have two paths (one of them could be another pic) and if I want to 'transverse' the actions only to one of them, I have to use pic actions for this one, not to the other. For example if in my exampe you replace draw with path in pic{one} and then you add draw when you use the two pics, the first one will be drawn, not the second one. – Kpym Dec 05 '14 at 18:47
  • 2
    @Kpym TikZ (in its current version) is not designed to allow tikzpictures into a node. Sometimes it works, but this is not guaranteed. It's the same for matrices (explicitly forbidden). For pics, I could be wrong. – Paul Gaborit Dec 05 '14 at 23:13
  • 1
    @Kpym In fact, nested pics are allowed but there is always some bugs. – Paul Gaborit Dec 05 '14 at 23:26
  • @Paul Gaborit thanks for your link. The intent of this question was to check if this is a bug before to put a ticket on sourceforge. – Kpym Dec 06 '14 at 08:49

3 Answers3

5

I just came across this and it seems to still be an issue with TikZ in 2023 (PGF version 3.1.10).

Digging a bit deeper in the code, we get that:

  • pic actions expands to \tikz@addmode{\tikz@picmode} which appends \tikz@picmode to the macro \tikz@mode.
  • But when TikZ encounters a pic then what it does is \let\tikz@picmode=\tikz@mode. So \tikz@picmode is the contents of the \tikz@mode of the outer picture.

Therefore, with a nested pic, we have the following journey:

  • At the outermost level, \tikz@mode contains the instructions on what to do to the path (whether to draw or fill it, for example).

  • When TikZ encounters the first pic, \tikz@picmode is let to this, so now contains these instructions. Within the pic, \tikz@mode is cleared.

  • Now we invoke pic actions which appends \tikz@picmode to \tikz@mode, so that \tikz@mode now consists of (at least) \tikz@picmode (there might have been other stuff also added to it).

  • We want to have a nested pic, so let's let TikZ encounter another pic. It aliases \tikz@mode as \tikz@picmode which means that \tikz@picmode now consists of (at least) \tikz@picmode.

    And here's our problem. It's the old \def\a{\a}\a problem. \tikz@picmode contains a reference to itself, so when it is (finally) invoked then it enters an infinite loop.

A possible solution (which might introduce additional problems, but at the very least solves this) is to add an expansion step so that when \tikz@picmode is appended to \tikz@mode then it is actually the contents of \tikz@picmode that is appended to \tikz@mode. This avoids the infinite recursion.

So pic actions ought to be:

\makeatletter
\tikzset{
  pic actions/.code=\expandafter\tikz@addmode\expandafter{\tikz@picmode}
}
\makeatother

In my own code, I'm currently calling this sub pic actions as I don't like to clobber TikZ's version of things even if I think I have an improvement.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/215580/86}
\usepackage{tikz}

\makeatletter \tikzset{ sub pic actions/.code=\expandafter\tikz@addmode\expandafter{\tikz@picmode} } \makeatother

\tikzset{ working demo/.pic={ \pic[sub pic actions] {demo sub}; }, not working demo/.pic={ \pic[pic actions] {demo sub}; }, demo sub/.pic={ \path[pic actions] (0,0) to[out=30,in=180] ++(1.5,.5) -- ++(0,.5) -- ++(.3,0) -- ++(0,-.5) to[out=0,in=150] ++(2.5,-.5) to[out=210,in=-30] (0,0); } }

\begin{document} \begin{tikzpicture} \pic[draw] { %not working demo}; \end{tikzpicture} \end{document}

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
3

I give here my partial knowledge of the problem.

Disclaimer

I'm not a texpert, and most of the thing that I write here are guesses.

Prerequisite

Some styles are inherited from the scope to the path and other are not. Some styles are inherited from the path to nodes and pics and other are not.

I don't know what is the precise rule, but I suppose that the only styles that are not inherited are the "actions" (as we can reed in the documentation about pic actions : "actions" are drawing, filling, shading, and clipping or any combination thereof).

How styling pics works

I don't know how exactly styling pics works, but my investigation (not of the code, because as I said I'm not an texpert) leads me to the following algorithm :

\tikzset{picname/.pic={the pic code}}
\pic[some style]{picname};

is transformed to something like

\begin{scope}[coordinate transform, every pic, some style]
  the pic code
\end{scope}

This is oversimplified version because there is foreground code and background code and so on ...

So pic actions is useful when we want to inherit some action, let say fill from 'some style' to a path that is inside 'the pic code'. And this is well documented in the doc.

The problem with 'pic actions'

The problem with pic actions is that when we investigate it with something like

\pgfkeysgetvalue{/tikz/pic actions/.@cmd}{\temp};
\wlog{\meaning\temp}

the result is

\long macro:#1\pgfeov ->\tikz@addmode {\tikz@picmode }

which is not what we observe for some "ordinary" style, like :

\long macro:#1\pgfeov ->\pgfkeysalso {fill}

So probably here is the key why using pic actions in nested pics fails.

I think that pic actions must be used only for simple inheritance. But still there is a bug in pic actions, because when you use it in nested pics latex freeze without throwing an error.

When we need more complexe inheritance, we need to use some styling as shown below.

How to overcome 'pic actions'

Here is an example of how we can create complexe picture with complexe style and realy complex nesting (it works in a fashion similar to @cfr answer).

\tikzset {
  every cherry fruit/.style = {fill,red},
  cherry fruit/.style={every cherry fruit/.append style={#1}},
  every cherry stem/.style = {brown, ultra thick},
  cherry stem/.style={every cherry stem/.append style={#1}},
  every cherry leaf/.style = {fill, green},
  cherry leaf/.style={every cherry leaf/.append style={#1}},
  cherry/.pic = {
    \draw[every cherry fruit] (0,0) {[rounded corners=1cm] -- (1,1) -- (2,-1) -- (0,-3) -- (-2,-1) -- (-1,1)} -- cycle;
    \draw[bend left, every cherry stem] (0,0) to coordinate[pos=.7] (A) (1,2);
    \draw[bend left, every cherry leaf] (A) to +(-1.5,1) to cycle;
  },
  every cherry one/.style={},
  cherry one/.style={every cherry one/.append style={#1}},
  every cherry two/.style={},
  cherry two/.style={every cherry two/.append style={#1}},
  two cherries/.pic = {
    \pic[every cherry one]{cherry};
    \pic[xshift=5cm, every cherry two]{cherry};
  }
}
\begin{tikzpicture}
  \pic[cherry fruit=yellow]{cherry};
  \pic[xshift=5cm]{cherry};
  \pic[yshift=-5cm, cherry fruit={fill=none, draw, ultra thick}, cherry one={cherry fruit=yellow}]{two cherries};
\end{tikzpicture}

enter image description here

Kpym
  • 23,002
2

You could pass the options you want as an argument to two:

\documentclass[tikz, border=5pt]{standalone}
\tikzset{
  one/.pic = {\path[pic actions] (0,0) -- (1,1);},
  two/.pic = {\path pic[#1]{one};}
}

\begin{document}

  \begin{tikzpicture}
    \path pic[ultra thick, red, draw]{one};
    \path pic{two={thin, blue, draw}};
  \end{tikzpicture}

\end{document}

Since I'm not sure just how you're using this, I'm not sure whether this is an option you've rejected, an option which you've not rejected yet but will have to reject now, or an option which might be of use.

cfr
  • 198,882
  • thanks for your answer, but it doesn't respond to my question 'Is this a bug, or is this simply how pics works?'. I know that I can do what you propose, but doing this pic{two} is not used like standard pic. I'm more and more convinced that this is a bug. I can't see a reason to not be able to pass pic actions style to the subpic. – Kpym Dec 06 '14 at 07:11
  • @Kpym I was really responding to your comment about the problem of one being drawn but not the other. – cfr Dec 06 '14 at 15:30
  • ok. And I was responding to Mark Wibrow when he said 'In fact it seems to make not much sense to say \path pic [pic actions] {...}.' ;) – Kpym Dec 06 '14 at 15:55