4

New version

\begin{pgfonlayer} cannot be used inside a matrix. If I try, the node is completely misplaced:

image

I tried to submit a bug on tikz here https://github.com/pgf-tikz/pgf/issues/1282, does any of you has an idea how to fix this tikz bug? Note that even if I would be curious to hear about workarounds, I would really prefer a real way to fix tikz, so I’d ideally like to assign the bounty to someone that actually solves the Tikz bug.

MWE

\documentclass[]{article}

\usepackage{tikz}

\begin{document}


\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}   %% some additional layers for demo

\begin{tikzpicture}
  \node [matrix] (my matrix) at (2,1)
  {
    \node[fill=pink,name=hello]{Hello}; & \node{A}; \\
    \node{B};                           & \node[fill=green,name=bye]{Bye}; \\
  };
  \draw[very thick, red] (hello.center) -- (bye.center);
\end{tikzpicture}

Adding pgfonlayer only: I would expect the line to be above Hello, but below Bye. Instead, the node is completely misplaced:

\begin{tikzpicture}
  \node [matrix] (my matrix) at (2,1)
  {
    \node[fill=pink,name=hello]{Hello}; & \node{A};\\
    \node{B}; &
    \begin{pgfonlayer}{foreground}
      \node[fill=green,name=bye]{Bye};
    \end{pgfonlayer}
    \\
  };
  \draw[very thick, red] (hello.center) -- (bye.center);
\end{tikzpicture}

\end{document}

Below is the original question (back then I thought the issue was with labels and a custom node on layer style… in fact it is much more general). If you also manage to solve it at the same time, that would be awesome.

Old version:

I've been trying this answer https://tex.stackexchange.com/a/20426/116348 to put a node on a layer using only its style, like in \node[node on layer=front]{A};. It works great... except in tikz matrices (tested with tikzcd). In that case, the node goes out of the matrix... and it's still worse for nodes with labels. Any idea how to fix it?

enter image description here

MWE

