4

There is an update, see below

I'm designing mosfet symbols, which should be able to be flipped. The important thing about flipping is that the relative positioning with the positioning library should stay the same, e.g. placing a mirrored node right of a mirrored node should keep the same distance as a 'normal' node placed right of a 'normal' node. For this, i thought about using the .is if handler and replace the anchors and the background path accordingly. While trying this with the original code, i could change the path, but not the anchors. While creating a MWE for a question here i did something wrong, so now the .is if handler does nothing. Now i'm very confused. Can somebody point me in the right direction?

Note: i know of the xscale=-1 solution for mirroring nodes, but this requires a manual overwrite of the involved anchors of the nodes. I want to avoid this, and i'm open for all kinds of (elegant/easy-to-use) solutions.

Here is the MWE:

\documentclass[parskip=half]{scrartcl}

\pagestyle{empty}

\usepackage{tikz}
\usetikzlibrary{positioning}

\newif\ifmirrorshape
\pgfkeys{
    /tikz/mirror/.is if=mirrorshape
}

\pgfdeclareshape{testshape}
{
    \ifmirrorshape
        \savedanchor{\center}{
            \pgfpointorigin
        }
        \savedanchor{\left}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{0cm}}
        }
        \savedanchor{\upperright}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{0.5cm}}
        }
        \savedanchor{\lowerright}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{-0.5cm}}
        }
        \anchor{center}{\center}
        \anchor{north}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{0.25cm}}}
        \anchor{south}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{-0.25cm}}}
        \anchor{west}{\left}
        \anchor{east}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{0cm}}}
    \else
        \savedanchor{\center}{
            \pgfpointorigin
        }
        \savedanchor{\left}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{0cm}}
        }
        \savedanchor{\upperright}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{0.5cm}}
        }
        \savedanchor{\lowerright}{
            \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{-0.5cm}}
        }
        \anchor{center}{\center}
        \anchor{north}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{0.25cm}}}
        \anchor{south}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{-0.25cm}}}
        \anchor{west}{\left}
        \anchor{east}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{0cm}}}
    \fi
    \backgroundpath{
        \pgfpathmoveto{\left}
        \pgfpathlineto{\upperright}
        \pgfpathlineto{\lowerright}
        \pgfpathclose
        \pgfusepath{stroke}
    }
}

\begin{document}
\begin{tikzpicture}[every node/.style={align=center}]
    \node[testshape] (s1) at(0, 0) { };
    \node[red, testshape, right=of s1, mirror] { };
\end{tikzpicture}
\end{document}

enter image description here

Update:

I've come further with @cfr's answer, but its still not quite right. What i need is to place (outside visible, i don't care about the internals) anchors depending on the 'mirror state'.

Here is an example:

\newif\ifmirrorshape
\pgfkeys{
    /tikz/mirror/.is if=mirrorshape
}

\pgfdeclareshape{testshape}
{
    \savedanchor{\center}{
        \pgfpointorigin
    }
    \savedanchor{\left}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{0cm}}
    }
    \savedanchor{\right}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{0cm}}
    }
    \savedanchor{\upperright}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{0.5cm}}
    }
    \savedanchor{\lowerright}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{0.5cm}{-0.5cm}}
    }
    \savedanchor{\upperleft}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{0.5cm}}
    }
    \savedanchor{\lowerleft}{
        \pgfpointadd{\pgfpointorigin}{\pgfpoint{-0.5cm}{-0.5cm}}
    }
    \anchor{center}{\center}
    \anchor{north}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{0.25cm}}}
    \anchor{south}{\pgfpointadd{\pgfpointorigin}{\pgfpoint{0cm}{-0.25cm}}}
    \anchor{right}{\right}
    \anchor{left}{\left}
    \anchor{west}{\left}
    \anchor{east}{\right}
    \backgroundpath{
        \ifmosfetmirror
            \pgfpathmoveto{\right}
            \pgfpathlineto{\upperleft}
            \pgfpathlineto{\lowerleft}
        \else
            \pgfpathmoveto{\left}
            \pgfpathlineto{\upperright}
            \pgfpathlineto{\lowerright}
        \fi
        \pgfpathclose
        \pgfusepath{stroke}
    }
}

Testcase:

\begin{tikzpicture}[every node/.style={align=center}]
    \node[testshape] (s1) { };
    \node[testshape, mirror, right=of s1] (s2) { };
\end{tikzpicture}

I've tried to place the \savedanchor's differently (doesn't work):

\ifmirrorshape
    \savedanchor...
\else
    \savedanchor...
\fi

I've tried to change anchor within the \deferredanchor (doesn't work):

\deferredanchor{...}{\ifmirrorshape ... \else ... \fi}

I have no idea how to solve this, any help is appreciated.

  • The example I gave in my answer already uses right=of s1 successfully to place s2. So I'm not really sure what the problem is or what you expect right=of to do, if not what it does with my code. – cfr Aug 22 '16 at 0:11
  • Do you want east to sometimes be 5mm to the right of the node and west sometimes 5mm to the left? Because that is what you've told TikZ to give you, I think. You shouldn't stroke the background path. See the TikZ manual for details of what to do if you really want to always draw something. – cfr Aug 22 '16 at 0:23
  • I thought i needed that, but the solution you showed me is better. The anchors should not move. However, i need some anchors to switch their position. Regarding the stroking of the path: why shouldn't you stroke the \backgroundpath? I know the manual says you shouldn't and if you wanted to do that, you should use \beforebackgroundpath. There reason for this suggestion is unclear, but i think it is to keep a maximum flexibility for the user, how might want to use the node for text and doesn't want to see the path. However, there is no situation where i don't want the path to be seen. – pschulz Aug 22 '16 at 5:51
4

