6

Is there a general way to disable preactions/postactions in TikZ on which effects such as shadows depend?

If not, is there a straightforward way to enable users to add options to an existing style so that they can neutralise their effects e.g. by setting a shadow's opacity to 0 as in https://tex.stackexchange.com/a/354728/?

First some code which works (I think) as I want it to:

\documentclass[tikz,border=10pt]{standalone}
\usetikzlibrary{shadows,positioning}
\newif\ifshowplaceholders
\showplaceholdersfalse
\tikzset{%
  my node/.style={fill=gray,draw=black},
  placeholders/.is choice,
  placeholders/off/.code={%
    \showplaceholdersfalse\let\myphantom\phantom
    \pgfqkeys{/tikz}{placeholder/.style={my node,fill=none, draw=none}}%
  },
  placeholders/on/.code={%
    \showplaceholderstrue\def\myphantom##1{##1}%
    \pgfqkeys{/tikz}{placeholder/.style={my node,opacity=.25,text opacity=.5}}%
  },
  placeholders/.default=on,
  placeholders=off,  
}
\begin{document} 
\begin{tikzpicture}
  \node (n) [my node] {My Node};
  \node (p) [above=10pt of n,placeholder] {\myphantom{Placeholder}};
\end{tikzpicture} 
\end{document}

The basic idea is that placeholder nodes can be created to help with the placement of 'real' nodes. Under normal circumstances, these are invisible, but they can still be used for placement.

real node with invisible placeholder above

If desired, they can be rendered visible for debugging purposes.

  placeholders=on,

real node with visible placeholder above

Now suppose somebody wants to add a drop shadow to the 'real nodes'

  my node/.style={fill=gray,drop shadow,draw=black},
%   placeholders=on,

Now the placeholder is invisible, as it should be, but its shadow is not.

real node with invisible placeholder's shadow above

Ideally, I would like to disable preactions and postactions for placeholder nodes, but, not knowing how to do that, I tried to at least enable users to add neutralising code, but I can't get it to work. I suspect this has something to do with expansion, but I've not found a way which doesn't yield either an error or visibility.

I wouldn't expect the following to work, so I'm not surprised it doesn't, but I'm including it because it represents a schematic of how I was thinking the problem might be handled.

