7

I want to draw a bunch of nodes with different styles (a, b, ...) at different points (named 00, 01, ...). I am using the following code:

\foreach \n/\lst in {
  a/{00, 14, 20, 34},
  b/{01, 11, 23, 33} } {
  \foreach \xy in \lst {
    \node[\n] at (\xy) {\n};
  }
}

with the hope that \lst would be assigned a list, and \xy would be assigned elements from this list. However, I get error Package pgf Error: No shape named {01, 11, 23, 33} is known. How to fix this?

  • Why don't you just use two loops? One for a and one for b. – Henri Menke Oct 30 '13 at 22:03
  • 1
    I have a much longer list; I put only the first two elements in my code. – Māris Ozols Oct 30 '13 at 22:05
  • Do you mean \node (\n) instead of \node[\n]? And are nodes with names like 00, 14 defined? Otherwise this won't work, as the format (x,y) is expected. So please add a MWE. In general what you want works, try \foreach \x/\ylst in {1/{2, 3, 5, 7}, 2/{0, 1, 4, 9}} { \foreach \y in \ylst { \node (\x-\y) at (\x,\y) {\x,\y}; } } – Tom Bombadil Oct 30 '13 at 22:50
  • @TomBombadil No, he wanted [\n] since that'll be the style of the nodes. And the problem is in expansion and bracket removal, not in TikZ. – yo' Oct 30 '13 at 23:15
  • 1
    Remove the space between 33} and } which will get stuffed into the second \lst. – Qrrbrbirlbel Oct 31 '13 at 0:23
11

\foreach strips spaces before each list item, and then collects everything up to the next comma (or the list terminating token) before assigning to the relevant variables.

The \foreach problem arises in this case because of the extra space given before the closing brace in the list. Consider the following:

\foreach \p/\q in {a/{1,2,3}, b/{4,5,6} }
    \foreach \r in \q { (\r) }

This produces:

(1)  (2)  (3)  (4,5,6 )

the same effect is found if there is a space before the comma in the first list item:

