7

I have two diagrams (both are supposed to be the same), one with explicit draw and node commands, the other one with foreach loops. Obviously, I don't understand foreach loops, as I can only get one to work. I followed the tikz tutorial, but it quickly got over my head. I am trying to understand tikz logic through trial and error, but I became a little discouraged after trying seemingly simple things. I need help with the other ones. I ask for a nudge in the right direction as to how I am to do this. How would I need to design my loops to shorten the code of the first diagram. The second one doesn't work, when the lines are commented in.

\documentclass{article}
\usepackage[margin=20mm]{geometry}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);
\foreach \x in {1.5,4,6.5,9}
    \foreach \y in {1.5,4,6.5}
        \draw (\x,\y) circle (1);

\node at (1.5,1.5) {PreA30};
\node at (1.5,4) {PreB30};
\node at (1.5,6.5) {PreC30};

\node at (4,1.5) {PreA50};
\node at (4,4) {PreB50};
\node at (4,6.5) {PreC50};

\node at (6.5,1.5) {PreA70};
\node at (6.5,4) {PreB70};
\node at (6.5,6.5) {PreC70};

\node at (9,1.5) {PreA90};
\node at (9,4) {PreB90};
\node at (9,6.5) {PreC90};

\node at (1.5,-.5) {30\%};
\node at (4,-.5) {50\%};
\node at (6.5,-.5) {70\%};
\node at (9,-.5) {90\%};
\node at (-.5,1.5) {A};
\node at (-.5,4) {B};
\node at (-.5,6.5) {C};
\end{tikzpicture}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);

\foreach \x in {1.5,4,6.5,9}
    \foreach \y in {1.5,4,6.5}
        \draw (\x,\y) circle (1);

% the next block actually compiles, but the result is quite alarming
% I tried a different order, but that doesn't seem to matter at all
%\foreach \x in {1.5,4,6.5,9}
%   \foreach \y in {1.5,4,6.5}
%       \foreach \sam in {A,B,C}
%           \foreach \perc in {30,50,70,90}
%               \node at (\x,\y) {Pre\sam\perc};
%
%\foreach \x in {1.5,4,6.5,9}
%   \foreach \perc in {30,50,70,90}
%       \node at (\x,-.5) {\perc\%};
%
%\foreach \y in {1.5,4,6.5}
%   \foreach \sam in {A,B,C}
%       \node at {-.5,\y} {\sam};
\end{tikzpicture}

\end{document}

With marmots answer, we get what is shown in the picture below. The first diagram is what is desired. The second diagram has superpositions of text in all nodes. I know this probably has to do more with logic than LaTeX, but still, I can't quite wrap my head around the right design of the for-loops. desired and undesired effect

  • I guess you need to put the loops into groups, i.e. surround them by { and }. – user121799 Feb 22 '18 at 15:03
8

You have four nested loops, while you only want two, but you need to have more than one loop variable. That can be done with e.g.

\foreach \x/\perc in {1.5/30,4/50,6.5/70,9/90}

i.e. each item in the list of variables contain two values, separated by /.

Regarding your original code, the loops work fine, the only problem is that you've used \node at {-.5,\y} {\sam}; instead of \node at (-.5,\y) {\sam}; in the final one. (Curly braces instead of parentheses in the coordinate.) Fixing that you get the overprinting issue, because for every pair of \x/\y values, you print 12 nodes: Three different letters (A, B, C) and four different numbers (30, 50, 70, 90), give 12 combinations.

\documentclass{article}
\usepackage[margin=20mm]{geometry}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);
\foreach \x in {1.5,4,6.5,9}
    \foreach \y in {1.5,4,6.5}
        \draw (\x,\y) circle (1);

\node at (1.5,1.5) {PreA30};
\node at (1.5,4) {PreB30};
\node at (1.5,6.5) {PreC30};

\node at (4,1.5) {PreA50};
\node at (4,4) {PreB50};
\node at (4,6.5) {PreC50};

\node at (6.5,1.5) {PreA70};
\node at (6.5,4) {PreB70};
\node at (6.5,6.5) {PreC70};

