4

I've started using pic elements to avoid excessive copy-pasting, but I am likely doing something wrong when trying to place such elements using relative coordinates.

The vertical placement on the left works fine. The horizontal placement at the centre is off. The hacky horizontal placement on the right works fine. The red and blue elements are usually invisible and are shown here for debugging purposes.

\documentclass[tikz,border=6pt]{standalone}
\usetikzlibrary{graphs,arrows.meta,positioning}
\tikzset{
  neuron/.style={circle,very thick,minimum size=7.5mm,inner sep=0,outer sep=0,draw},
  contact/.style={neuron,node contents={},minimum size=2mm},
  sum/.style={neuron, minimum size=7.5mm/2, node contents={}},
  gate/.pic={
    \node (-g) [sum, draw=red, very thin];
    \node [sum, xshift=-2pt];
    \draw [thick] (-g.225) -- +(0,7.5mm/4*2^.5);
    \draw (-g.west) -- +(2pt,0);
    \draw ([yshift=.5pt] -g.south) -- +(135:7.5mm/4);
    \draw [{Latex[length=2pt]}-] ([yshift=-.5pt] -g.north) -- +(-135:7.5mm/4);
  },
  node distance=7.5mm,
}
\begin{document}
\begin{tikzpicture}

% Place nodes, vertical layout \pic (g) {gate}; \node (B) [contact, left=of g-g, label=(b)]; \node (E) [contact, above=of g-g, label=(e)]; \node (C) [contact, below=of g-g, label=below:(c)];

% Connect nodes \graph [use existing nodes]{ {B, C} -- (g-g) -> E; };

% Place nodes, horizontal layout \node (c1) [contact, right=of g-g, label=(c)]; \pic (f1) [transform shape, rotate=90, yscale=-1, right=of c1] {gate};

\node (c2) [contact, right=4*7.5mm of g-g, label=(c)]; \node (s2) [sum, blue, right=of c2]; \pic (f2) at (s2) [transform shape, rotate=90, yscale=-1] {gate};

\end{tikzpicture} \end{document}

enter image description here

Atcold
  • 1,727
  • By the way, have yo seen the circuitikz package? It has a bunch of symbols predefined. – Qrrbrbirlbel Nov 20 '23 at 16:40
  • «I can't really follow what the output should be.» The expected output is the one on the right, where the element is placed to the right of c2. Namely, I'd like to choose where the element f-g is placed, which is the -g circle inside the gate pic. Similarly, on the left, I place the contact element according to g-g. – Atcold Nov 20 '23 at 16:45
  • Yeah, I'm aware of the electronics package. I'm more interested in creating custom elements myself. So, here I was trying to figure out placement without hacks. – Atcold Nov 20 '23 at 16:47
  • 1
    So, the basic problem is that right=of sets anchor=west and then you rotate the node which rotates it around this anchor, basically. That's why it is higher than you desire. We need another interface for rotation. (And the yscale thing I'd solve by toggling the arrow specification.) – Qrrbrbirlbel Nov 20 '23 at 17:12
  • 1
    An alternative solution would be to use on grid (globally or at least for the gate pic). This will choose the center anchor both for placing the new node as well as the reference anchor. This will make it trivial creating and placing these gates but now the node distance is measured between the center of the nodes and not their borders. – Qrrbrbirlbel Nov 20 '23 at 20:55

1 Answers1

4

