TikZ 中的形状可以由“子形状”组成吗?

TikZ 中的形状可以由“子形状”组成吗?

假设有人要在 TikZ 中制作一个简单的“哑铃”形状。(我的实际示例更复杂,但我们坚持这个简单的示例)。也就是说,我们想要一个由两个圆和它们之间的线段构成的形状。

您希望通过 来定义它\pgfdeclareshape,然后在您的 TikZ 代码中,您希望能够从子形状“继承锚点”,即您希望能够说类似的话:

\node[dumbbell] (A) at (0,0) {};
\draw (A.left circle.north east) to (1,3);

据我所知,目前这是不可能的。或者有没有什么巧妙的方法可以做到这一点?关键是你不想手动编码每个圆上的所有这些锚点。即使在这个例子中这样做是可以的,想象一下如果你的形状很复杂,包含几十个圆、椭圆、梯形等……你真的希望能够构建一个由“子形状”组成的形状,然后为锚点建立一个“目录结构”。

其中一种做法是,用dumbbell一个来\savedmacro实际构建另外两个圆类型的节点,如下所示:

\pgfdeclareshape{dumbbell}
...
\savedmacro{}{\pgfnode{circle}{my line.left anchor}{}{[?name of dumbbell node?].left circle}{.code to create circle..}}

问题是当我们创建哑铃节点的子代圆时,我们需要知道它的名称因为我们想在实践中实际使用它,就像上面那样,即

\node[dumbbell] (A) at (0,0) {};
\draw (A.left circle.north east) to (1,3);

这可能吗?您可以让 pgf 给您一个节点的名称吗?

更新:2011 年 5 月 17 日。我尝试了 Martin 的第二个建议,即使用密钥\tikz@fig@name访问\pgfdeclareshape环境内部节点的名称,然后\pgfnode根据此名称在 中创建一个新节点\backgroundpath。我无法让它工作,我收到错误No shape named ... is known。也许我创建的新节点在某种程度上只是\backgroundpath创建它的环境的本地节点?

答案1

答案是“可以”,但“不应该”。 经过一些实验表明,节点名称在形状定义中可用,因此可以分层定义形状。例如,下面是哑铃。

\makeatletter
\pgfdeclareshape{dumbbell}{
  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
  \backgroundpath{
    \edef\@temp{%
      \noexpand\node[circle,draw] (\tikz@fig@name-left) at (-1,0) {};
      \noexpand\node[circle,draw] (\tikz@fig@name-right) at (1,0) {};
      \noexpand\draw (\tikz@fig@name-left) -- (\tikz@fig@name-right);
}
    \@temp
}
}

因此\node[dumbbell,draw] (a) {};将绘制哑铃并将其命名为a。组件可以通过a-left和进行访问a-right。例如:

\begin{tikzpicture}
\node[dumbbell] (a) at (2,0) {};
\draw (a-left) to[out=60,in=120] (a-right);
\end{tikzpicture}

会产生

哑铃

但是这段代码有很多问题!首先,我在里面使用了“高级”(即纯 TikZ)命令,\pgfdeclareshape这并不好。其次,在后台路径中构建的路径并非全部都是一条路径。即使我们将所有内容转换为低级 PGF 命令,我们仍然不会拥有一条路径,而是多条路径:每个节点组件一条路径,它们之间的位一条路径(要看到这一点,请注意,当在路径上构建节点时,可以将节点边界的颜色与主路径不同。这只有在单独的路径中才有可能)。这使得节点路径的样式设计成为一场绝对的噩梦。您必须确保样式得到正确继承,还必须防止无限递归。

一个更强大的方法是做所有实际的绘画在主形状中,然后根据需要添加额外的节点,纯粹是为了锚点。我脑海中浮现的一个例子是:

\pgfdeclareshape{dumbbell}{

  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
  \backgroundpath{
    \edef\@temp{%
      \noexpand\node[circle,draw=none,fill=none,minimum size=2cm](\tikz@fig@name-left) at (-2,0) {};
      \noexpand\node[circle,draw=none,fill=none,minimum size=2cm](\tikz@fig@name-right) at (2,0) {};
    }
    \@temp
    \pgfpathcircle{\pgfqpoint{-2cm}{0cm}}{1cm}
    \pgfpathcircle{\pgfqpoint{2cm}{0cm}}{1cm}
    \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
    \pgfpathlineto{\pgfqpoint{1cm}{0cm}}
  }
}

