9

I'd like to use arrays in tikzmath. I'd like to be able to manually define an array, with the equivalent of the python code myarray = [1,0,0,1,1], and I also would like to be able to access it in tikzmath, using something like myarray[3] as well as modify it, using something like myarray[4] = 42. I found two ways to deal with arrays, but each method has a problem.

The first one (see my MWE) is interesting because it lets me define arrays on the fly, but I don't know how to manually initialize this array using one line, without using a loop to compute the value.

The second solution I found has the opposite problem: I can define an array in a one-line manner, but then access it in tikzmath is difficult, and I need to use crazy structures like:

\pgfmathparse{\myarray[\s]}
\edef\val{\pgfmathresult}

While I'd like to use directly \myarray[\s] in the tikzmath code.

Do you know what is the best way to create an array that you can easily access and modify in tikzmath, and such that if needed I can specify the initial value in a single line command?

Thanks!

MWE:

\documentclass{report}
\usepackage{tikz}
\usetikzlibrary{math}

\begin{document}    

%%% FIRST METHOD
\begin{tikzpicture}
  \tikzmath{
    \n = 5;
    % Works, but one problem: how to manually choose the values
    % without using a loop?
    int \i;
    for \i in {1,...,\n}{
      \myarray{\i} = mod(\i,2);
    };
    % Display
    for \i in {1,...,\n}{
      if \myarray{\i} == 1 then { \ccolor = "green!30";}
      else { \ccolor = "blue!30"; };
      {\node[draw,circle,fill=\ccolor] at ({90+360/\n * (\i-1)}:2cm) {$\i$};};
    };
  }
\end{tikzpicture}