The root of your problem is that right=of sets the anchor=westand then you basically rotate the node around that anchor (because that's the anchor you place the node with at a specific position). I'll define anchor rotate that also sets rotate but also adjust the anchor to be rotated. The \tikz@polar@dir@… macros are used to reverse the anchor names like west and south east into values like 180 and 315.
(The macros are actually used for a polar coordinate like (south east:1) but oh well. The macro \tikz@on@text (which just expands to center) is also used to check whether the anchor is set to center to adjust the anchor.)

I've adjusted the gate pic a bit to not rely so much on hard-coded numbers regarding the size of the circle but instead use other means to find start or end of lines. Now, the hard-coded values are basically in relation to line width of the thick unnamed circle.

The point of outer sep=+.28pt is so that lines connecting to -g will stop just right before the thick circle. Similarly, the 1.3pt in the last two paths are used to get on the inner side of the thick circle. (This could also be solved by proper math but trigonometry is no fun.)

Code

\documentclass[tikz,border=6pt]{standalone}
\usetikzlibrary{graphs, arrows.meta, positioning}
\tikzset{
  neuron/.style ={% let's not set outer sep to zero
    shape=circle, very thick, minimum size=7.5mm, inner sep=+0pt, draw},
  contact/.style={
    neuron, minimum size=  2mm,   node contents=},
  sum/.style    ={
    neuron, minimum size=7.5mm/2, node contents=},
  node distance=7.5mm,
  % tip specification for gate pic:
  gate pic arrow/.tip={Latex[length=2pt]},
  pics/@gate/.style n args={3}{code={
    % place the auxiliary node with no path and guesstimated outer sep
    % but use anchor rotate which adjusts the given anchor
    % so that the node is rotated #1 around its center (and not the anchor)
    \node [name=-g, sum, path only, outer sep=+.28pt, anchor rotate={#1}];
    % shift the real circle in relation to #1
    \node at (-g.center) [anchor=center, sum, shift=(#1:-2pt)];
    % use the auxiliary node to draw the thick line → no calculation
    % shorten to offset outer sep of -g
    % an auxiliary coordinate is placed midway between them …
    \draw[shorten >=+.4pt, shorten <=+.4pt, thick]
      (-g.225) -- coordinate[midway] (@)(-g.135);
    % … which we use to connect the line from west → no calculation
    \draw (-g.west) -- (@);
    % line cap=round to hide imperfections
    % use #1 to specify shift for south/north
    %   [could have also used ($(-g.south)!1pt!(-g.center)$) or
    %                         ($(-g.north)!1pt!(-g.center)$)    ]
    % save that starting point and find intersection with thick line
    % by providing a direction → no calculation (yay)
    \draw[line cap=round, #2] ([shift=(#1+90:1.3pt)] -g.south) coordinate (@)
      -- (intersection of @--{[shift=(#1+135:1cm)]@} and -g.225---g.135);
    \draw[line cap=round, #3] ([shift=(#1-90:1.3pt)] -g.north) coordinate (@)
      -- (intersection of @--{[shift=(#1+225:1cm)]@} and -g.225---g.135);
  }},
  % user-interface: gate = <rotation> and reversed gate' = <rotation>
  pics/gate/.style ={@gate={#1}{               }{gate pic arrow-}},
  pics/gate'/.style={@gate={#1}{gate pic arrow-}{               }},
}
\makeatletter
\tikzset{
  anchor rotate/.code=% will fail with anchor set to base/mid or similar
    \tikzset{rotate={#1}}%
    \ifx\tikz@anchor\tikz@on@text % no change when set anchor is center
    \else
      \pgfutil@IfUndefined{tikz@polar@dir@\tikz@anchor}
        {\pgfmathsetmacro\tikz@anchor{\tikz@anchor-#1}}
        {\pgfmathsetmacro\tikz@anchor{%
          \csname tikz@polar@dir@\tikz@anchor\endcsname-#1}}%
    \fi}
\makeatother
\begin{document}
\begin{tikzpicture}
% Place nodes, vertical layout
\pic  (g) {gate=0};
\node (B) [contact, left=of g-g, label=\(b\)];
\node (E) [contact, above=of g-g, label=\(e\)];
\node (C) [contact, below=of g-g, label=below:\(c\)];

% Connect nodes \graph [use existing nodes]{ {B, C} -- (g-g) -> E; };

% Place nodes, horizontal layout \node (c1) [contact, right=of g-g, label=(c)]; \pic (f1) [right=of c1] {gate'=90};

\draw[red, <->] (c1) -- (f1-g); \end{tikzpicture} \end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821
  • Of course, this assumes that the center anchor is actually in the center and the reverse function from anchor to angle is correct (which it isn't for a rectangle node), for a simple circle this will be the case though. – Qrrbrbirlbel Nov 20 '23 at 19:03
  • Wow, I'm impressed. I'm still trying to understand what you wrote. 1. How did you know the pic was rotated on its anchor? 2. Why non-zero outer sep (I use it extensively throughout my book so that elements are placed like on a grid without keeping in consideration the border thickness)? 3. What's the @gate and @? 4. anchor rotate sets rotate before playing with the anchors, so I'm not sure how it works… – Atcold Nov 20 '23 at 22:51
  • I did a test rotating my gate by several angles and it turns out that the rotation anchor is set to be -g.west and the following unnamed node has zero influence (no idea why). Maybe because its xshift=-2pt is somehow ignored. – Atcold Nov 20 '23 at 23:02
  • 1
    Not the pic is rotated, its content is. Not the pic is placed but its nodes are. The pic is just an interface to define a prefix here. If you specify rotate=90, anchor=west, the node will be rotated and placed with its west anchor on the at part. right=of … sets anchor=west, at=(….east), xshift=<node distance>. Just \node[draw] (O) at (0,0) {O}; \node[draw, right=of O, rotate=90] {Foobar};. (It might be easier to see this with rectangular nodes.) @gate is the “real” pic where the first argument is the rotation, and the other are options for the last two paths. – Qrrbrbirlbel Nov 20 '23 at 23:15
  • 1
    But you would want to use gate and gate' which sets these two arguments so that you don't have to yscale=-1 anything. The following node also inherits right=of …. I overwrite this with using an explicit at, an explicit anchor and also shifting it by 2pt but dependent on the #1 rotation argument. The node has no name and will not be referenced later, so of course it has no influence. (I rather would connect the two last paths to the inside of this node but that will involve trigonometry or the intersections library to do it proper.) – Qrrbrbirlbel Nov 20 '23 at 23:18
  • 1
    @Atcold You usually want an outer sep (usually of .5\pgflinewidth which is the default) so that lines connecting to these nodes just touch the border and not protrude it. This is certainly true for the contacts. The invisible node is a special case here but since it is invisible we can use an outer sep that makes it so that the other lines look good in relation to the thick circle. The @ coordinates are auxiliary coordinates (there are three different onces), no special reason for its name. – Qrrbrbirlbel Nov 20 '23 at 23:24
  • Aaaaaah! So, (-g.225) -- coordinate[midway] (@)(-g.135) is where @ is defined! LOL, I was struggling to make sense, haha! If we do not set outer sep=0, then I need node distance=7.5mm-.5\pgflinewidth? Alternatively, one could make the connecting lines shorter by .5\pgflinewidth. The syntax (intersection of A--B and C--D) doesn't seem to be documented on the manual. At he beginning you say «place the auxiliary node with no path» and later path only. What does this mean? Also, how does \pgfmathsetmacro\tikz@anchor{\tikz@anchor-#1} rotate anything? I'm confused. – Atcold Nov 21 '23 at 15:34
  • @Atcold Well, sure. The node distance always measures between borders of nodes (unless on grid is used, see above) so outer sep does have an effect on the placement. There's a lot of different things one could do. As complex as TikZ is as plentiful are possible solutions to one task. Yes, the intersection of syntax is, unfortunately, undocumented. The path only option disables all possible uses (draw, fill, …) for a path. – Qrrbrbirlbel Nov 21 '23 at 17:33
  • Yes, you could leave it out here but in case someone messes with the styles sum or even node, path only will make sure that node is not visible. The \pgfmathsetmacro doesn't rotate the node, it rotates the used anchor in the opposite direction so that circle seems to be rotated around the center. Maybe I'll add an animation to the answer so that it is clearer … – Qrrbrbirlbel Nov 21 '23 at 17:35