3

I'm trying to dynamically construct a \draw command inside a tikz picture like this:

\documentclass[]{scrartcl}

\usepackage{tikz}
\usepackage{listofitems}

\begin{document}

\begin{tikzpicture}
    \draw[black,thick] (0,0)--(0,-10)--(0,10)--cycle;

    \readlist*\mylist{-10,10}

    % This line attempts to mimic the above \draw dynamically, but it fails
    %\draw[black,thick] (0,0)--{\foreachitem\x\in\mylist[]{\ifnum\xcnt>1\ -- \else\ \fi(0,\x)}}--cycle;
\end{tikzpicture}

\end{document}

However, this fails to compile with a slew of errors, including this one:

tikz Error: Cannot parse this coordinate

How can I dynamically construct a draw like this that ends with a cycle? I know this MWE is kinda dumb right now because it's just a straight line, but if I can solve this then I can achieve my actual goal.

Update

I made some progress using @marmot's suggestion of using foreach, but unfortunately I hit the same problem again when trying to apply it to my actual use case, which is this:

% args:
% 1 = draw style
% 2 = value count
% 3 = list of values (comma-separated)
\newcommand{\band}[3]{
    % I want to achieve something like this (assuming arg #2 = 3, and arg #3 = 1,2,3)
    \draw[#1] (360/3:1) -- (360/3*2:2) -- (360/3*3:3) --cycle;

    % But this fails    
    %\draw[#1] foreach [count=\xi] \X in {#3} { (360/3*\xi:\X) -- }cycle;
}
6
  • May I ask why you do not just a normal foreach here? (This is not a criticism, really just a question. ;-)
    – user121799
    Jun 12, 2019 at 23:47
  • \draw[black,thick] (0,0) foreach \X in {-10,10} { -- (0,\X)}--cycle; or \draw[black,thick] plot[samples at={0,-10,10}] (0,\x) --cycle; do what I think you want to do.
    – user121799
    Jun 12, 2019 at 23:53
  • @marmot Mainly ignorance. It's being used in an environment. I need the caller to be able to pass in any number of coordinates via a single parameter, which I will split at commas using the listofitems package.
    – me--
    Jun 12, 2019 at 23:56
  • I just played around with foreach and was able to get it working based on your suggestion. It's even cleaner now than using listofitems so that's great!
    – me--
    Jun 13, 2019 at 0:00
  • @marmot Actually, I'm back where I started when I tried to apply this to my actual problem. See my question update.
    – me--
    Jun 13, 2019 at 0:12

2 Answers 2

2

For whatever reason, the \draw command doesn't like the listofitem calculations inside of its arguments. The alternative here is to save the desired tokens in a token list and then out put the token list clean, after all the calculations are done.

I introduce two macros, \addpathtoks{} to add literal tokens to the path, and \xaddpathtoks, to expand (once) the first token in argument and add the result to the path. The expand part is needed to turn the \x into an actual number.

UPDATED ANSWER to updated question

Note the black triangle is done via \band macro, the dashed yellow (overlaying) triangle is done manually.

\documentclass[]{scrartcl}

\usepackage{tikz}
\usepackage{listofitems}
\newtoks\pathtoks
\newcommand\addpathtoks[1]{%
  \pathtoks\expandafter{\the\pathtoks#1}}
\newcommand\xaddpathtoks[1]{%
  \expandafter\addpathtoks\expandafter{#1}}

\newcommand{\band}[3]{
\readlist*\mylist{#3}
\pathtoks{}
\foreachitem\x\in\mylist[]{%
  \ifnum\xcnt=1\relax \addpathtoks{\draw#1}\else
    \addpathtoks{--}
  \fi
  \addpathtoks{(360/}\addpathtoks{#2*}%
  \xaddpathtoks{\x:}\xaddpathtoks{\x)}
  \ifnum\xcnt=\listlen\mylist[]\relax\addpathtoks{--cycle;}\fi
}
\the\pathtoks
}
\begin{document}
\begin{tikzpicture}

\band{[black,thick]}{3}{1,2,3}
\draw[yellow,thin,dashed] (360/3:1) -- (360/3*2:2) -- (360/3*3:3) --cycle;
\end{tikzpicture}

\end{document}

enter image description here

See SUPPLEMENT for possible improvement.

ORIGINAL ANSWER to original question

\documentclass[]{scrartcl}

\usepackage{tikz}
\usepackage{listofitems}
\newtoks\pathtoks
\newcommand\addpathtoks[1]{%
  \pathtoks\expandafter{\the\pathtoks#1}}
\newcommand\xaddpathtoks[1]{%
  \expandafter\addpathtoks\expandafter{#1}}
\begin{document}
   \readlist*\mylist{-10,10}
\begin{tikzpicture}
    \draw[black,thick] (0,0)--(0,-10)--(0,10)--cycle;

\pathtoks{}
\foreachitem\x\in\mylist[]{%
  \ifnum\xcnt=1\relax \addpathtoks{\draw[black,thick] (1,0)}\fi
  \addpathtoks{--(1,}\xaddpathtoks{\x)}%
  \ifnum\xcnt=\listlen\mylist[]\relax\addpathtoks{--cycle;}\fi
}
\the\pathtoks

\end{tikzpicture}

\end{document}

SUPPLEMENT

If I understand what is actually being done here, one may be able to get away with two, rather than three, arguments to \band, if it is always the case that the original #2 will always be the list length of #3.

\documentclass[]{scrartcl}

\usepackage{tikz}
\usepackage{listofitems}
\newtoks\pathtoks
\newcommand\addpathtoks[1]{%
  \pathtoks\expandafter{\the\pathtoks#1}}
\newcommand\xaddpathtoks[1]{%
  \expandafter\addpathtoks\expandafter{#1}}
\newcommand\xxaddpathtoks[1]{%
  \expandafter\xaddpathtoks\expandafter{#1}}

\newcommand{\band}[2]{
\readlist*\mylist{#2}
\pathtoks{}
\foreachitem\x\in\mylist[]{%
  \ifnum\xcnt=1\relax \addpathtoks{\draw#1}\else
    \addpathtoks{--}
  \fi
  \addpathtoks{(360/}\xxaddpathtoks{\listlen\mylist[]*}%
  \xaddpathtoks{\x:}\xaddpathtoks{\x)}
  \ifnum\xcnt=\listlen\mylist[]\relax\addpathtoks{--cycle;}\fi
}
\the\pathtoks
}
\begin{document}
\begin{tikzpicture}

\band{[black,thick]}{1,2,3}
\draw[yellow,thin,dashed] (360/3:1) -- (360/3*2:2) -- (360/3*3:3) --cycle;
\end{tikzpicture}

\end{document}
7
  • Absolutely fantastic answer and evidence I will never understand LaTeX. Thank you so much!
    – me--
    Jun 13, 2019 at 1:53
  • If I need to have { and } in my token output, how can I do that? It works if the opening and closing braces are in the same \addpathtoks call, but not if I need to have an opening in one and closing in another (because I have a \xaddpathtoks in between).
    – me--
    Jun 13, 2019 at 2:15
  • @me-- Let's say you needed \x inside a brace added to the token list, you would use \xaddpathtoks{\expandafter{\x}}. Since the x expands the first item in the list, which is \expandafter. That in turn expands \x into a number, while leaving it inside the brace pair. The only limitation is that the complete brace pair has to be added to the token list at once...you can't add a left brace in one moment and then a right brace separately. Jun 13, 2019 at 2:17
  • @me-- If you really needed to add them {} to the token list far apart, there is another way to get there with the token list. But that would be worthy of a new question. Jun 13, 2019 at 2:21
  • Hmm, I'm trying to get \xcnt into the token list, but the actual value rather than the token.
    – me--
    Jun 13, 2019 at 2:22
3

Your updated question is very closely related to this one, where nearly (?) identical difficulties have been reported. The upshot is, as explained by Paul Gaborit in this comment that

Each iteration of foreach is a (TeX) group. You can't write \draw {(0,0) --} (0,1); but you can write \draw (0,0) { -- (0,1)};

There are various ways to make it work. The essential trick is that you expand the foreach before \draw sees it. (pgfplots has a \pgfplotsinvokeforeachungrouped for precisely this purpose and there other means to achieve that.) Here is a primitive yet short and working way, which does not require any packages beyond TikZ, which you are using anyway.

\documentclass[tikz,border=3.14mm]{standalone}
% args:
% 1 = draw style
% 2 = value count
% 3 = list of values (comma-separated)
\tikzset{my foreach code/.code={\xdef\Lst{}\foreach #1 }}
\newcommand{\band}[3][]{
    % I want to achieve something like this (assuming arg #2 = 3, and arg #3 = 1,2,3)
    %\draw[#1] (360/3:1) -- (360/3*2:2) -- (360/3*3:3) --cycle;

    \draw[#1,my foreach code={\X [count=\xi] in {#3} {\xdef\Lst{\Lst (360/3*\xi:\X) -- }}}] 
      \Lst   cycle;;
}
\begin{document}
\begin{tikzpicture}
    \band{3}{1,2,3}
    \band[red]{3}{5,7,9}
\end{tikzpicture}
\end{document}

enter image description here

Let me stress that if you just want to draw these triangles and do insist on having a loop over atoms of the form (coordinate) -- then there are much simpler ways. Here is an example.

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\begin{tikzpicture}[band/.style={insert path={plot[samples at={0,1,2}]
    ({360/(\x+1)}:{#1}[\x]) -- cycle}}]
  \draw[band={1,2,3}];
  \draw[red,band={5,4,3}];
\end{tikzpicture}
\end{document}

enter image description here

This is supposed to illustrate two points:

  • You do not need to do all these \newcommands, TikZ allows you to do pretty much everything with styles.
  • You seem to re-invent plot handlers.

You must log in to answer this question.

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