%%% SECOND METHOD
\begin{tikzpicture}
  \def\n{5}
  \def\myarray{{42,1,0,1,1,0}}
  \foreach \s in {1,...,\n}
  {
    %%% Works but not really easy to read/write, and is not inside the
    %%% tikzmath code.
    % \pgfmathparse{\myarray[\s]}
    % \edef\val{\pgfmathresult}
    % \tikzmath{ if \val == 1 then { \ccolor = "green!30"; }
    %%% Does not work, no idea why
    \tikzmath{ if \myarray[\s] == 1 then { \ccolor = "green!30"; }
      else { \ccolor = "blue!30";};
    }
    \node[draw,circle,fill=\ccolor] at ({90+360/\n * (\s-1)}:2cm) {$\s$};
  }
\end{tikzpicture}
\end{document}
3

Easy with listofitems, using the simple syntax of readlist\myarray{1,0,1,1,0}. This allows cell-by-cell access via the fully expandable \myarray[5], for example. One can access the fully expandable array length to set the loop limit, as I do with \edef\n{\listlen\myarray[]} (or, alternately, via \edef\n{\myarraylen}).

I have provided the \reassignitem macro, with syntax \reassignitem\myarray[5]{42}, to reassign array values.

In the MWE, subscripts have been placed on the graphical result, merely to show that the \reassignitem macro can, in fact, reassign the value of a list item.

\documentclass{report}
\usepackage{tikz}
\usetikzlibrary{math}
\usepackage{listofitems}
\catcode`\_11
\def\reassignitem#1[#2]#3{\loi_argcsname\loi_def{\loi_macroname#1[#2]}{#3}}
\loi_restorecatcode
\begin{document}    
\readlist\myarray{1,0,1,1,0}
\reassignitem\myarray[5]{42}
\begin{tikzpicture}
  % HERE CAN DEFINE \n IN TERMS OF ARRAY LENGTH, FOR EXAMPLE
  \edef\n{\listlen\myarray[]}
  \foreach \s in {1,...,\n}
  {
    \tikzmath{ if \myarray[\s] == 1 then { \ccolor = "green!30"; }
      else { \ccolor = "blue!30";};
    }
    \node[draw,circle,fill=\ccolor] at ({90+360/\n * (\s-1)}:2cm) {$\s_{\myarray[\s]}$};
  }
\end{tikzpicture}
\end{document}

enter image description here

The \reassignitem code

\catcode`\_11
\def\reassignitem#1[#2]#3{\loi_argcsname\loi_def{\loi_macroname#1[#2]}{#3}}
\loi_restorecatcode

is essentially equivalent to the following, but with listofitems error checking.

\makeatletter
\def\reassignitem#1[#2]#3{%
  \expandafter\def\csname\expandafter\@gobble\string#1[#2]\endcsname{#3}}
\makeatother

DISCLAIMER: I would be remiss if I did not provide the disclaimer that \reassignitem is not intended to work with nested item lists (the data in the parent/child lists will be out of sync), and that one will break listofitems logic if one were to reassign the separator character (here the comma,) as part of the reassignment replacement text.

5

Accessing the array from inside \pgfmathsetmacro, or \pgfmathparse, seems to do what you want:

  \documentclass{report}
  \usepackage{tikz}

  \begin{document}

  \begin{tikzpicture}
    \def\n{5}
    \def\myarray{{42,1,0,1,1,0}}
    \foreach \s in {1,...,\n}
    {
      \pgfmathsetmacro\ccolor{ifthenelse(\myarray[\s]==1, "green!30", "blue!30")}
      \node[draw,circle,fill=\ccolor] at ({90+360/\n * (\s-1)}:2cm) {$\s$};
    }
  \end{tikzpicture}
  \end{document}

Note that you don't need \usetikzlibrary{math}.

The output is the same as in marmot's answer:

enter image description here

  • Thank you for the answer, however I really would like to use the math library, I think it's really more readable and it's really easy to do computations inside. – tobiasBora Feb 7 '18 at 1:38
  • @tobiasBora Sorry. I played with tikzmath a little but could not see an easier way to initialise an array – Andrew Feb 7 '18 at 1:57
5
+25

Inside \tikzmath you cannot do assignments except of the allowed kind (that is, to local variables).

Here's a solution, with a reimplementation of arrays and a new keyword for \tikzmath that allows executing code at the same level (not in a group). Beware of not doing assignments that change the \tikzmath state; also no “tikzmath” is available inside it, so there must be an indirection as shown.

\documentclass{report}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{math}

% define a new keyword
\makeatletter
\def\tikz@math@process@keyword@assign{%
  \tikz@math@collecttosemicolon{\tikz@math@process@keyword@@assign}%
}
\def\tikz@math@process@keyword@@assign{%
  \tikz@math@collected\tikz@math@parse
}
\makeatother


%% Let's reimplement TikZ arrays
\ExplSyntaxOn
\cs_new:Npn \shp #1 { \prop_show:c { l_tobias_array_#1_prop } }
\NewDocumentCommand{\definearray}{mO{}}
 {% #1 = array name, #2 = items
  \prop_clear_new:c { l_tobias_array_#1_prop }
  \int_step_inline:nnnn { 0 } { 1 } { \clist_count:n { #2 } - 1 }
   {
    \prop_put:cnx { l_tobias_array_#1_prop } { ##1 } { \clist_item:nn { #2 } { ##1 + 1 } }
   }
 }
\NewDocumentCommand{\setarrayitem}{mmm}
 {% #1 = array name, #2 = index, #3 = value
  \prop_put:cff { l_tobias_array_#1_prop } { #2 } { #3 }
 }
\NewExpandableDocumentCommand{\arrayitem}{mm}
 {
  \prop_item:cf { l_tobias_array_#1_prop } { #2 }
 }
\cs_generate_variant:Nn \prop_put:Nnn { cff }
\cs_generate_variant:Nn \prop_item:Nn { cf }
\ExplSyntaxOff

\begin{document}    

%%% FIRST METHOD
\begin{tikzpicture}
  \definearray{myarray}
  \tikzmath{
    \n = 5;
    % Works, but one problem: how to manually choose the values
    % without using a loop?
    int \i;
    for \i in {1,...,\n}{
      \tmp = mod(\i,2);
      assign \setarrayitem{myarray}{\i}{\tmp};
    };
    % Display
    for \i in {1,...,\n}{
      if \arrayitem{myarray}{\i} == 1 then { \ccolor = "green!30";}
      else { \ccolor = "blue!30"; };
      {\node[draw,circle,fill=\ccolor] at ({90+360/\n * (\i-1)}:2cm) {$\i$};};
    };
  }
\end{tikzpicture}

%%% SECOND METHOD
\begin{tikzpicture}
  \def\n{5}
  \definearray{myarray}[42,1,0,1,1,0]
  \foreach \s in {1,...,\n}
  {
   \tikzmath{ if \arrayitem{myarray}{\s} == 1 then { \ccolor = "green!30"; }
      else { \ccolor = "blue!30";};
    }
    \node[draw,circle,fill=\ccolor] at ({90+360/\n * (\s-1)}:2cm) {$\s$};
  }
\end{tikzpicture}
\end{document}

enter image description here

3

Now it's a bit more like an answer. I was using this answer. It is a bit odd that one needs to call \pgfmathparse in an tikzmath environment.

\documentclass{report}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}    
\def\myarray{{42,1,0,2,1,0}}

\begin{tikzpicture}
  \tikzmath{
    \n = 5;
    % Works, but one problem: how to manually choose the values
    % without using a loop?
    int \i;
    for \i in {1,...,\n}{{\pgfmathparse{\myarray[\i]}\xdef\mynum{\pgfmathresult}};
      if \mynum == 1 then { \ccolor = "green!30";}
      else { \ccolor = "blue!30"; };
      {\node[draw,circle,fill=\ccolor] at ({90+360/\n * (\i-1)}:2cm) {$\i$};};
    };
  }
\end{tikzpicture}

\end{document}

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.