\node at (9,1.5) {PreA90};
\node at (9,4) {PreB90};
\node at (9,6.5) {PreC90};

\node at (1.5,-.5) {30\%};
\node at (4,-.5) {50\%};
\node at (6.5,-.5) {70\%};
\node at (9,-.5) {90\%};
\node at (-.5,1.5) {A};
\node at (-.5,4) {B};
\node at (-.5,6.5) {C};
\end{tikzpicture}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);

\foreach \x/\perc in {1.5/30,4/50,6.5/70,9/90}
   { % here you need to use braces, because there is more than one thing in the loop
   \node at (\x,-0.5) {\perc\%};
   \foreach \y/\sam in {1.5/A,4/B,6.5/C}
       {
       \draw (\x,\y) circle (1);
       \node at (\x,\y) {Pre\sam\perc};
       }
   }

% second loop for y-labels
\foreach \y/\sam in {1.5/A,4/B,6.5/C}
   \node at (-0.5,\y) {\sam};

\end{tikzpicture}
\end{document}

enter image description here

Second version

There are more available options for \foreach, as described in chapter 83 in the manual. For a case like this with regularly spaced nodes it might make sense to make use of the count option, which gives you a counter for the loop variable. And further, you can use evaluate to do calculations.

This way you can have just one loop variable for each of the loops, and calculate the coordinates based on which iteration you are on in the loop. Output is as above.

\documentclass{article}
\usepackage[margin=20mm]{geometry}
\usepackage{tikz}
\usetikzlibrary{fit, backgrounds}
\begin{document}

\begin{tikzpicture}

\foreach [count=\i, evaluate={\x=\i*2.5}] \perc in {30,50,70,90}
   { % here you need to use braces, because there is more than one thing in the loop
   \foreach [count=\j, evaluate={\y=\j*2.5}] \sam in {A,B,C}
       {
       \node [circle,draw,minimum size=2cm] (\sam\perc) at (\x,\y) {Pre\sam\perc};
       \ifnum \i=1
         \node [left=5mm] at (\sam30.west) {\sam};
       \fi
       } % inner loop ends here
   % so this node is only in outer loop
   \node [below=5mm] at (A\perc.south) {\perc\%};
   }

\begin{scope}[on background layer]
  % fit both around same nodes, with different inner sep
  \node[draw,fit=(A30)(C90),inner sep=3mm, fill=blue!20] (frame) {}; % outer frame, larger inner sep
  \node[draw,fit=(A30)(C90),inner sep=2mm, fill=red!20] (frame) {}; % inner frame, smaller inner sep
\end{scope}
\end{tikzpicture}
\end{document}

as above with filled background frame

  • Ah, good, this will teach me a great deal about the working of forloops when experimenting with it. I will do so as soon as I'm home. – thymaro Feb 22 '18 at 17:27
  • @thymaro I just added a second version as well, by the way. – Torbjørn T. Feb 22 '18 at 17:41
  • Well, the error with the braces and parentheses is likea clap on the hand (from the code on my hand, figuratively) when trying to write the line on my own instead of using the code snipets provided by my text editor. It just so happens to often be safer to click the buttons instead of trying to copy by hand. It was quicker, but not standard. Same thing happened at Tchernobyl, just on another scale. I guess I shouldn't be let near an atomic power plant's operation center. I'll keep that mind, just in case. \end{ramblings} – thymaro Feb 22 '18 at 18:01
  • nicely done. now, in the second solution, the frame is drawn at the end. suppose (I'm not gonna do it, but still) I would like to fill the two frame rectangles. How do I send them to the background? Hmm, I might just open another question about that. – thymaro Feb 22 '18 at 20:53
  • 1
    @thymaro See above. – Torbjørn T. Feb 22 '18 at 21:26
5

I know this question is about foreach, but if someone has problems understanding foreach use, it's easy to reproduce this diagram with a matrix help:

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, matrix, fit}

\begin{document}
\begin{tikzpicture}

