pgfonlayer 在 tikz 矩阵内失败

pgfonlayer 在 tikz 矩阵内失败

新版本

\begin{pgfonlayer}不能在矩阵内部使用。如果我尝试,节点会完全放错位置:

图像

我尝试在这里提交 tikz 上的错误https://github.com/pgf-tikz/pgf/issues/1282,你们当中有人知道如何修复这个 tikz 错误吗?请注意,即使我很想了解解决方法,我还是更希望找到一种真正修复 tikz 的方法,因此我理想情况下希望将赏金分配给真正解决 Tikz 错误的人。

平均能量损失

\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}

以下是原始问题(当时我以为问题出在标签和自定义node on layer样式上……事实上,问题更为普遍)。如果你能同时解决这个问题,那就太棒了。

旧版本:

我一直在尝试这个答案https://tex.stackexchange.com/a/20426/116348仅使用其样式将节点放在图层上,就像在 中一样\node[node on layer=front]{A};。它工作得很好……除了在 tikz 矩阵中(使用 tikzcd 测试)。在这种情况下,节点会超出矩阵……对于带有标签的节点来说,情况更糟。有什么想法可以解决它吗?

在此处输入图片描述

平均能量损失

\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}

答案1

矩阵很奇怪

PGF 矩阵使用原始矩阵,并且比 PGF/TikZ 中的任何其他构造都\halign更接近于。tabular

只有在构建矩阵后才能知道每个节点的最终位置,为此,每个节点名称将收集在一个大宏中,然后循环两次遍历所有节点并更正它们的位置。然而,尽管矩阵本身将被放置在正确的位置,但通过环境,pgfonlayer您将它的内容放在不同的框中,而这些框不会正确地转换到正确的位置。

(其内部结构很奇怪,它是盒子的组合,\halignPGF 跟踪节点的位置的方式是……)

我们如何才能追踪我们自己的东西?

由于没有很好的方法来挂钩这个循环机制,我们使用我们自己的坐标作为这个挂钩系统的辅助。

我们不能使用原始的图层框 - 是的,每个图层只是另一个 TeX 框,它会随着时间的推移而被填充,并且在图片的末尾,图层将按照正确的顺序解包 - 但我们需要每个矩阵的每个单元至少一个框,然后将这些存储的迷你图片插入正确的图层框中。

上述坐标的位置用于在 PGF 的帮助下放置我们自己的 TeX 框\pgfqboxsynced

盒子

起初,我根据行号和列号为每个框(即我们存储的迷你图片)赋予一个唯一的名称(这仍然是 的pgfmatrixlayer可选参数的默认值)。这可确保您不能对不同的单元格使用相同的框。但是,这可能会造成浪费,因为它会分配很多您可能只会使用一次的框。

但是,如果你总共只需要一到两个盒子(但每次它们都在不同的单元格中)或者如果你想把一个单元格的不同内容放在不同的层上,你可以或者必须使用可选参数并赋予它们自己的名称。不要在一个矩阵中两次使用与不同单元格中的
可选参数相同的名称。pgfmatrixlayer

放置\pgfplacematrixbox所述盒子(希望正确)然后再次清空盒子以用于下一个矩阵。没有进行其他“垃圾收集”,以确保您不会意外地在两个矩阵中使用盒子而不在中间放置内容。

使用ext-layers上述手动方法

我现在将上述方法与我的实验1)库结合起来 ext.layerstikz-ext包裹

使用的关键是matrix node on layer = <name>:<layer>where <name>(和冒号)是可选的,<row>-<column>如果没有给出,则与以前一样。因此,如果您想在不同层上的一个单元中使用两个节点,则需要提供自定义的<name>s。

every matrix设置样式,以便在矩阵之后重置和使用集合,而键将matrix node on layer根据其参数填充该列表。

这感觉非常接近 PGF 所描述的延迟节点定位但我还没有尝试过。

标签位于其父节点后面?

也就是说,如果您想要的只是将一个节点放在其父节点后面,那就容易多了:

每个 TikZ 路径(即整个\path … ;2)已经带有三层:

这些层和键将与节点、图片、边和绘图标记一起使用:基本上所有具有其自己的路径(但没有箭头)的东西。

除非默认值已被更改,否则只需使用label={[behind path]Lab}即可将标签置于其父标签后面。


1)事实上,它只是实验性的,我只发现了两到三个错误。2
)请记住,一切\node\path node在路径上,即使该路径是空的。

代码

\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}

输出

在此处输入图片描述

在此处输入图片描述

答案2

在此处输入图片描述

一种解决方案是使用 apreaction或 apostaction作为要插入另一层的节点。不过标签的行为很奇怪。当保留在主层上时,它可以正常工作。

评论。node on layer=...当尝试通过您定义的不带 的命令引入节点时preaction,输出是位于边界框中心的节点。

我包含了上面示例的代码,但主要是您原来的代码和一些preactions添加的代码。

代码

\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}

相关内容