`\foreach \p/\q in {a/{1,2,3} , b/{4,5,6} }
    \foreach \r in \q { (\r) }

which produces

(1,2,3 )  (4,5,6 )

Note, this effect will also appear if there is a space after the / when using multiple assignment.

\foreach \p/\q in {a/ {1,2,3},b/{4,5,6}}
        \foreach \r in \q { (\p:\r) }

which produces

(1,2,3 )  (4)  (5)  (6)

By removing these spaces the problems disappear...

\foreach \p/\q in {a/{1,2,3}, b/{4,5,6}}
    \foreach \r in \q { (\r) }

which produces

(1)  (2)  (3)  (4)  (5)  (6)

Should all spaces be stripped automatically? Automatically stripping spaces at the beginning is trivial (\pgfutil@ifnextchar x{...}{...} is used all over the place in PGF parsing), however it is a relatively expensive operation.

Stripping space after items is hard and would involve character by character parsing, which would be very slow. So, it is easier (and not that inconvenient) just to be careful about spaces.

The other problem is the coordinate specification. Anything that isn't obviously one of the known coordinate systems is assumed to be a node, so 00, 14 are treated as node names. There are many ways to split this specification up, here is one using the .expanded key handler:

\documentclass[border=0.125cm]{standalone}

\usepackage{tikz}

\tikzset{
    at xy split/.style 2 args={
        at={(#1,#2)}
    },
    a/.style={circle, draw=red},
    b/.style={rectangle, draw=blue}
}
\begin{document}

\begin{tikzpicture}
\foreach \n/\lst in {a/{00, 14, 20, 34},  b/{01, 11, 23, 33}} {
  \foreach \xy in \lst {
    \node[\n/.try, at xy split/.expanded=\xy] {\n};
  }
}
\end{tikzpicture}

\end{document}

enter image description here

  • nice and instructive explanations, interesting to learn that pgffor in its treatment of comma separated lists has the expected problems shared by others. I definitely should enhance \xintFor and \xintForpair with automatic stripping of the after-comma spaces. After this operation, there could be a test for an opening brace, and if that test is positive, the braces could be removed easily, one could make this conditional on the fact there is nothing but spaces after the closing brace in the item. This would not need character parsing. Presumably, I will implement it in \xintFor. – user4686 Oct 31 '13 at 8:08
  • (the braces would be removed but still play the expected rôle to protect inner commas, the whole thing would still be treated as one item, but it would be stripped of the protecting braces) – user4686 Oct 31 '13 at 8:15
  • I have implemented it: spaces coming first are discarded, and ..,<spaces>{stuff}<spaces>,.. sets the item to stuff without braces. And ..,<spaces>{stuff}stuff<afterspaces>,.. sets the item to {stuff}stuff<afterspaces>. No character per character parsing. Will be in xint 1.09f. – user4686 Oct 31 '13 at 10:41
  • @jfbu perhaps I should have meant "token by token" parsing. Either way I meant you cannot leave things to the TeX parser, you have to manually the token stream for the next token, do something on the basis of that token (which may involve taking more tokens out) and then check the next token. This is token by token parsing. Anyway, have you covered all use cases? What about <spaces>{stuff}<spaces>{stuff}<spaces>stuff? – Mark Wibrow Oct 31 '13 at 10:53
  • In my code which uses delimited macros and suitable \futurelet, all space tokens (arbitrarily many, and of any character codes) after commas are removed always, and spaces before the next comma are removed only if the item has the shape <spaces>{stuff}<spaces>. Your example will have only the first <spaces> removed (and braces will be kept untouched). – user4686 Oct 31 '13 at 11:54
4

Here is a very simple solution this advice from pgfmanual: "Different list items are separated by commas. However, this causes a problem when the list items contain commas themselves as pairs like (0,1) do. In this case, you should put the items containing commas in braces as in {(0,1)}."

\documentclass{article}
\usepackage{pgffor}
\begin{document}
\foreach \n/\lst in {%
  {a/00, 14, 20, 34},%
  {b/01, 11, 23, 33},%
  {c/02, 12, 26, 36}%
} {
  \foreach \xy in \lst {
    (\n,\xy)
  }
}
\end{document}

enter image description here

Another example with coordinates:

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \foreach \n/\lst in {%
    {a/(0,0),(1,1)},%
    {b/(1,0),(0,1)},%
    {c/(2,2)}%
  } {
    \foreach \xy in \lst {
      \node[circle,draw,minimum size=8mm] at \xy {\n};
    }
  }
\end{tikzpicture}
\end{document}

enter image description here

  • Ihis is the right way. I'll delete my answer. – yo' Oct 31 '13 at 7:32
  • are the (to me, comforting) percent signs necessary? I am a bit lost from all the TikZ code I see on this site where it seems spaces do not matter, which is great, but it appears they do matter in certain circumstances nevertheless... and TikZ makes it hard on other packages as new users are raised to sprinkle their input liberally with spaces everywhere... – user4686 Oct 31 '13 at 7:39
  • ah, I see that with {a/00, 14, 20, 34 }, the space after 34 is kept, which is reassuring to me, at least some spaces do matter. – user4686 Oct 31 '13 at 7:43
  • @jfbu TeX point of vue: The percent sign of the last item are necessary to delimit complete last item. A comma is sufficient for other items. AucTeX/Emacs point of vue: the percent signs allows to keep a good presentation after fill-paragraph... – Paul Gaborit Oct 31 '13 at 8:58
  • @PaulGaborit fill-paragraph: my nightmare at times when in a .dtx file and editing my code comments :) actually this is on reason why I arrange things for my .dtx's to contain the user manual not % prefixed. – user4686 Oct 31 '13 at 12:57
3

edit (2017): since xint 1.1 (2014/10/28) one needs here \usepackage{xinttools}, not \usepackage{xint}. Code updated.

(I am editing this to remove comments made obsolete by release 1.09f of xint)

TikZ code will surely arrive here quickly. In the mean time, you can do this kind of things with the \xintFor loops from xint

\documentclass{article}
\usepackage{xinttools}
\usepackage{tikz}

\begin{document}

% starting point, this code from OP
  % \foreach \n/\lst in { a/{00, 14, 20, 34}, b/{01, 11, 23, 33} } { \foreach \xy
  %   in \lst { \node[\n] at (\xy) {\n}; } }

% defining a node named xy at position (x,y)
%
\def\DefineNode #1{\definenode #1}
\def\definenode #1#2{(#1,#2) node (#1#2) [shape=circle,draw] {#1#2}}

\begin{tikzpicture}
% FIRST, I need to name a bunch of nodes in an automated way, 
% I decide that xy is at coordinates (x,y). 
%
% This code applies  \DefineNode to each item, but it appears that this must 
% be done before the \path command, hence the \expandafter's. 
%     (the \xintApplyUnbraced needs two expansions to get fully expanded)
%
  \expandafter\expandafter\expandafter\path 
   \xintApplyUnbraced\DefineNode {\xintCSVtoList{00, 14, 20, 34, 01, 11, 23, 33}}
               ;
% SECOND, I do what was asked in the OP
% xint 1.09f allows spaces in the input, around commas and parentheses
%
  \xintForpair #1#2 in 
     { (color=blue, { 00 , 14 , 20 , 34 } ), 
       (color=red, { 01 , 11 , 23 , 33 } ) } 
  \do
     {% 
          \xintFor #3 in {#2} \do {\node [#1] at (#3) {#3}; }%
     }
\end{tikzpicture}

\end{document}

styles nodes with xintForpair

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.