\documentclass[tikz,border=10pt]{standalone}
\usetikzlibrary{shadows,positioning}
\newif\ifshowplaceholders
\showplaceholdersfalse
\tikzset{%
  my node/.style={fill=gray,draw=black},
  placeholders/.is choice,
  placeholders/off/.code={%
    \showplaceholdersfalse\let\myphantom\phantom
    \pgfqkeys{/tikz}{placeholder/.style={my node,fill=none, draw=none}}%
  },
  placeholders/on/.code={%
    \showplaceholderstrue\def\myphantom##1{##1}%
    \pgfqkeys{/tikz}{placeholder/.style={my node,opacity=.25,text opacity=.5}}%
  },
  placeholders/.default=on,
  placeholders=off,
  placeholders/when off/.code={%
%     \edef\tempa{#1}%
    \pgfqkeys{/tikz}{%
      placeholders/off/.append code={%
        \pgfqkeys{/tikz}{placeholder/.append code={\pgfqkeys{/tikz}{#1}}}%
      },
    }%
    \ifshowplaceholders
    \else
      \pgfqkeys{/tikz}{placeholder/.append code={\pgfqkeys{/tikz}{#1}}}%
    \fi
  },
  placeholders/when on/.code={%
%     \edef\tempa{#1}%
    \pgfqkeys{/tikz}{%
      placeholders/on/.append code={%
        \pgfqkeys{/tikz}{placeholder/.append code={\pgfqkeys{/tikz}{#1}}}%
      }%
    }%
    \ifshowplaceholders
      \pgfqkeys{/tikz}{placeholder/.append code={\pgfqkeys{/tikz}{#1}}}%
    \fi
  },
  my node/.style={fill=gray,drop shadow,draw=black},
  placeholders/when off={drop shadow={fill=none}},
%   placeholders=on,
}
\begin{document} 
\begin{tikzpicture}
  \node (n) [my node] {My Node};
  \node (p) [above=10pt of n,placeholder] {\myphantom{Placeholder}};
\end{tikzpicture} 
\end{document}

I actually began by trying with .append style, but that didn't work either. As a last resort, I can use expl3, but I'd rather not do that for what seems as if it should be a relatively simple problem.

I should mention some options I'm not considering which might seem good approaches from the MWE above:

  • not using my node in the placeholder style at all (I don't know what the style will be and I need the placeholders to be standardised relative to that style);
  • eliminating placeholders for the final run (then placing things relative to them wouldn't work - these are not being used like the temporary grids used for placement).

Things I could do:

  • declare the package incompatible with shadows and similar things when applied to elements of this type, unless placeholders are disabled (tempting, but somewhat nuclear, and people will want to do it anyway);
  • introducing (yet another) layer (or two), filling it with white and creating the placeholders behind it when off (but the package already creates a lot of layers and this seems a lot of infrastructure just to disable shadows - and it won't work if users use pre/postaction-involving things which draw on different layers).

My default is to just document the incompatibility or to do something with expl3, but I'm hoping there's some better solution.

cfr
  • 198,882
  • 1
    In the options to the placeholder node, could you do something like drop shadow/.style={}? You might have to find a hook that is executed before the every node style, or perhaps define \placeholder as \path[disable stuff] node[placeholder]. – Andrew Stacey Aug 31 '23 at 06:54
  • 1
    “I would like to disable preactions and postactions for placeholder nodes”: They are simply stored in \tikz@preactions and \tikz@postactions. It should be possible to def them to {} or let them to any of the defined empty macros. You should be able to use phantom node as defined in A688111 to avoid the \myphanntom key. – Qrrbrbirlbel Aug 31 '23 at 07:57
  • 1
    That said, maybe a \placeholder macro that deals with everything is a better aproach. But at the end of the day, a user could do anything with every node and my node you can't account for. – Qrrbrbirlbel Aug 31 '23 at 08:34
  • @AndrewStacey I did try that, but the result seemed to be unbalanced {}. – cfr Aug 31 '23 at 13:36
  • @Qrrbrbirlbel I know and I'm not trying to make it user-proof. But, right now, there is no means to add shadows to nodes of this kind and, while I have no problem saying the package is incompatible with doing more complicated things with these nodes, shadows seem a relatively reasonable thing to want. (Externalisation and spy are also incompatible, as far as I can tell, but shadows strike me as a simpler desideratum.) – cfr Aug 31 '23 at 13:39
  • @Qrrbrbirlbel Thanks. \letting those to \@empty seems to work well. I've also switched to your phantom node. I'm not sure it matters much in this case as there's no possibility of a line break in the content (even though the nodes are multiline), but perhaps it's neater and I'm committed to internals by zapping pre/post actions anyway. Do you want to answer? – cfr Aug 31 '23 at 14:49
  • Code: phantom node/.code=\tikz@addoption{% <ref> \expandafter\let\csname pgf@sh@boxes@\tikz@shape\endcsname\pgfutil@empty}, % <ref> zap preactions/.code=\let\tikz@preactions\@empty, zap postactions/.code=\let\tikz@postactions\@empty, placeholders/off/.code={% \chronos@placeholdersfalse \pgfqkeys{/chronos}{placeholder/.style={fill=none, draw=none,/chronos/.cd,phantom node,zap preactions,zap postactions}}% }, – cfr Aug 31 '23 at 14:51
  • placeholders/on/.code={% \pgfqkeys{/chronos}{placeholder/.style={on chronos middle ground layer,fill opacity=.1,draw opacity=.25,text opacity=.5,/chronos/.cd,zap preactions,zap postactions}}% }, – cfr Aug 31 '23 at 14:52

1 Answers1

3

I would like to disable preactions and postactions for placeholder nodes

Pre- and postactions are just stored in list-like macros:

  • \tikz@preactions and
  • \tikz@postactions.

Setting these to empty removes all previously added actions.

Of course, a user can just add another action after you emptied them out. But you can always kill the keys themselves: postaction/.code=, preaction/.code=. (But that also applies to draw, fill, etc.)

Speaking of draw and fill, I've replaced your draw=none, fill=none with path only which disables all actions: draw, fill, shading, fading, pattern, clip, use as bounding box, path picture, double, ….

I've also introduced the phantom node key which disables the shipout of the TeX boxes by PGF for the node in question. The shape's definition will still take the measurement of those boxes in consideration but will just not put in on the pages.

The \pgf@sh@boxes@<shape name> is yet another macro that contains a list. In most usecases, this will just be text which stands for \pgfnodeparttextbox. See the manual on \pgfmultipartnode for a few more words on this topic.


If you compare the two pages Code 1 returns you will notice that the one where the placeholder node is not shown is a bit smaller. (And if you don't add line width=1ex to my node.) This is because a path that is drawn will get a padding of .5\pgflinewidth on all sides added to the bounding box of the picture.

This does not happen when the path is not drawn (i.e. path only or draw = none).

I would just add overlay to every placeholder node thereby disabling any bounding box considerations for those nodes. This seems inline with its auxilliary usage but will of course print those placeholder nodes outside of the picture's bounding box as the case may be.

I've played around with extra actions for path that allow more control of this by adding undraw and draw undraw where undraw wouldn't stroke the path but alters the bounding box according to the line width and draw undraw which strokes the path but ignores the linewidth for the bounding box.

Unfortunately, this needs a redefinition of PGF' \pgfusepath macro, I doubt you want to experiment with this.


Speaking of boxes …
when TikZ encounters nodes, edges, pics or plotmarks on a path they will more or less constructed and typeset immediately but put inside a box, either the foreground or the background one so that a path actually provides three layers and nodes can be on top of the path irregardless of when they are encountered along the path.

While it isn't trivial to put just a node onto a separate PGF layer (see [1], [2] (sec 5), [3]) it is rather easy to make a node vanish.

While there is the special discard layer, if you really don't want to see that node … instead of the path's foreground or background box, we can just use any box and then not use it (it's going to be forgotten after the path's ; as well).

By not setting path only or draw = none the bounding box will still be adjusted – at least without overlay or similar, of course – but not vary just because of the line width. All the actions and typesetting will still be carried out and the node will be created.

Code 1 (disable drawing and actions)

\documentclass[tikz,border=10pt]{standalone}
\usetikzlibrary{shadows, positioning}
\newif\ifshowplaceholders
\showplaceholdersfalse
\makeatletter
\tikzset{
  no actions/.code=\let\tikz@preactions\pgfutil@empty
                   \let\tikz@postactions\pgfutil@empty,
  phantom node/.code=\tikz@addoption % no recovery!
    {\expandafter\let\csname pgf@sh@boxes@\tikz@shape\endcsname\pgfutil@empty}}
\makeatother
\tikzset{
  show placeholders/.is if=showplaceholders,
  placeholder/is on/.style={
    every placeholder node, opacity=.25, text opacity=.5},
  placeholder/is off/.style={
    every placeholder node, path only, no actions, phantom node},
  placeholder/.style={placeholder/is \ifshowplaceholders on\else off\fi},
  placeholders/when off/.style={/tikz/placeholders/is off/.append style={#1}},
  placeholders/when  on/.style={/tikz/placeholders/is  on/.append style={#1}}}

\tikzset{ my node/.style={fill=gray, draw=black}, every placeholder node/.style=my node} \begin{document} \begin{tikzpicture}[show placeholders, my node/.append style=drop shadow] \node (n) [my node] {My Node}; \node (p) [above=10pt of n, placeholder] {Placeholder}; \end{tikzpicture}

\begin{tikzpicture}[show placeholders=false, my node/.append style=drop shadow] \node (n) [my node] {My Node}; \node (p) [above=10pt of n, placeholder] {Placeholder}; \end{tikzpicture} \end{document}

Code 2 (put node into throwaway box)

\documentclass[tikz, border=10pt]{standalone}
\usetikzlibrary{shadows, positioning}
\newif\ifshowplaceholders
\showplaceholdersfalse
\makeatletter
\tikzset{
  discard node/.code=\setbox\pgfutil@tempboxa\box\pgfutil@voidb@x % empty out box
                     \def\tikz@whichbox{\pgfutil@tempboxa}}
\makeatother
\tikzset{
  show placeholders/.is if=showplaceholders,
  placeholder/is on/.style={every placeholder node, opacity=.25, text opacity=.5},
  placeholder/is off/.style={every placeholder node, discard node},
  placeholder/.style={placeholder/is \ifshowplaceholders on\else off\fi},
  placeholders/.is choice,
  placeholders/when off/.style={/tikz/placeholders/is off/.append style={#1}},
  placeholders/when  on/.style={/tikz/placeholders/is  on/.append style={#1}}}

\tikzset{ my node/.style={fill=gray, draw=black}, every placeholder node/.style=my node} \begin{document} \begin{tikzpicture}[show placeholders, my node/.append style=drop shadow] \node (n) [my node] {My Node}; \node (p) [above=10pt of n, placeholder] {Placeholder}; \end{tikzpicture}

\begin{tikzpicture}[show placeholders=false, my node/.append style=drop shadow] \node (n) [my node] {My Node}; \node (p) [above=10pt of n, placeholder] {Placeholder}; \end{tikzpicture} \end{document}

Qrrbrbirlbel
  • 119,821
  • This is way more than I expected in an answer. It will take me some time to digest and thank you so much! I won't necessarily use anything quite this complex in this particular case, because I have a lot of control over the content of the node and what's done to it, but it looks incredibly useful for more general cases. – cfr Sep 01 '23 at 15:19
  • Have I understood correctly that the discard option does take account of the line width, while the draw-nothing option doesn't? I've been ignoring that complication, but if there's a straightforward fix .... – cfr Sep 01 '23 at 15:48
  • May I use this in a package to be released under LPPL? – cfr Sep 01 '23 at 15:50
  • 1
    @cfr Yes, the discard node key does all the same things with the node it would do without it, it just puts the drawing in a box (to be thrown away). It's as if you would have done \sbox\@tempboxa{\TeX} The \TeX will still be typeset but we will never see it unless we use the box. And yes, of course, you may use this under LPPL. – Qrrbrbirlbel Sep 01 '23 at 17:23
  • Initial attempt with discard looks good. I can still zap pre- and post- for when placeholders are shown, which I think makes it easier visually. I don't want to go the overlay route because I'm relying on the placeholders to affect the bounding box, even though that's not obvious from the MWE. (I could workaround this, but I'd have to rethink how that bit worked and who wants to think? ;) Thanks so much. – cfr Sep 02 '23 at 01:13
  • I'd like to add a bounty for this answer, but I don't think I can do it yet. Feel free to remind me if I forget. – cfr Sep 02 '23 at 01:14