\matrix[matrix of nodes,
            nodes={anchor=center, 
                circle, draw, minimum size=2cm},
            column 1/.style={%
                nodes={rectangle, minimum size=0pt, draw=none}},
            row 4/.style={%
                nodes={rectangle, minimum size=0pt, draw=none}},
            column sep=5mm, row sep=5mm]
(top)
{
C & PreC30 & PreC50 & PreC70 & PreC90 \\
B & PreB30 & PreB50 & PreB70 & PreB90 \\
A & PreA30 & PreA50 & PreA70 & PreA90 \\
  & 30\% & 50\% & 70\% & 90\% \\
};
\node[draw, double, fit=(top-1-2) (top-3-5), inner sep=3mm] {};
\end{tikzpicture}
\end{document}

enter image description here

  • Thanks for the alternate approach. It looks neat, especially the very much humanly readable tabular-like matrix. This could come in handy if the texts in the circles dont follow a pattern and have no apparent connection to each other. – thymaro Feb 22 '18 at 21:19
3

If you use the \foreach loops you need to put everything which should be inside the loop in a group, i.e. surround it by { and } unless you have only one command/group in the loop. Applying this to your code yields

\documentclass{article}
\usepackage[margin=20mm]{geometry}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);
\foreach \x in {1.5,4,6.5,9}
    \foreach \y in {1.5,4,6.5}
        \draw (\x,\y) circle (1);

\node at (1.5,1.5) {PreA30};
\node at (1.5,4) {PreB30};
\node at (1.5,6.5) {PreC30};

\node at (4,1.5) {PreA50};
\node at (4,4) {PreB50};
\node at (4,6.5) {PreC50};

\node at (6.5,1.5) {PreA70};
\node at (6.5,4) {PreB70};
\node at (6.5,6.5) {PreC70};

\node at (9,1.5) {PreA90};
\node at (9,4) {PreB90};
\node at (9,6.5) {PreC90};

\node at (1.5,-.5) {30\%};
\node at (4,-.5) {50\%};
\node at (6.5,-.5) {70\%};
\node at (9,-.5) {90\%};
\node at (-.5,1.5) {A};
\node at (-.5,4) {B};
\node at (-.5,6.5) {C};
\end{tikzpicture}

\begin{tikzpicture}
\draw (0,0) rectangle +(10.5,8);
\draw (0.1,0.1) rectangle +(10.3,7.8);

\foreach \x in {1.5,4,6.5,9}
    {\foreach \y in {1.5,4,6.5}
        \draw (\x,\y) circle (1);}

% the next block actually compiles, but the result is quite alarming
% I tried a different order, but that doesn't seem to matter at all
\foreach \x in {1.5,4,6.5,9}
  {\foreach \y in {1.5,4,6.5}
      \foreach \sam in {A,B,C}
          {\foreach \perc in {30,50,70,90}
              \node at (\x,\y) {Pre\sam\perc};}}

\foreach \x in {1.5,4,6.5,9}
  {\foreach \perc in {30,50,70,90}
      \node at (\x,-.5) {\perc\%};}

\foreach \y in {1.5,4,6.5}
  {\foreach \sam in {A,B,C}
      \node at (-.5,\y) {\sam};}
\end{tikzpicture}

\end{document}

The code gives you now what I think you wanted to get.

  • nay. now it just superposes every piece of text at each location (for each foreach group, that is). – thymaro Feb 22 '18 at 16:44
  • but thanks anyway for the advice. maybe I can build on it and figure something out. – thymaro Feb 22 '18 at 16:44
  • @thymaro Sorry, I probably misunderstood your question. If you tell me what you want I may solve it. But "didn't work" is not a clear description of the problem. – user121799 Feb 22 '18 at 16:51
  • 1
    @thymaro I guess Torbjørn T. solved your problem. I just did not understand what you want. – user121799 Feb 22 '18 at 17:16
  • 1
    @thymaro In principle I did precisely what the title of the question asked me to do: Misunderstanding tikz foreach loop. ;-) – user121799 Feb 23 '18 at 6:11

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.