\documentclass{article}
%\url{https://tex.stackexchange.com/q/46957/86}
\usepackage{tikz}
\usetikzlibrary{cd}
%\usepackage[tracelevel=silent]{trace-pgfkeys}
\pgfdeclarelayer{back}
\pgfdeclarelayer{front}
\pgfsetlayers{back,main,front}

\makeatletter
\pgfkeys{%
  /tikz/on layer/.code={
    \pgfonlayer{#1}\begingroup
    \aftergroup\endpgfonlayer
    \aftergroup\endgroup
  },
  /tikz/node on layer/.code={
    \gdef\node@@on@layer{%
      \setbox\tikz@tempbox=\hbox\bgroup\pgfonlayer{#1}\unhbox\tikz@tempbox\endpgfonlayer\egroup}
    \aftergroup\node@on@layer
  },
  /tikz/end node on layer/.code={
    \endpgfonlayer\endgroup\endgroup
  }
}

\def\node@on@layer{\aftergroup\node@@on@layer}

\makeatother
\begin{document}
No layer:
\begin{tikzcd}
  |[fill=green, label={[fill=red,circle,outer sep=-1mm]Lab}]| A \rar & B
\end{tikzcd}

One layer, no label:
\begin{tikzcd}
  |[fill=green, node on layer=front]| A \rar & B
\end{tikzcd}

Both:
\begin{tikzcd}
  |[fill=green, node on layer=front, label={[fill=red,circle,outer sep=-1mm,node on layer=back]Lab}]| A \rar & B
\end{tikzcd}


\end{document}
8
  • label uses append after command to put another node outside of the path so god knows what \tikz@tempbox is actually referring to at that point. Commented Oct 14, 2021 at 10:35
  • @HenriMenke So I don't have any method to put a label in a different layer from a matrix? It would be very practical in my application...
    – tobiasBora
    Commented Oct 14, 2021 at 14:08
  • I'm sure it's possible, just not like that. Commented Oct 14, 2021 at 14:35
  • @HenriMenke Oh, then I would love to hear your solution ^^
    – tobiasBora
    Commented Oct 14, 2021 at 15:00
  • 1
    This doesn't surprise me. I've noticed as much when experimenting with my ext.layers library … At the end of the matrix a special operation is called that corrects all nodes' positions because only then the correct position of the matrix on the canvas is known. A PGFlayer is just a TeX box that gets filled over time and only at the end the boxes get unpacked in the right order. The node will be put on its special layer before the matrix' position is correct on the main layer. I'm not sure we can relocate a box' content after it's in there. Commented Oct 3, 2023 at 15:09

2 Answers 2

3
+200

Matrices are weird

A PGF matrix uses the \halign primitive and is somewhat closer to a tabular than any other construct in PGF/TikZ.

The final place of each node is only known after the matrix has been constructed and for that each node name will be collected in a big macro and twice a loop will iterate over all nodes and correct their positions. However where as the matrix itself will be placed on the correct position, with the pgfonlayer environment you place its content in a different box which will not get correctly transformed to the right place.

(The internals are weird, it's a combination of boxes, \halign, the way PGF keeps track where nodes are …)

How can we keep track of our own stuff?

Since there's no nice way to hook into this looping mechanism we're using our own coordinate as a helper into this hook system.

We can't use the original layer boxes – yes, each layer is just another TeX box that gets filled over time and at the end of the picture, the layers will be unpacked in the proper order – but we need at least one box for each cell per matrix to then insert these stored away mini-pictures in the correct layer box.

The position of the aforementioned coordinate is used to place our own TeX box with the help of PGF's \pgfqboxsynced.

Boxes

At first, I gave each box, i.e. mini-picture we store away, a unique name based on their row and column number (which is still the default for pgfmatrixlayer's optional argument). This makes sure that you can't use the same box for different cells. However, this might be wasteful since it allocates a lot of boxes you might only use once.

However, if you only need one or two boxes total (but each time they're in a different cell) or if you want to put different content of one cell on different layers, you can or must use the optional argument and give them your own name.
Don't use the same name in one matrix twice as the optional argument to pgfmatrixlayer in different cells.

The \pgfplacematrixbox places said box – hopefully correctly – and then empties the box again for the next matrix. No other “garbage collection” is done to make sure you don't accidentally use a box in two matrices without placing the content in between.

Using ext-layers with the manual approach above

I've now combined the approach above with the experimental1) ext.layers library from my tikz-ext package.

The key to use is matrix node on layer = <name>:<layer> where <name> (and the colon) is optional and will be <row>-<column> as before if it's not given. So if you want to use two nodes in one cell on different layers you will need to provide custom <name>s.

The every matrix style is set up so that a collection is reset and used after the matrix whereas the matrix node on layer key will fill that list according to its parameters.

This feels very close to what PGF describes as Deferred Node Positioning but I haven't experimented with that.

Label behind its parent node?

That said, if all you want is to put a node behind its parent, it's much easier:

Every TikZ path (i.e the whole \path … ;)2) comes with three layers already:

These layers and keys will be used with nodes, pics, edges and plotmarks: basically everything that has its own path (but not arrow tips).

Just using label={[behind path]Lab} will place the label behind its parent unless the default has been changed.


1) It's so experimental in fact I just found two to three bugs.
2) Remember that \node is just \path node and everything is on a path even if that path is otherwise empty.

Code

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{cd, ext.layers}
\makeatletter
%%%BEGIN_FOLD manual way
\def\pgfplacematrixbox#1{%
  \pgfutil@IfUndefined{pgf@sh@nt@pgf@matrixlayer@#1}{%
    \pgfutil@packagewarning{PGF layer matrices}{No PGF matrix layer #1 known.}%
  }{%
    \pgfsys@beginscope
      \pgfsettransform{\csname pgf@sh@nt@pgf@matrixlayer@#1\endcsname}%
      \expandafter\pgfqboxsynced\csname pgf@matrixlayer@#1\endcsname
    \pgfsys@endscope
    \global\expandafter\setbox\csname pgf@matrixlayer@#1\endcsname
      \box\pgfutil@voidb@x
  }%
}
\newenvironment*{pgfmatrixlayer}[1][\the\pgfmatrixcurrentrow
                                   -\the\pgfmatrixcurrentcolumn]{%
  \edef\pgf@m{pgf@matrixlayer@#1}%
  \pgfutil@ifundefined{\pgf@m}{%
    \csname pgf@newbox\expandafter\endcsname\csname\pgf@m\endcsname
  }{}%
  \begingroup % quick version of
    % \path[name prefix=,name suffix=,reset cm]coordinate(\pgf@m);
    % the \pgf@nodecallback is called by \pgfmultipartnode
    % which is used by TikZ node and coordinate but not by \pgfcoordinate
    % this is our hook into the whole matrix node callback stuff
    % we're just using a coordinate that's handled by PGF
    % so we don't have to deal with the two (!) corrections by ourselves
    \pgftransformreset
    \pgfcoordinate{\pgf@m}{\pgfpointorigin}%
    \expandafter\pgf@nodecallback\expandafter{\pgf@m}%
  \endgroup
  % the rest is basically a clone of pgfcorelayers.code.tex's
  % implementation of \pgfonlayer
  \pgfsetlinewidth{.4pt}%
  % pgfonlayers boxes are also global,
  % we need this too because we're inside a cell inside a matrix
  \global\expandafter\setbox\csname\pgf@m\endcsname=\hbox to 0pt\bgroup
    \expandafter\box\csname\pgf@m\endcsname
    \begingroup
}{%
    \endgroup
    \hss
  \egroup
}
%%%END_FOLD

%%%BEGIN_FOLD using ext.layers and a few macros from the manual way
\tikzset{
  /tikz-ext/layers/patch=node, % enable patch (should be scoped to necessary path)
  reset matrix boxes/.code=%
    \global\let\tikzext@layers@matrixcollection\pgfutil@empty,
  every matrix/.append style={
    reset matrix boxes,
    append after command=\pgfextra{%
      \pgfinterruptpath\tikzext@layers@matrixcollection\endpgfinterruptpath}},
  @matrix node on layer/.code args={#1:#2}{%
    \edef\pgf@m{pgf@matrixlayer@#1}% same as pgfmatrixlayer environment
    \pgfutil@ifundefined{\pgf@m}{%   but uses ext.layers interface
      \csname pgf@newbox\expandafter\endcsname\csname\pgf@m\endcsname}{}%
    \begingroup\pgftransformreset\pgfcoordinate{\pgf@m}{\pgfpointorigin}%
      \expandafter\pgf@nodecallback\expandafter{\pgf@m}\endgroup
    \pgfkeysalso{/tikz-ext/layers/in box/.expand once=\csname\pgf@m\endcsname}%
    \pgfutil@g@addto@macro\tikzext@layers@matrixcollection{%
      \pgfonlayer{#2}\pgfplacematrixbox{#1}\endpgfonlayer}
  },
  matrix node on layer/.code={% <name>:<layer> or <layer>
    \pgfutil@in@{:}{#1}%
    \ifpgfutil@in@ % .expanded just to be safe
      \tikzset{@matrix node on layer/.expanded={#1}}%
    \else          % .expanded needed
      \tikzset{@matrix node on layer/.expanded=%
        \the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn:{#1}}%
    \fi}}
%%%END_FOLD
\makeatother

%%% Layer seup
\pgfdeclarelayer{back}
\pgfdeclarelayer{front}
\pgfsetlayers{back, main, front}

\begin{document}
\begin{tikzpicture}
\node [matrix, draw] (my matrix) at (2,1) {
  \begin{pgfmatrixlayer}
    \node[fill=pink] (back) {back};
  \end{pgfmatrixlayer}
& \node{A}; \\
  \node{B};
& \begin{pgfmatrixlayer}
    \node[fill=green] (front) {front};
  \end{pgfmatrixlayer}
\\};
\begin{pgfonlayer}{front}
\pgfplacematrixbox{2-2}
\end{pgfonlayer}
% after front but should be below
% before back but should be above
\draw[very thick, red] (back.center) -- (front.center);
\begin{pgfonlayer}{back}
\pgfplacematrixbox{1-1}
\end{pgfonlayer}
\end{tikzpicture}

\tikzset{every label/.append style={fill=red, shape=circle, label distance=+-1mm}}
\begin{tikzcd} % normal
  |[fill=green, label=Lab]| A \rar & B
\end{tikzcd}

\begin{tikzcd} % one node in front (uses box 1-1)
  |[fill=green, matrix node on layer=front]| A \rar & B
\end{tikzcd}

\begin{tikzcd} % one node in front, one node in back
  |[fill=green,
    matrix node on layer=front:front,
    label={[matrix node on layer=back:back]Lab}]| A \rar & B
\end{tikzcd}

\begin{tikzcd} % label behind node is much easier
  |[fill=green, label={[behind path]Lab}]| A \rar & B
\end{tikzcd}
\end{document}

Output

enter image description here

enter image description here

3
  • Who, thanks for this solution! This is indeed not as easy to use as the original code, so I’ll wait a bit before giving away the bounty, but I think I can imagine a solution to make the syntax not too bad using a style like node on layer=mylayer in my special use case (by running the code later)… I need to think about it since I am not so sure how to run \begin{pgfmatrixlayer} before the node since I already started to read the node content… But at least I see a hope, let me know if you have some suggestions?
    – tobiasBora
    Commented Oct 3, 2023 at 18:32
  • @tobiasBora I've combined the previous approach (store content in a box and place box later in the correct position inside the desired layer) with the key interface of my ext.layers library: matrix node on layer. That said, if all you wanted was to place a label behind its parent, there are easier ways, just: label=[behind path]Lab. Commented Oct 3, 2023 at 20:34
  • That is really cool, thank you so much! I’ll experiment a bit to automatically find the name when I define a subnode, but it should not be too hard now that you did the hard work! Thanks again! And your tikz-ext library looks great!
    – tobiasBora
    Commented Oct 3, 2023 at 21:10
0

enter image description here

One solution is to use either a preaction or a postaction for the node you want to insert on another layer. The behaviour of the label is strange though. It works acceptably when kept on the main layer.

Remark. When trying to introduce a node through the command you defined node on layer=... without a preaction, the output is a node placed at the center of the bounding box.

I include the code for the example above, but it is mainly your original code the some preactions added.

The code

\documentclass{article}
%\url{https://tex.stackexchange.com/q/46957/86}
\usepackage{tikz}
\usetikzlibrary{cd}
\pgfdeclarelayer{back}
\pgfdeclarelayer{front}
\pgfsetlayers{back, main, front}

\makeatletter
\pgfkeys{%
  /tikz/on layer/.code={
    \pgfonlayer{#1}\begingroup
    \aftergroup\endpgfonlayer
    \aftergroup\endgroup
  },
  /tikz/node on layer/.code={
    \gdef\node@@on@layer{%
      \setbox\tikz@tempbox=\hbox\bgroup\pgfonlayer{#1}
      \unhbox\tikz@tempbox\endpgfonlayer\egroup}
    \aftergroup\node@on@layer
  },
  /tikz/end node on layer/.code={
    \endpgfonlayer\endgroup\endgroup
  }
}

\def\node@on@layer{\aftergroup\node@@on@layer}
\makeatother

\begin{document}

No layer:
\begin{tikzcd}
  |[fill=green, label={[fill=yellow, circle, outer sep=-1mm]Lab}]| A \rar
  & B
\end{tikzcd}

\vspace{1cm}
Many layers, no label:
\begin{tikzcd}[outer sep=1ex, column sep=4em]
  |[postaction={fill=green, node on layer=back}]| A
  \arrow{d} \arrow{r}
  & B \\
  C \arrow{r}
  & |[shift={(0, .5)},
  preaction={fill=blue!50, node on layer=front}]| D \arrow{r}
  \arrow{d}
  & D' \\
  E \arrow{r}
  & F
\end{tikzcd}

\vspace{1cm}
Two layers and label on \emph{main}:
\begin{tikzcd}
  |[postaction={fill=green, node on layer=front}]
  [label={%
    [fill=yellow, circle, outer sep=-1mm] 90:Lab
  }]| A \rar & B
\end{tikzcd}

\vspace{1cm}
Two layers and label on \emph{front} outside the preaction (or post):
\begin{tikzcd}
  |[postaction={fill=green, node on layer=front}]
  [label={%
    [fill=yellow, circle, node on layer=front, outer sep=-1mm] 90:Lab
  }]| A \rar & B
\end{tikzcd}

\vspace{1cm}
Two layers and label on \emph{front}:
\begin{tikzcd}
  |[postaction={fill=green, node on layer=front}]
  [postaction={
    label={%
      [fill=yellow, circle, node on layer=front, outer sep=-1mm] 90:Lab
    }
  }]| A \rar & B
\end{tikzcd}

\end{document}
1
  • Thanks for your answer, and sorry for my late answer… but I'm confused, now we get rid of misplaced figure… but it seems like node on layer=front is not doing anything now. Like on the figure "two layers and the label on main", I would expect the green node to be above the yellow label, but it's still behind, like originally
    – tobiasBora
    Commented Feb 23, 2023 at 22:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .