Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make an option to turn off repeat behavior of \foreach when variable values are missing. #1329

Open
DominikPeters opened this issue May 7, 2024 · 4 comments

Comments

@DominikPeters
Copy link

Brief outline of the proposed feature

The \foreach command allows users to specify several variables separated by slashes. The manual gives the example

\foreach \x / \y in {1/2,a/b} {``\x\ and \y''} yields “1 and 2”“a and b”.

There is also a behavior if some variables are not specified, namely

If some entry in the list does not have “enough” slashes, the last entry will be repeated.

The manual gives this nice example:
image

However, I frequently run into situations where I would prefer a different fallback behavior, namely that unspecified variables should be left empty. For example, suppose I want to make a bunch of blue circles, with some getting a special color.
image
At the moment, one way to do this would be

\tikz{
  \foreach \x/\nodecolor in {1/blue!20,2/blue!20,3/pink,4/blue!20,5/blue!20} {
    \node [fill=\nodecolor,inner sep=7,circle] at (\x, 0) {};
  }	
}

However, it's annoying having to always have to specify the "default" color blue!20. It would be nice to only need to specify it in cases when the color is "special". If pgffor would provide an option to turn off the "repeat" behavior, this would be possible together with an if, for example:

\tikz{
  \foreach [repeat=false] \x/\nodecolor in {1,2,3/pink,4,5} {
    \ifx\nodecolor\empty
      \def\nodecolor{blue!20}
    \fi
    \node [fill=\nodecolor,inner sep=7,circle] at (\x, 0) {};
  }	
}

Counterargument to the need for such a repeat=false option: the same effect can already be achieved by providing a final end-slash, i.e., {1/,2/,3/pink,4/,5/} which would set \nodecolor to be empty. But this is still a bit annoying, especially if I'm wrapping this code as a macro like \circlelist{1,2,3,4,5} where the user quite frequently wants none of the nodes to be special.

For one way to implement this option, see this diff:
12f1783...DominikPeters:pgf-tikz-html-manual:pgffor-repeat

Usage example

No response

@muzimuzhi
Copy link
Member

Maybe what you need here is a way to set default value(s) for last entry/entries, through for example default=<variable> using <formula>.

Currently this can mostly be emulated by evaluate option:

\documentclass{article}
\usepackage{tikz}

\begin{document}
\tikz{
  \foreach
      % pgfmath function ifthenelse() cannot be used to compare strings
      % ifthenelse("a"=="a", "yes", "no") raises error
      [evaluate=\nodecolor using \ifx\nodecolor\x"blue!20"\else"\nodecolor"\fi]
    \x/\nodecolor in {1,2,3/pink,4,5} {
      \node [fill=\nodecolor, inner sep=7,circle] at (\x, 0) {};
    }
}
\end{document}

@DominikPeters
Copy link
Author

default=<variable> using <formula> also makes sense and would be convenient.

Emulation: that's more or less what I've been doing so far (check if the two variables are equal), and that works in practice. I just feel it's dirty because one could imagine cases where one actually wants both variables to take on the same value, for example if I want circles, some of which are big:

\foreach 
  [evaluate=\radius using \ifx\radius\x"1"\else"\radius"\fi]
  \x/\radius in {1, 2/3, 3/3, 4, 5} {
    \fill [blue] (\x, 0) circle [radius=\radius*3pt];
}
image

yields only the second circle as big, when I'd want the second and third circle big.

But of course this is a theoretical problem only.

@muzimuzhi
Copy link
Member

Yes, that's the corner case the evaluate emulation cannot cover. A real implementation of default option should do the check before a missing entry is set to be the same as previous entry.

@Qrrbrbirlbel
Copy link
Contributor

I'd solve this differently:

\tikz[color 3/.style=pink]
  \foreach \x in {1, ..., 5} % allows ... notation!
    \node [fill=blue!20, color \x/.try, inner sep=7pt, circle] at (\x, 0) {};

where you also provide something like

\tikzset{
  set color/.style={@set color/.list={#1}},
  @set color/.style args={#1:#2}{color #1/.append style={#2}}}

and then you can use set color=3:pink or set color={3:pink, 4:red} for a nice shortcut.

The original notation may be achieved with:

\tikz[test/.style args={#1/#2/#3}{at={(#1,0)}, fill=#2}]
  \foreach \x in {1, 2, 3/pink, 4, 5}
    \node [fill=blue!20, test/.expand once=\x/blue!20/, inner sep=7pt, circle] {};

but that loses somehwat of the charm of PGFFor.


With \usepackage{pgffor-ext, xparse} you can do

\tikz
  \foreach[xparser Om] \Options/\x in {1, 2, [pink]3, 4, 5}
    \node [fill=blue!20, \Options, inner sep=7,circle] at (\x, 0) {};

(this technically needs style/.expand once=\Options to allow actual multiple keys or even one key-value pair)
or even

\tikz
  \foreach[xparser={ O{blue!20} }{{#2}/{#1}}]
      \x/\nodecolor in {1, 2, [pink]3, 4, 5}
    \node [fill=\nodecolor, inner sep=7, circle] at (\x, 0) {};

for a true default value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants