5

I'm trying to set the color of every three items in a 5x3 pattern from a tikz picture:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}


\begin{tikzpicture}
\foreach \m in {1,2,3}{
\foreach \n in {1,2,3,4,5}{
\node at (3*\n,\m) {foo};
}
}
\end{tikzpicture}

\end{document}

I can't just do it vertically.

The first foo's in the top row should be, say, red, the next two, along with the first in the second row, blue, etc. But I can't find a way to do it.

I tried adding a conditional, eg \ifnum \5*(\m-1)+\n < 2 {...} \else {...} but I don't see how to include calculations in the statement of a conditional. I tried to get around that by defining a value \def \col {\5*(\m-1)+\n} but that defines \col as the string, not the value. I thought perhaps I could just define a counter outside the if...then and then augment it at each n (so it keeps a running tally that doesn't reset each time the loop is completed), but when I try to do that, I get an error that the counter is already defined.

Of course, it would be easier to solve this without an if...then. For instance, if I could define a vector like colors <- c(replicate(3,red),replicate(3,blue),...), and I could index into the vector based on m and n (eg color = colors[5*(\m-1)+\n]), it would be easy to solve the problem. But there doesn't seem to be a TeX way to do something like that. And I don't know another TeX solution to the problem besides an if...then statement.

Are there any ways to do something like this in TeX?

0

3 Answers 3

7

I tried to get around that by defining a value \def \col {5*(\m-1)+\n} but that defines \col as the string, not the value.

TeX is not a programming language. You only define what the macro should expand to. If you want to do arithmetic on it, you could – powered by PGFMath – use

\pgfmathsetmacro\col{5*(\m-1)+\n}

then \col will expand to an integer. For \ifnum you can now use \col that way.

But you can also use \inteval:

\ifnum\inteval{5*(\m-1)+\n}<…

which does the evaluation on the spot. (\inteval{…} is basically the LaTeX equivalent of the TeX \numexpr…\relax)

The LaTeX kernel defines much more programming macros that could help you tacke a problem programmatically but that's another topic, really, and some of them are already provided in the other answers.


In this answer, I want to provide some PGFKeys-powered solutions.

The first, the key set list of colors defines colors tikzcol0, tikzcol1, … to be the given colors. You then just have to choose the right one inside the loop.

Here, I'm using the evaluate key that sets up the loop so that each evaluation is done at the start of each iteration. It would be similar to using \pgfmathsetmacro or (in the case of int) \pgfmathtruncatemacro.

