在 tikz 中的矩形节点自定义形状的锚点处添加额外的节点?

在 tikz 中的矩形节点自定义形状的锚点处添加额外的节点?

我想要创建一个 SPDT 开关符号节点,它看起来像这样:

spdt-原始文件.png

因此,我想,我可以先从矩形节点派生出自定义形状,其大小将由 设定minimum width/height;然后我可以在south westnorth west和处添加小圆形节点,并从到节点east绘制带箭头的连接线。我希望添加的节点是可寻址的(这样我以后可以将它们用作连接线);我还希望有一个参数来控制小圆形节点的大小。eastnorth west

我尝试阅读:

...因此我尝试编造一个例子,但是我在这一点上遇到了困难:

测试31.png

和矩形边框X只是为了调试而添加的;它开始正常(我可以绘制一个圆形路径,也可以在同一位置添加一个圆形节点south west),但问题是 - 我似乎无法正确计算“小”节点的主节点的高度north west

解决这个问题的正确方法是什么?另外,下面的 MWE 可以用于above=of定位,但right=of定位时会崩溃 - 有什么方法可以解决这个问题吗?

以下是我目前的 MWE:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}

\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}

% https://tex.stackexchange.com/questions/18400/can-a-shape-be-composed-out-of-subshapes-in-tikz
% https://tex.stackexchange.com/questions/65469/how-to-draw-saturation-symbol-inside-a-node-in-tikz
% https://tex.stackexchange.com/questions/73877/pgfdeclareshape-with-variable-dimensions-using-pgfkeys
\makeatletter
\pgfkeys{/tikz/spdtcircsize/.initial = 0.15cm}
\pgfdeclareshape{spdt}{
  %\savedanchor\centerpoint{
  %  \pgf@x = .5\wd\pgfnodeparttextbox
  %  \pgf@y = .5\ht\pgfnodeparttextbox
  %}
  %\anchor{center}{\centerpoint}
  \inheritsavedanchors[from={rectangle}]
  \inheritbackgroundpath[from={rectangle}]
  \inheritanchorborder[from={rectangle}]
  \foreach \x in {center,north east,north west,north,south,south east,south west}{
    \inheritanchor[from={rectangle}]{\x}
  }
  \saveddimen\circsize{\pgf@x=\pgfkeysvalueof{/tikz/spdtcircsize}}
%   \savedanchor{\center}{%
%     \pgfpointorigin}
%   \anchor{center}{\center}
%%
%   \backgroundpath{
  \foregroundpath{
%     \centerpoint
%     \pgfkeys{/pgf/minimum size = \circsize}
%     \pgfset{inner sep=1pt}
%%
    \southwest
    \pgfpathcircle{\southwest}{\circsize}
%     \pgfusepath{draw}
    {
    \pgfkeys{/pgf/minimum size = \circsize}
    \pgftransformshift{\southwest}
    \pgfnode{circle}{center}{}{\tikz@fig@name-c1}{\pgfusepath{draw}}
    }
    %\pgfpathmoveto{\pgfpoint{0}{-\pgf@ya}}
    \pgfpointdiff{\northeast}{\southwest}
    \pgf@xa=\pgf@x \pgf@ya=\pgf@y
%     \pgfpathmoveto{\pgfpointadd{\southwest}{\pgfpoint{0}{-1.0\pgf@ya}}}
    \pgfpathcircle{\pgfpointadd{\southwest}{\pgfpoint{0}{-1.0\pgf@ya}}}{\circsize}
%     \pgfnode{circle}{center}{}{\tikz@fig@name-c2}{}
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[draw] (n1) at (1,0) {Testing};
% right= of causes ! Package PGF Math Error: Unknown function `west' (in 'west').
\node[spdt,draw,minimum width=20pt,minimum height=20pt] (n2) [above=10pt of n1] {X};
\end{tikzpicture}
\end{document}

答案1

最终解决方案:我将圆圈移到边界内,创建了新的锚点并绘制了开关。所有这些 \pgfscope 都是必需的。

转变

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
\usetikzlibrary{arrows.meta}

\newlength{\circsize}
\newlength{\spdtright}
\newlength{\spdtup}
\newlength{\spdtleft}
\newlength{\spdtdown}
\newlength{\spdtlen}

\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}

\pgfkeys{/tikz/spdtcircsize/.initial = 0.08cm}
\pgfdeclareshape{spdt}{
  \inheritsavedanchors[from={rectangle}]
  %\inheritbackgroundpath[from={rectangle}]
  \inheritanchorborder[from={rectangle}]
  \foreach \x in {center,text,north east,north west,north,south,south east,south west,east,west}{
    \inheritanchor[from={rectangle}]{\x}
  }
\anchor{out}{
  \pgfextractx{\spdtright}{\northeast}%
  \pgfextracty{\spdtup}{\northeast}%
  \pgfextracty{\spdtdown}{\southwest}%
  \pgfpoint{\spdtright}{0.5\spdtup+0.5\spdtdown}
 }
\anchor{off}{
  \circsize=\pgfkeysvalueof{/tikz/spdtcircsize}%
  \pgfextractx{\spdtleft}{\southwest}%
    \pgfextracty{\spdtdown}{\southwest}%
    \pgfpoint{\spdtleft}{\spdtdown+\circsize}%
}
\anchor{on}{
  \circsize=\pgfkeysvalueof{/tikz/spdtcircsize}%
  \pgfextracty{\spdtup}{\northeast}%
  \pgfextractx{\spdtleft}{\southwest}%
   \pgfpoint{\spdtleft}{\spdtup-\circsize}%
}
\foregroundpath{
  \circsize=\pgfkeysvalueof{/tikz/spdtcircsize}%
  \pgfextractx{\spdtright}{\northeast}%
  \pgfextracty{\spdtup}{\northeast}%
  \pgfextractx{\spdtleft}{\southwest}%
  \pgfextracty{\spdtdown}{\southwest}%
  % compute sin and cos for sloped line
  \pgfscope
    \spdtlen=0.5\spdtup-0.5\spdtdown-\circsize% length
    \pgfmathmultiply{\spdtlen}{\spdtlen}%
    \let\spdtcos=\pgfmathresult% macro
    \spdtlen=\spdtright-\spdtleft-2\circsize%
    \pgfmathparse{sqrt(\spdtlen*\spdtlen+\spdtcos)}
    \let\spdtsin=\pgfmathresult%
    \pgfmathdivide{\spdtlen}{\spdtsin}%
    \global\let\spdtcos=\pgfmathresult%
    \spdtlen=0.5\spdtup-0.5\spdtdown-\circsize%
    \pgfmathdivide{\spdtlen}{\spdtsin}%
    \global\let\spdtsin=\pgfmathresult%
  \endpgfscope
  % draw circles
  \pgfscope
    \pgfpathcircle{\pgfpoint{\spdtleft+\circsize}{\spdtdown+\circsize}}{\circsize}%
    \pgfpathcircle{\pgfpoint{\spdtleft+\circsize}{\spdtup-\circsize}}{\circsize}%
    \pgfpathcircle{\pgfpoint{\spdtright-\circsize}{0.5\spdtup+0.5\spdtdown}}{\circsize}%
    \pgfusepath{stroke}
  \endpgfscope
  % draw arrow
  \pgfscope
    \pgfsetarrowsend{Triangle[open]}%
    \pgfpathmoveto{\pgfpoint{\spdtright-\circsize-\spdtcos\circsize}{0.5\spdtup+0.5\spdtdown+\spdtsin\circsize}}%
    \pgfpathlineto{\pgfpoint{\spdtleft+\circsize+\spdtcos\circsize}{\spdtup-\circsize-\spdtsin\circsize}}%
    \pgfusepath{stroke}%
    \endpgfscope
  }
}
\begin{document}
\begin{tikzpicture}
\node[draw] (n1) at (1,0) {Testing};
\node[spdt,draw,minimum width=20pt,minimum height=20pt] (n2) [above=10pt of n1] {};
\draw (n2.on) -- +(-0.2,0)
      (n2.off) -- +(-0.2,0)
      (n2.out) -- +(0.2,0);
\node[spdt,draw,minimum width=20pt,minimum height=20pt] (n3) [below=10pt of n1] {X};
\end{tikzpicture}
\end{document}

答案2

好的,我这里遇到了几个问题。

首先,我同时绘制了圆路径和圆节点,以确保我理解正确,这样我就可以根据需要使用其中任何一种。事实证明,必须小心\pgfdeclareshape 中圆形路径与圆形节点的大小- in\pgfpathcircle有一个半径参数,但\pgfnodeviaminumum size有一个直径参数。当然,在这种情况下,我只需要节点,因为我想在外部使用它们。

其次,我尝试计算主节点的高度和宽度(\pgfpointdiff{\northeast}{\southwest}),因为我不知道如何访问它的锚点;结果发现有一个命令,\pgfpointanchor-并且\tikz@fig@name可以与它一起用来引用主节点。

第三,我不太了解 PGF 引擎——我的理解是,如果你调用类似的命令\pgfpointanchor,那么你就会在内部寄存器中获得结果\pgf@x\pgf@y然后你可以将其分配给临时寄存器等\pgf@xa\pgf@ya因此,我认为这可以获取锚点坐标并绘制圆圈:

{
\pgftransformreset
\pgfpointanchor{\tikz@fig@name}{south west}
\xdef\mycoordinate{\noexpand\pgfpoint{\the\pgf@x}{\the\pgf@y}}
}
\pgfpathcircle{\mycoordinate}{\circsize}

... 但出于某种原因,它没有 -\mycoordinate结果错了。但是 - 这是奇怪的部分 - 如果我把整个放在\pgfpointanchor{\tikz@fig@name}{south west}\pgfpathcircle不是中\mycoordinate,那么定位就没问题了?!

因此,考虑到所有这些因素,我得出了下面发布的 MWE,其结果如下:

测试11.png

红色节点(n2)仅用于测试样式和位置;否则(n3)其右侧就是如何使用它。

但还有一个问题 - 我从某处注意到自定义形状(通过\pgfdeclareshape)应该只使用 PGF 基元,而不是 TikZ 绘图命令 - 但我必须使用\draw,这样我才能轻松指定箭头尖端,并且该线连接圆形节点的边界 - 而不是它们的中心。如果有人知道可以使用 PGF 命令实现相同解决方案的解决方案,请发布答案。

除此之外,看来这个 MWE 为我做了这件事:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
\usetikzlibrary{arrows}

\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}

\makeatletter
\pgfkeys{/tikz/.cd,
  mycircsize/.store in=\circsize,
  mycircsize=0.2cm
}
\pgfdeclareshape{spdt}{
  \inheritsavedanchors[from={rectangle}]
  \inheritbackgroundpath[from={rectangle}]
  \inheritanchorborder[from={rectangle}]
  \foreach \x in {center,north,north east,north west,south,south east,south west,east,west}{
    \inheritanchor[from={rectangle}]{\x}
  }
%   \backgroundpath{
  \foregroundpath{
    \pgfkeys{/pgf/minimum size = \circsize}
    \pgfset{inner sep=0pt,outer sep=0pt}
    % draw connector nodes
    \southwest
    \pgfpathcircle{\southwest}{0.5*\circsize}% just a test
    {
    \pgftransformshift{\southwest}
    \pgfnode{circle}{center}{}{\tikz@fig@name-c1}{\pgfusepath{draw}}
    %\pgfusepath{draw}
    }
    {
    \pgftransformshift{\pgfpointanchor{\tikz@fig@name}{north west}}
    \pgfnode{circle}{center}{}{\tikz@fig@name-c2}{\pgfusepath{draw}}
    }
    {
    \pgftransformshift{\pgfpointanchor{\tikz@fig@name}{east}}
    \pgfnode{circle}{center}{}{\tikz@fig@name-c3}{\pgfusepath{draw}}
    }
    % draw switch arrow line
    % with PGF primitives, line goes from center of nodes, not borders
    %\pgfpathmoveto{\pgfpointanchor{\tikz@fig@name}{east}}
    %\pgfpathlineto{\pgfpointanchor{\tikz@fig@name}{north west}}
    % so using a TikZ draw command:
    \draw[-open triangle 60] (\tikz@fig@name-c3) -- (\tikz@fig@name-c2);

  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[draw] (n1) at (1,0) {Testing};
\node[spdt,draw=red,line width=2pt,minimum width=20pt,minimum height=20pt] (n2) [above=10pt of n1] {X};
\node[spdt,minimum width=20pt,minimum height=20pt] (n3) [right=40pt of n2] {};

\draw (n1.west) -- ++(-15pt,0) |- (n2-c1);
\draw (n1.east) -- ++(15pt,0) |- (n2-c3);

\draw (n1.east) -- (n3-c3|-n1.east) -- ++(15pt,0) |- (n3-c3);

\end{tikzpicture}
\end{document}

相关内容