3

I want to make a node which will be a SPDT switch symbol, which looks like this:

spdt-orig.png

So, I thought, I could start by deriving a custom shape from a rectangular node, whose size will be set by minimum width/height; then I could add small circular nodes at south west, north west and east, and draw a connector line with arrow from the eastto the north west node. I would like the added nodes to be addressable (so I can use them later for connector lines); and I would also like to have a parameter for controlling the small circular nodes' size.

I tried reading:

... and so I tried to cook up an example, but I get stuck at this point:

test31.png

The X and the rectangular border are added just for debug; it starts fine (I can draw a circle path, and also add a circular node at the same position, at south west), but then the problem is - it seems I cannot calculate the height of the master node properly for the north west "small" node.

What would be the right way to solve this? Also, the MWE below works for above=of positioning, but crashes for right=of positioning - any way to fix that?

Here is my MWE so far:

\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
    I think you are almost there. Add east,west anchors to the foreach. – percusse Apr 22 '14 at 22:51
  • Many thanks for the comment @percusse, very nicely spotted (that's what happens when I copypaste blindly :)). That fixes the right=of issue, but I still don't know how to use the height provided by \pgfpointdiff properly... Cheers! – sdaau Apr 22 '14 at 22:56
  • 1
    First question: Do you want to include text inside the node? If not, you can use a fixed size instead of messing with \pgfnodepartextbox. – John Kormylo Apr 24 '14 at 1:15
  • Thanks for that, @JohnKormylo - I would like text more-less for debugging purposes (and maybe to set size), but those \pgfnodepartextbox are simply artefacts of copypasting of snippets... Cheers! – sdaau Apr 24 '14 at 4:16
  • 1
    It was a stupid question. If you are going to inherit the anchors from rectangle, you will want the switch to match. I'm just more used to doing ciruitikz components, which have a fixed size. – John Kormylo Apr 24 '14 at 13:11
2

Final solution: I moved the circles inside the boundary, created new anchors and drew the switch. All those \pgfscope's really are needed.

switch

\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}
  • Many thanks @JohnKormylo - that is exactly where I want the circles to go; it is \pgfextractx that I was missing... In the meantime, I also arrived at a solution, will be posting it here soon.. Cheers! – sdaau Apr 25 '14 at 5:36
  • @sdaau - I would have placed the circles inside the boundary and defined new anchors (on, off, and out) at the contact points. – John Kormylo Apr 25 '14 at 14:42
1

Ok, there were several problems I had here.

First of all, I was both drawing circle paths, and circle nodes, to make sure I understand them correctly, so I could use either if need be. Turns out, one has to be careful about the Size of circle path vs. circle node in \pgfdeclareshape - in that \pgfpathcircle has a radius argument, but \pgfnode via minumum size has a diameter argument. Of course, in this case, I only need nodes, as I want to utilize them externally.

Second, I tried calculating height and width of the master node (\pgfpointdiff{\northeast}{\southwest}), because I didn't know how to access its anchors; turns out there is a command for it, \pgfpointanchor - and \tikz@fig@name can be used with it to refer to the master node.

Third, I don't really understand the PGF engine - the way I understood, if you call commands like \pgfpointanchor, then you get the results in internal registers \pgf@x, \pgf@y, which you can then assign to temporary registers \pgf@xa, \pgf@ya etc. Thus, I thought this would work in obtaining an anchor points coordinates and drawing a circle:

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

... but for some reason it doesn't - \mycoordinate turns out wrong. BUT - and this is the weird part - if I stick the entire \pgfpointanchor{\tikz@fig@name}{south west} in \pgfpathcircle instead of \mycoordinate, then the positioning is fine ?!

So, taking all this into account, I arrived at the MWE posted below, which results with this:

test11.png

The red node (n2) is just to test the styling and positions; otherwise (n3) to its right is how it would be used.

But there is one more issue - I've noted from somewhere that custom shapes (via \pgfdeclareshape) should use only PGF primitives, not TikZ drawing commands - but I have to use \draw, so I can easily specify an arrow tip, and so that the line connects the borders of the circular nodes - not their centers. If anyone knows of a solution where the same can be achieved with PGF commands, please post an answer.

Other than that, it seems that this MWE does it for me:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.