(再次强调,高级命令应该转换为低级命令。)请注意,我明确关闭了额外形状上的任何渲染命令。实际上,我更愿意绘制额外的节点外部背景路径只是为了将锚节点与主形状分开。所以我认为我的实际代码有点像下面这样:

\documentclass{standalone}
\usepackage{tikz}

\makeatletter
\pgfdeclareshape{dumbbell}{

  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
  \backgroundpath{
    \pgfpathcircle{\pgfqpoint{-2cm}{0cm}}{1cm}
    \pgfpathcircle{\pgfqpoint{2cm}{0cm}}{1cm}
    \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
    \pgfpathlineto{\pgfqpoint{1cm}{0cm}}
  }
  \beforebackgroundpath{
    \tikzset{minimum size=2cm}
    {
      \pgftransformxshift{-2cm}
      \pgfnode{circle}{center}{}{\tikz@fig@name-left}{}
    }
    {
      \pgftransformxshift{2cm}
      \pgfnode{circle}{center}{}{\tikz@fig@name-right}{}
    }
  }
}

\makeatother
\begin{document}
\begin{tikzpicture}
\node[dumbbell,draw,line width=2pt,red,fill=orange] (a) at (2,0) {};
\draw (a-left) to[out=60,in=120] (a-right);
\node[dumbbell,red,draw] (b) at (2,-3) {};
\draw (b-left) to[out=60,in=-120] (a-right);
\end{tikzpicture}
\end{document}

哑铃标记 3

请注意,我现在正在为节点使用正确的低级命令。这意味着,例如,它们不受任何 TikZ 选项的影响。您可以通过draw呼叫命令并every node/.style={draw}在内部添加\tikzset形状定义。额外的节点仍然没有绘制。

事实上,这看起来真的是一个巧妙的解决方案,我(老实说!)正在思考使用 TQFT 包时受到了以下启发:使用 pstricks 或 tikz 绘制的拓扑量子场论图,即如何向边界组件添加合理的锚点。很高兴您问了这个问题!

答案2

更新:

刚刚发现这一点pgfmoduleshapes.code.tex:节点名称似乎是可访问的,因为\pgfreferencednodename 它正式仅适用于通用锚点定义为\pgfdeclaregenericanchor,但似乎也可用于普通锚点。


类似定义的节点的名称\node[dumbbell] (A) at (0,0) {};显然存储在中\tikz@fig@name,您应该能够使用以下命令将其保存在\pgfdeclareshape

\savedmacro{\nodename}{%
    \let\nodename\tikz@fig@name
}

但是您可能需要在形状的某些路径命令中创建子节点,例如在\backgroundpath\foregroundpath。在那里您可以简单地使用主节点的锚点来定位子节点。据我所知,PGF 在处理节点锚点时也会移动坐标系,以便节点的中心/原点位于(0,0)。我认为您不需要知道主节点的名称。

答案3

pics(TiKZ 3.0 - 手册部分“18. 图片:路径上的小图片”)机制允许组成复杂的图形,将它们重复为单个元素并引用内部节点锚点。

一个可以模仿的小例子循环空间结果

\documentclass[tikz,border=2mm]{standalone}

\begin{document}

\begin{tikzpicture}[%
    dumbbell/.pic={%
        \node[circle, draw, minimum size=1cm, fill] (-right) at (1,0) {};
        \node[circle, draw, minimum size=1cm, fill] (-left) at (-1,0) {};
        \draw (-right)--(-left);
    }]

\draw[fill=white] pic (A) at (0,0) {dumbbell};

\draw[line width=2pt, red, fill=orange] pic (B) at (0,1.5) {dumbbell};

\draw (B-left) to[out=60,in=120] (B-right);
\draw (A-left) to[out=60,in=-120] (B-right);

\end{tikzpicture}

\end{document}

在此处输入图片描述

其他一些使用图片(优点和缺点)的例子有:

相关内容