4

I tried to use a foreach loop to define a new style. However, the code within the loop seems to have no effect. The following MWE ilustrates my problem:

\documentclass[tikz]{standalone}

\tikzset{
    mystyle/.code=
    {
        %\tikzset{very thick}, % This line has an effect (if uncommented)
        \foreach \pos in {0.1, 0.2, 0.3, 0.4} % **EDIT**: Should be a list of names in the end.
        {
            % There will be other statements here that actually use the list members

            \tikzset{very thick}, %This line is an exact copy of the comented line but has no effect.
            \message{======== Foreach: pos=\pos}
        }
    }
}

\begin{document}
\begin{tikzpicture}
    \draw [help lines] (-10,-10) grid (10,10);
    \draw[mystyle] (0,0) --++ (5,0);
\end{tikzpicture}
\end{document}

When I compile this document, the console shows the messages

... ======== Foreach: pos=0.1 ======== Foreach: pos=0.2 ======== Foreach: pos=0.3 ======== Foreach: pos=0.4 [1] (./mwe.aux)) ...

so I believe the \tikzset is actually run. There are no error messages.

So, why does the \tikzset command not make my line thick? And how can I get this to work?

EDIT: Context: This is (of course) part of a bigger thing. In the end, I want to create transistor stick diagrams like shown here: http://www.southampton.ac.uk/~bim/notes/cad/guides/sticks.html (Summary: Chip-level transistor layouts are sketched using mere lines of different colours. Where a line with the "Polysilicon" colour crosses a line with the "N Diffusion" oder "P Diffusion" colour, there is a transistor gate. For this I first created some line and node styles, borrowing code from Loop Space and circuitikz (for the node styles). With this, I can draw my diagrams, but it is still rather manual since I have to draw multiple lines to create a transistor: The diffusion and polysilicon lines. So, I decided to create a new line style, that would allow me to place the transistor gates automatically. Using code borrowed from Casimir, I was able to create the following:

\documentclass[tikz]{standalone}

% Code adapted from: https://tex.stackexchange.com/a/393496/69074
\usetikzlibrary{decorations.markings}