I think you are trying to use methods which are not legitimate at the time you are using them. In particular, you need to distinguish code which is executed when the shape is defined from code which is executed when the shape is called.

Note that for positioning to work as expected, I think you want the west and east anchors to be placed west and east rather than flipped when the node is mirrored.

The key thing for saving mirroring-sensitive anchors is just to think very carefully about which code is being executed when.

\documentclass[border=10pt,multi,tikz]{standalone}
\usetikzlibrary{positioning}

\newif\ifmirrorshape
\tikzset{
  mirror me/.is if=mirrorshape
}
\makeatletter
\newdimen\pgf@testxb
\newdimen\pgf@testxa
\pgfdeclareshape{testshape}
{
  \savedanchor{\center}{%
    \pgfpointorigin
  }%
  \anchor{center}{\center}
  \anchor{north}{%
      \pgfpointorigin
      \pgf@x=0pt
      \pgf@y=2.5mm
  }%
  \anchor{south}{%
      \pgfpointorigin
      \pgf@x=0pt
      \pgf@y=-2.5mm
  }%
  \anchor{west}{%
    \pgfpointorigin
    \pgf@x=-5mm
    \pgf@y=0pt
  }%
  \anchor{east}{%
    \pgfpointorigin
    \pgf@x=5mm
    \pgf@y=0pt
  }%
  \savedanchor{\apex}{%
    \pgfpointorigin
    \ifmirrorshape
      \pgf@x=5mm
    \else
      \pgf@x=-5mm
    \fi
    \pgf@y=0pt
  }%
  \savedanchor{\base}{%
    \pgfpointorigin
    \ifmirrorshape
      \pgf@x=-5mm
    \else
      \pgf@x=5mm
    \fi
    \pgf@y=0pt
  }%
  \savedanchor{\upbase}{%
    \pgfpointorigin
    \ifmirrorshape
      \pgf@x=-5mm
    \else
      \pgf@x=5mm
    \fi
    \pgf@y=5mm
  }%
  \savedanchor{\downbase}{%
    \pgfpointorigin
    \ifmirrorshape
      \pgf@x=-5mm
    \else
      \pgf@x=5mm
    \fi
    \pgf@y=-5mm
  }%
  \deferredanchor{apex}{%
    \apex
  }%
  \deferredanchor{base}{%
    \base
  }%
  \deferredanchor{upper base}{%
    \upbase
  }%
  \deferredanchor{lower base}{%
    \downbase
  }%
  \backgroundpath{%
    \ifmirrorshape
      \pgf@testxa=5mm
      \pgf@testxb=-5mm
    \else
      \pgf@testxa=-5mm
      \pgf@testxb=5mm
    \fi
    \pgfpathmoveto{\pgfpoint{\pgf@testxa}{0pt}}%
    \pgfpathlineto{\pgfpoint{\pgf@testxb}{5mm}}%
    \pgfpathlineto{\pgfpoint{\pgf@testxb}{-5mm}}%
    \pgfpathclose
  }%
}
\makeatother
\begin{document}
\begin{tikzpicture}[every node/.style={align=center}]
  \node [testshape, draw, minimum size=15mm] (s1) at (0,0) { };
  \node (s2) [red, mirror me, testshape, draw, right=15mm of s1, minimum size=15mm] { };
  \foreach \i/\k/\l in {center/below/gray,north/above/gray,south/below/gray,west/left/gray,east/right/gray,apex/above/blue!50!cyan,base/above/magenta,upper base/above/orange,lower base/below/green!50!black}
  \foreach \j in {1,2}
  \path [fill, fill opacity=.5, text opacity=1, draw opacity=1, \l] (s\j.\i) circle (1pt) node [\k, font=\tiny, \l] {\i};
\end{tikzpicture}
\end{document}

mirrored node

\backgroundpath should not draw or fill anything. If the path should always be drawn, \beforebackgroundpath should be used. Although the manual does not say why, it cannot be a question of flexibility: drawing the path in \beforebackgroundpath is just as inflexible as drawing it in \backgroundpath would be. I suspect there are cases in which drawing in \backgroundpath will do the wrong thing because drawing is done at a different point in the construction of the node. So stroking this path is likely to lead to nasty surprises at some point, unless your TikZ code never mixes certain kinds of techniques with your custom nodes. These techniques do not need to be very exotic. Neither of the following work correctly if you stroke the node as part of \backgroundpath:

  • fill=yellow
  • drop shadow (wouldn't want to use this without filling anyway, of course)

and surely more exotic things are likely to fail as well.

  • Ah, i see, the key is to use \deferredanchor. This helps a lot, thank you. – pschulz Aug 20 '16 at 10:08
  • No wait, thats not right. It seems right, but it isn't. The actual savedanchors are not changing. I think, the if gets only evaluated during declaring the shape. I also tried putting the if inside the deferredanchor declaration, but that didn't help. I'm lost here, i've updated my question. – pschulz Aug 21 '16 at 23:21
  • @pschulz I guess I don't understand what you're trying to do. right=of s1 works fine for me, as shown in the example I posted. – cfr Aug 22 '16 at 0:06
  • Yes, you are right, the placement of the node is correct. But what i need is an anchor, which changes it position depending on the state of the mirrorring (not east or west, you are right that they shouldn't move). – pschulz Aug 22 '16 at 5:47
  • @pschulz Please see edit. Mirroring-sensitive anchors seem to work OK if you need them. You just need to think about the timing of code execution, I think. But don't stroke in \backgroundpath. It breaks stuff in weird ways. It is much easier not to set yourself up for unexpected unpleasantness even if you are sure you will never trigger the nasty surprises. (At least, it is certainly easier when it is easily avoided, as here.) – cfr Aug 22 '16 at 22:20

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.