To retrieve the colors, the suffix is evaluated by PGFMath (the int is necessary so we don't ask for the colors tikzcol0.0, tikzcol1.0, …).


I've also provided a .choose = {<list>}{<n>} handler which finds – in a way – the nth entry in the list. However, each entry can occupy multiple neigbouring spots in the list. A list like 2 = foo, 5 = bar means that the first two spots evaluate to foo, while the next five spots return bar. If you use an n that – in this case ­– is bigger than the available spots in the list, it will return no value.

As with everything in this answer, counting starts at 0. It makes the looping math easier and also applies to the .choose handler.

If an entry of the list contains no equal sign than that entry occupies /utils/choose (initally 3) spots.

This would actually allow you to just write

text/.choose={red, blue, green, orange, magenta}{\i}
% or
text/.choose={red, blue, green, orange, magenta}{\x+5*\y}

Code

\documentclass[tikz]{standalone}
\tikzset{
  set list of colors/.style={
    /utils/exec=\def\temp{-1},
    /tikz/@set list of colors/.list={#1}},
  @set list of colors/.code=%
    \edef\temp{\inteval{\temp+1}}%
    \colorlet{tikzcol\temp}{#1}}
\makeatletter
\tikzset{
  /utils/choose/.initial=3,
  /handlers/.choose/.code 2 args=%
    \begingroup
      \pgfmathtruncatemacro\pgfkeys@choose{#2}% make sure it's an integer
      \let\pgfkeys@return\pgfkeysnovalue      % fallback
      \pgfqkeys{/handlers/.choose}{#1}%
    \expandafter\endgroup\expandafter
    \pgfkeys@exp@call\expandafter{\pgfkeys@return},
  /handlers/.choose/.unknown/.code=%
    \ifx\pgfkeyscurrentvalue\pgfkeysnovalue@text
      \let\pgfkeyscurrentvalue\pgfkeyscurrentname
      \pgfkeysgetvalue{/utils/choose}\pgfkeyscurrentname
    \fi
    \ifnum\pgfkeyscurrentname>\pgfkeys@choose\relax
      \let\pgfkeys@return\pgfkeyscurrentvalue
      \pgfkeysdef{/handlers/.choose/.unknown}{}%
    \fi
    \edef\pgfkeys@choose{\the\numexpr\pgfkeys@choose-\pgfkeyscurrentname\relax}}
\makeatother
\begin{document}
\tikz[ % one loop
    set list of colors={red, blue, green, orange, magenta}]
  \foreach[evaluate={\x=mod(\i,5); \y=int(\i/5); \c=int(\i/3);}]
      \i in {0, ..., 14}
    \node[
      text=tikzcol\c,
      draw/.choose={1=red, 2=blue, green, 4=orange, 5=magenta}{\i}
     ] at (\x, -\y) {Foo};

\tikz[ % two loops
    set list of colors={red, blue, green, orange, magenta}]
  \foreach\y in {0, ..., 2}
    \foreach[evaluate={\c=int((\x+5*\y)/3);}] \x in {0, ..., 4}
      \node[
        text=tikzcol\c,
        draw/.choose={1=red, 2=blue, green, 4=orange, 5=magenta}{\x+5*\y}
      ] at (\x, -\y) {Foo};
\end{document}

Output

enter image description here

1
  • This is very helpful! Thanks! Commented Aug 14 at 17:02
6

Quick and dirty solution:

\documentclass{standalone}
\usepackage{tikz}
% ateb: https://tex.stackexchange.com/a/724372/
\begin{document}


\begin{tikzpicture}
\foreach \m [count=\i from 0, evaluate=\i as \k using (int((5*\i)+1))]  in {1,2,3}{
\foreach \n [count=\j from \k ]  in {1,2,3,4,5}{
\ifnum\j<4 \colorlet{mycolour}{red}
\else\ifnum\j<7 \colorlet{mycolour}{blue}
\else\ifnum\j<10 \colorlet{mycolour}{green}
\else\ifnum\j<13 \colorlet{mycolour}{orange}
\else\colorlet{mycolour}{magenta}
\fi
\fi
\fi
\fi
\node [mycolour] at (3*\n,\m) {foo \j};
}
}
\end{tikzpicture}

\end{document}

colour triads going from lower left to top right by rows read left to right

Here's a more verbose but potentially more flexible solution using an expl3 sequence. Sequences are ordered lists and items can be retrieved by indices. If a negative index is specified, items are counted from the other end of the sequence, which makes it easy to 'flip' the list of colours. This method also allows us to push some of the calculations onto expl3, which is faster than relying on pgfmath (which is extremely slow).

  • \SetColours[<integer>]{<comma-separated list of colours>} sets the colours. <integer> defaults to 3 which repeats each specified colour 3 times when adding the colours to the sequence.
  • \ClearColours just clears the sequence.
  • \AddToColours[<integer>]{<colour>} adds <colour> <integer> times to the sequence. <integer> defaults to 3.
  • \PickColour[<integer>]{<integer>}{<integer>} calculates an index from the specified integers and expands to the colour at that position in the sequence of colours. The optional <integer> defaults to 5.

So,

\SetColours{red,blue,green,orange,magenta}

sets our colour list as before. I set this in the preamble, but you could set it inside the document or the tikzpicture as required.

Now we can use \PickColour{\m}{\n} to set, say, the colour of the node's text inside the loops as before.

\foreach \m in {1,2,3} {
  \foreach \n in {1,...,5} {
    \node [text/.expanded=\PickColour{\m}{\n} ] at (3*\n,\m) {foo};
  }
}

Alternatively, to use the sequence from the other end,

\foreach \m in {1,2,3} {
  \foreach \n in {1,...,5} {
    \node [text/.expanded={\PickColour[-5]{\m}{-\n}} ] at (3*\n,\m) {foo};
  }
}

This isn't exactly the vectorisation you wanted, but perhaps sequences can serve your purposes.

colour sequence used first one way and then the other

[I'm sorry: code looks awful with no indentation, but every time I paste it here, my indentation disappears. I've added some back above, but adding it all back would be extremely tedious.]

\documentclass[tikz]{standalone}
% ateb: https://tex.stackexchange.com/a/724372/
\ExplSyntaxOn
\seq_new:N \l_matt_colour_seq
\NewDocumentCommand \SetColours { O{3} +m }
{
\seq_clear:N \l_matt_colour_seq
\clist_map_inline:nn { #2 }
{
\int_step_inline:nn { #1 }
{
\seq_put_right:Nn \l_matt_colour_seq { ##1 }
}
}
}
\NewDocumentCommand \ClearColours {}
{
\seq_clear:N \l_matt_colour_seq
}
\NewDocumentCommand \AddToColours { O{3} +m }
{
\int_step_inline:nn { #1 }
{
\seq_put_right:Nn \l_matt_colour_seq { #2 }
}
}
\NewExpandableDocumentCommand \PickColour { O{5} m m }
{
\seq_item:Ne \l_matt_colour_seq
{
(#2-1)*#1 + #3
}
}
\ExplSyntaxOff
\SetColours{red,blue,green,orange,magenta}
\begin{document}
\begin{tikzpicture}
\foreach \m in {1,2,3} {
\foreach \n in {1,...,5} {
\node [text/.expanded=\PickColour{\m}{\n} ] at (3*\n,\m) {foo};
}
}
\begin{scope}[yshift=-3.5cm]
\foreach \m in {1,2,3} {
\foreach \n in {1,...,5} {
\node [text/.expanded={\PickColour[-5]{\m}{-\n}} ] at (3*\n,\m) {foo};
}
}
\end{scope}
\end{tikzpicture}

\end{document}
1
  • This solved my problem yesterday, and the expanded code will be very helpful. Thanks! Commented Aug 14 at 17:06
6

Probably you don't need to nest \foreach-loops but can do with tuples where components are separated by /:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\foreach \m/\n/\o  in {%
  % positive vertical coordinates are above the picture's origin.
  3/1/red,%  3/...  is top-row
  3/2/red,%
  3/3/red,%
  3/4/blue,%
  3/5/blue,%
  2/1/blue,%  2/...  is middle-row
  2/2/olive,%
  2/3/olive,%
  2/4/olive,%
  2/5/orange,%
  1/1/orange,%  1/...  vertically is closest to origin 0/0 and thus bottom-row
  1/2/orange,%
  1/3/teal,%
  1/4/teal,%
  1/5/teal%
}{\expandafter\node\expandafter[\o] at (3*\n,\m) {foo};}

\end{tikzpicture}

\end{document}

enter image description here

Or combine \numexpr and \ifcase:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\foreach \m in {1,2,3}{
\foreach \n in {1,2,3,4,5}{
  \node [text=%
    \ifcase
    \numexpr
      5-(%
        (((\m)*5+1-(\n))/(3))%
        % Check whether with \numexpr's division rounding down occurred:
        \ifnum\numexpr(((\m)*5+1-(\n))/(3))*(3)\relax<\numexpr((\m)*5+1-(\n))\relax
          +1%
        \fi
      )%
    \relax
    red\or blue\or olive\or orange\or teal\else black\fi
  ] at (3*\n,\m) {foo};
}
}
\end{tikzpicture}

\end{document}

enter image description here

1
  • l3int provides \int_div_truncate:nn (which PGF undocumentedly cloned as \pgfintdivtruncate). Commented Aug 14 at 23:29

You must log in to answer this question.

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