%{{{ Transistor Stick Diagrams

%{{{ Layer Magic

% Source: Adapted from https://tex.stackexchange.com/a/20426/69074
\pgfdeclarelayer{M3L}   % Metal 3
\pgfdeclarelayer{M2L}   % Metal 1
\pgfdeclarelayer{M1L}   % Metal 1
\pgfdeclarelayer{POL}   % Poly-Silicon
\pgfdeclarelayer{DDL}   % Diffusion
\pgfdeclarelayer{CONTL} % Contacts
\pgfsetlayers{DDL,POL,M1L,M2L,M3L,CONTL,main}

\makeatletter
\pgfkeys{%
    /tikz/on layer/.code={
        \pgfonlayer{#1}\begingroup
        \aftergroup\endpgfonlayer
        \aftergroup\endgroup
    },
    /tikz/node on layer/.code={
        \gdef\node@@on@layer{%
            \setbox\tikz@tempbox=\hbox\bgroup\pgfonlayer{#1}\unhbox\tikz@tempbox\endpgfonlayer\egroup}
        \aftergroup\node@on@layer
    },
    /tikz/end node on layer/.code={
        \endpgfonlayer\endgroup\endgroup
    }
}

\def\node@on@layer{\aftergroup\node@@on@layer}

%}}}

%{{{ Declare the Node shapes

% Code stolen (and slightly adapted from Circuitikz:
% Source: /usr/share/texmf-dist/tex/generic/circuitikz/pgfcircshapes.tex
\makeatletter
\newdimen\sticknodewidth 
\sticknodewidth=1cm

%% Left out, boring and too long

\makeatother
%}}}

%{{{ Line Styles

\tikzstyle{M3}     = [draw=green,       line width=0.3, on layer=M3L  ] % Metal 3 Path
\tikzstyle{M2}     = [draw=yellow,      line width=0.3, on layer=M2L  ] % Metal 2 Path
\tikzstyle{M1}     = [draw=turquoise,   line width=0.3, on layer=M1L  ] % Metal 1 Path
\tikzstyle{PO}     = [draw=blue,        line width=0.3, on layer=POL  ] % Poly-Silicon Path (Gate)
\tikzstyle{PD}     = [draw=red!50,      line width=0.3, on layer=DDL  ] % P-Diffusion Path (PMOS)
\tikzstyle{ND}     = [draw=red,         line width=0.3, on layer=DDL  ] % N-Diffusion Path (NMOS)
\tikzstyle{air}    = [draw=black,dashed,line width=0.1, on layer=CONTL] % N-Diffusion Path (NMOS)
%}}}

%{{{ Port Styles

\tikzstyle{M3P}    = [stickport,    color=green,      node on layer=M3L  ] % Metal 3 Terminal
\tikzstyle{M2P}    = [stickport,    color=yellow,     node on layer=M2L  ] % Metal 2 Terminal
\tikzstyle{M1P}    = [stickport,    color=turquoise,  node on layer=M1L  ] % Metal 1 Terminal
\tikzstyle{POP}    = [stickport,    color=blue,       node on layer=POL  ] % Poly-Silicon Terminal
\tikzstyle{PDP}    = [stickport,    color=red!50,     node on layer=DDL  ] % P-Diffusion Terminal (PMOS)
\tikzstyle{NDP}    = [stickport,    color=red,        node on layer=DDL  ] % N-Diffusion Terminal (NMOS)
\tikzstyle{CON}    = [stickcontact, color=black,      node on layer=CONTL] % Via (between touching M3,M2,M1,PO,P-Diff,N-Diff)
\tikzstyle{TAP}    = [sticktap,     color=black,      node on layer=CONTL] % Substrate Tap (between touching M1,Diff,N-Diff)
\tikzstyle{TAPCON} = [sticktapcon,  color=black,      node on layer=CONTL] % Merged Tap and Via
%}}}

%{{{ Automatic Transistors

\tikzset{
    gate/.style 2 args=
    {
        thick,decoration=
        {
            markings, mark=at position {#1} with
            {
                \draw[PO] (0,-0.25)coordinate(m#2ga)--(0,0.25)coordinate(m#2gb);
                \node[inner xsep=0, inner ysep=0.1mm,above left,font=\tiny,rotate=\pgfdecoratedangle-90](x) {$M_{#2}$};
            }
        },
        postaction={decorate}
    },
    stick pmos/.style=
    {
        PD,
        gate/.list={#1}
    },
    pmos/.code=
    {
        \foreach \name in {#1}
        {
            % Calculate \pos somehow, I'm not there yet
            \tikzset{gate={\pos}{\name}}
        }
    }
}
%}}}

%}}}

\begin{document}
\begin{tikzpicture}
    \draw [help lines] (-10,-10) grid (10,10);

    \draw[stick pmos={{0.333}{1},{0.666}{2}}] (0,0)--++(0,2);
    \draw[PO] (m1ga)--++(0.5,0.5);
    \draw[PO] (m2gb)--++(-0.5,-0.5);

    \draw[pmos={a,b,c}] (3,0)--++(0,2); % Gates spaced automatically
    \draw[PO] (maga)--++(0.5,0.5);
    \draw[PO] (magb)--++(-0.5,-0.5);

\end{tikzpicture}
\end{document}

This thing is quite near to what I want to get. Yet, the last part is what inspired this question: The gate/.list handler will not allow me to automate the node placement (or I just did not find how to do it), so I wanted to do the looping myself. The manual, section 82.4.6 says, that the /.list handler uses a foreach loop internally, so I tried it and met the problems. The MWE I created is suboptimal in that it uses a list of numbers for the loop, while the final version should be a loop over names. Sorry for that.

7
  • 1
    Because the contents of \foreach are executed inside a group (actually two), so local definitions (\tikzset does local definitions) are lost when the loop ends. Depending on what the "statements" you mentioned do you can use different approaches to overcome the grouping of \foreach. That or use another looping command. Jan 30, 2019 at 18:07
  • I am going to add further tikzset commands. I have written a style which takes two parameters and draws a line crossing my line. In the end this is for creating transistor stick diagrams.
    – mox
    Jan 30, 2019 at 18:34
  • With other \tikzset commands the solution I proposed will work fine. Depending on how you are drawing the diagrams, this can perhaps be achieved with styles. But we'd probably need to see the whole drawing to say for sure. Jan 30, 2019 at 18:46
  • First: Thank you! (forgot that in my first comment) I added the context and how I want to use the code. It should be compilable if you comment out the last two lines inside the tikzpicture. They cannot work yet. Could you give an example how to make the code work if the list is not numbers, or -- even better -- how the <key>/.list handler works around this problem?
    – mox
    Jan 30, 2019 at 19:49
  • In the advanced code you already use all the \aftergroup trickery, which you could also use for \tikzset. So, to some extent you may benefit from smuggling. Yet I fail to see the purpose of all this here.
    – user121799
    Jan 30, 2019 at 20:02

1 Answer 1

3

As I said in the comment, this happens because the contents of \foreach are executed inside a group, so when the group ends, the value of line width (modified by the very thick key) is restored to the previous value. That's why doing it outside the \foreach loop works okay.

Solution to the first code

Here's a simple solution by changing the looping command. There are several options, here I used expl3's \fp_step_variable:nnnNn.

First I make a copy of \fp_step_variable:nnnNn called \fpStepVariable. The syntax of \fp_step_variable:nnnNn is:

\fp_step_variable:nnnNn {<initial value>} {<step>} {<final value>} <tl var> {<code>}

so I replaced:

\foreach \pos in {0.1, 0.2, 0.3, 0.4}

by

\fpStepVariable {0.1} {0.1} {0.4} \pos

I also added two \typeouts to show the effect. After running the code the console shows:

Line width before: 0.4pt
======== Foreach: pos=0.1 ======== Foreach: pos=0.2 ======== Foreach: pos=0.3 ======== Foreach: pos=0.4
Line width after: 1.2pt

Full code:

\documentclass[tikz]{standalone}
\usepackage{expl3}

\ExplSyntaxOn
\cs_set_eq:NN \fpStepVariable \fp_step_variable:nnnNn
\ExplSyntaxOff

\tikzset{
  mystyle/.code=
  {
    \typeout{Line width before: \the\pgflinewidth}
    \fpStepVariable {0.1} {0.1} {0.4} \pos
      {
        \tikzset{very thick}, %This line is an exact copy of the comented line but has no effect.
        \message{======== Foreach: pos=\pos}
      }
    \typeout{Line width after: \the\pgflinewidth}
  }
}

\begin{document}
\begin{tikzpicture}
  \draw [help lines] (-10,-10) grid (10,10);
  \draw[mystyle] (0,0) --++ (5,0);
\end{tikzpicture}
\end{document}

Solution to the second code

The solution I used here is very similar to the previous one except for a few details.

Previously I used \fp_step_variable:nnnNn because you had an uniform sequence of numbers. Now you have letters. One way to approach the problem is to use a \clist_map_... function to loop through a comma-separated list of things. To keep the usage syntax similar I used \clist_map_variable:nNn, whose syntax is:

\clist_map_variable:nNn {<comma list>} <tl var> {<code>}

I copied \clist_map_variable:nNn into \ClistMapVariable and used:

\ClistMapVariable{#1}\name

where #1 is the list a,b,c, which you passed to the pmos key. One problem you'll face is that the variable \name expands to the name (a, b, or c), but it's not the name itself, so you need to expand it first. For that I used:

\edef\tempa{\noexpand\tikzset{gate={\pos}{\name}}}
\tempa

after the \edef, \tempa will be something like \tikzset{gate={0.12345}{a}} which does what we want.

Finally, I used

\pgfmathparse{rnd}
\edef\pos{\pgfmathresult}

to give a value to \pos.

Full code:

\documentclass[tikz]{standalone}
\usepackage{expl3}
\ExplSyntaxOn
\cs_set_eq:NN \ClistMapVariable \clist_map_variable:nNn
\ExplSyntaxOff

% Code adapted from: https://tex.stackexchange.com/a/393496/69074
\usetikzlibrary{decorations.markings}

%{{{ Transistor Stick Diagrams

%{{{ Layer Magic

% Source: Adapted from https://tex.stackexchange.com/a/20426/69074
\pgfdeclarelayer{M3L}   % Metal 3
\pgfdeclarelayer{M2L}   % Metal 1
\pgfdeclarelayer{M1L}   % Metal 1
\pgfdeclarelayer{POL}   % Poly-Silicon
\pgfdeclarelayer{DDL}   % Diffusion
\pgfdeclarelayer{CONTL} % Contacts
\pgfsetlayers{DDL,POL,M1L,M2L,M3L,CONTL,main}

\makeatletter
\pgfkeys{%
    /tikz/on layer/.code={
        \pgfonlayer{#1}\begingroup
        \aftergroup\endpgfonlayer
        \aftergroup\endgroup
    },
    /tikz/node on layer/.code={
        \gdef\node@@on@layer{%
            \setbox\tikz@tempbox=\hbox\bgroup\pgfonlayer{#1}\unhbox\tikz@tempbox\endpgfonlayer\egroup}
        \aftergroup\node@on@layer
    },
    /tikz/end node on layer/.code={
        \endpgfonlayer\endgroup\endgroup
    }
}

\def\node@on@layer{\aftergroup\node@@on@layer}

%}}}

%{{{ Declare the Node shapes

% Code stolen (and slightly adapted from Circuitikz:
% Source: /usr/share/texmf-dist/tex/generic/circuitikz/pgfcircshapes.tex
\makeatletter
\newdimen\sticknodewidth 
\sticknodewidth=1cm

%% Left out, boring and too long

\makeatother
%}}}

%{{{ Line Styles

\tikzset{
  M3/.style  = { draw=green,       line width=0.3, on layer=M3L   },  % Metal 3 Path
  M2/.style  = { draw=yellow,      line width=0.3, on layer=M2L   },  % Metal 2 Path
  M1/.style  = { draw=turquoise,   line width=0.3, on layer=M1L   },  % Metal 1 Path
  PO/.style  = { draw=blue,        line width=0.3, on layer=POL   },  % Poly-Silicon Path (Gate)
  PD/.style  = { draw=red!50,      line width=0.3, on layer=DDL   },  % P-Diffusion Path (PMOS)
  ND/.style  = { draw=red,         line width=0.3, on layer=DDL   },  % N-Diffusion Path (NMOS)
  air/.style = { draw=black,dashed,line width=0.1, on layer=CONTL },  % N-Diffusion Path (NMOS)
}

%}}}

%{{{ Port Styles

\tikzset{
  M3P/.style    = {stickport,    color=green,      node on layer=M3L  }, % Metal 3 Terminal
  M2P/.style    = {stickport,    color=yellow,     node on layer=M2L  }, % Metal 2 Terminal
  M1P/.style    = {stickport,    color=turquoise,  node on layer=M1L  }, % Metal 1 Terminal
  POP/.style    = {stickport,    color=blue,       node on layer=POL  }, % Poly-Silicon Terminal
  PDP/.style    = {stickport,    color=red!50,     node on layer=DDL  }, % P-Diffusion Terminal (PMOS)
  NDP/.style    = {stickport,    color=red,        node on layer=DDL  }, % N-Diffusion Terminal (NMOS)
  CON/.style    = {stickcontact, color=black,      node on layer=CONTL}, % Via (between touching M3,M2,M1,PO,P-Diff,N-Diff)
  TAP/.style    = {sticktap,     color=black,      node on layer=CONTL}, % Substrate Tap (between touching M1,Diff,N-Diff)
  TAPCON/.style = {sticktapcon,  color=black,      node on layer=CONTL}, % Merged Tap and Via
}
%}}}

%{{{ Automatic Transistors

\tikzset{
    gate/.style 2 args=
    {
        thick,decoration=
        {
            markings, mark=at position {#1} with
            {
                \draw[PO] (0,-0.25)coordinate(m#2ga)--(0,0.25)coordinate(m#2gb);
                \node[inner xsep=0, inner ysep=0.1mm,above left,font=\tiny,rotate=\pgfdecoratedangle-90](x) {$M_{#2}$};
            }
        },
        postaction={decorate}
    },
    stick pmos/.style=
    {
        PD,
        gate/.list={#1}
    },
    pmos/.code=
    {
        \ClistMapVariable{#1}\name
        {
          \pgfmathparse{rnd}
          \edef\pos{\pgfmathresult}
          \edef\tempa{\noexpand\tikzset{gate={\pos}{\name}}}
          \tempa
        }
    }
}
%}}}

%}}}

\begin{document}
\begin{tikzpicture}
    \draw [help lines] (-10,-10) grid (10,10);

    \draw[stick pmos={{0.333}{1},{0.666}{2}}] (0,0)--++(0,2);
    \draw[PO] (m1ga)--++(0.5,0.5);
    \draw[PO] (m2gb)--++(-0.5,-0.5);

    \draw[pmos={a,b,c}] (3,0)--++(0,2); % Gates spaced automatically
    \draw[PO] (maga)--++(0.5,0.5);
    \draw[PO] (magb)--++(-0.5,-0.5);

\end{tikzpicture}
\end{document}

Remarks

  • This is one way to do it. There are many more. You could use Mr. marmot's suggestion to "smuggle" the \tikzset outside the groups, or you could use some other looping function, or even do something clever with styles.

  • I used \clist_map_variable:nNn to keep the syntax similar to before. You could use \clist_map_inline:nn, which would spare you from doing that expansion shenanigan. I'll leave this one for you.

  • The syntax \tikzstyle{a}=[b] is deprecated. You should use \tikzset{s/.style={b}}. I changed it in your code.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .