14

I have a project that requires many drawings like the one shown, with stacks of horizontal rectangles color-coded by length (e.g., all squares are white, all 1 by 2 rectangles are red, like Cuisenaire rods). I coded this in a very direct way using \put and \framebox, would be glad for an answer using TikZ. Ideally the input for a picture like this would be not that much more than the list of lengths, i.e., {{4},{3,2},{2,3},{1,4}} and {{1,3,1},{1,2,2},{1,1,3}}.

sample

\documentclass{article}
\usepackage{graphicx}
\usepackage{color}

\begin{document}

\begingroup
\setlength{\unitlength}{.5cm}
\begin{picture}(10,5)
\setlength{\fboxsep}{0pt}
\thicklines
\put(0,0){\colorbox{white}{\framebox(1,1){}}}  \put(1,0){\colorbox[rgb]{.75,0,1}{\framebox(4,1){}}}
\put(0,1){\colorbox{red}{\framebox(2,1){}}}  \put(2,1){\colorbox{green}{\framebox(3,1){}}}
\put(0,2){\colorbox{green}{\framebox(3,1){}}}  \put(3,2){\colorbox{red}{\framebox(2,1){}}}
\put(0,3){\colorbox[rgb]{.75,0,1}{\framebox(4,1){}}} 
\put(6,0){\colorbox{white}{\framebox(1,1){}}}  \put(7,0){\colorbox{white}{\framebox(1,1){}}}  \put(8,0){\colorbox{green}{\framebox(3,1){}}}
\put(6,1){\colorbox{white}{\framebox(1,1){}}}  \put(7,1){\colorbox{red}{\framebox(2,1){}}}  \put(9,1){\colorbox{red}{\framebox(2,1){}}}
\put(6,2){\colorbox{white}{\framebox(1,1){}}}  \put(7,2){\colorbox{green}{\framebox(3,1){}}}  \put(10,2){\colorbox{white}{\framebox(1,1){}}} 
\end{picture}
\endgroup

\end{document}
1
  • Thanks everyone for all the great answers. Picking just one is hard; I went with a mix of straightforward code and easy syntax (believe me, the project requires LOTS of these illustrations). Sep 29 '20 at 3:32
7

Here is a fairly short TikZ solution that constructs the coloured rectangles using nested \foreach statements to parse a comma separated list of rectangle lengths. With the code below the two lines

\ColouredRectangles{{4},{3,2},{2,3},{1,4}}    \qquad
\ColouredRectangles[ultra thick]{{1,3,1},{1,2,2},{1,1,3}}

produce the rectangles:

enter image description here

The colouring of the rectangles is a little cunning because this is done using the following TikZ styles:

\tikzset{
   rectangle 1/.style = {fill=white},
   rectangle 2/.style = {fill=red},
   rectangle 3/.style = {fill=green},
   rectangle 4/.style = {fill=violet},
}

When each rectangle is drawn it is given the appropriate colour by using the length of the rectangle to set its style to rectangle <length>.

Here is the full code:

\documentclass{article}
\usepackage{tikz}

\tikzset{
   % the rectangle size sets the style and hence the fill
   rectangle 1/.style = {fill=white},
   rectangle 2/.style = {fill=red},
   rectangle 3/.style = {fill=green},
   rectangle 4/.style = {fill=violet},
}

\newcommand\ColouredRectangles[2][]{%
  \begin{tikzpicture}[#1]
    \foreach \row [count=\rc] in {#2} {% loop through rows
      \xdef\offset{0} % need to remember how far we have drawn so far
      \foreach \col in \row {% loop through columns
         \draw[rectangle \col] (\offset,-\rc) rectangle ++ (\col, -1);
         \xdef\offset{\numexpr\offset+\col\relax}
      }
    }
  \end{tikzpicture}%
}

\begin{document}

    \ColouredRectangles{{4},{3,2},{2,3},{1,4}}    \qquad
    \ColouredRectangles[ultra thick]{{1,3,1},{1,2,2},{1,1,3}}

\end{document}

As the second example shows, the \ColouredRectangles command accepts an optional argument for styling of the underlying tikzpicture environment.

3
  • +1. Interesting. Is it really \foreach can read counters without knowing the set cardinality? I meant this \row [count=\rc] in {#2}. That's amazing. How that works?
    – user108724
    Sep 22 '20 at 12:35
  • @C.F.G To quote the TikZ manual, the count=⟨macro⟩ key allows ⟨macro⟩ to hold the position in the list of the current item. The optional from ⟨value⟩ statement allows the counting to begin from ⟨value⟩. So, what actually happens is that \rc is initially set to 0 and then it is incremented as \foreach iterates through the loop. As \foreach statements are enclosed in a TeX group, if you want to count the length of the list and use this outside of the loop then you can do something like \foreach \row [count=\rc] {#1}{\xdef\listLength{\rc}}.
    – user30471
    Sep 22 '20 at 13:53
  • This is great, thanks very much. Sep 23 '20 at 12:25
8

With expl3 and tikz.

Commands

  • \fancyblock receives a 2-dim array to construct blocks. Fill color is random by default (use random=false to change it), and you can set the path style by [<style>] or set the fill color quickly by |<color>|.
\fancyblock[
  at={(8, 0)}, name=b, random=false,
  transpose, y=0.5cm,
  block={
    very thick,
    line width=1pt,
    draw=teal,
  }
]{
  {2, 1, 1},
  {1, [fill=red]2, |green|1},
  {1, 3, {[line width=2pt, draw=black]|teal|1}}
}
  • \randomblock receives an 1-dim array to draw random blocks, with the length of every row be the corresponding element of the array.
\randomblock{4, 5, 4}
  • \randomrectangle receives two number, which are the width and height of the rectangle that is composed by multiple random blocks.
\randomrectangle{4}{7}

enter image description here

Complete Code

\documentclass[tikz, border=1cm]{standalone}
\usepackage{xparse}

\ExplSyntaxOn
\makeatletter

\tl_new:N   \l__at_tl
\tl_new:N   \l__name_tl
\tl_new:N   \l__anchor_tl
\tl_new:N   \l__block_style_tl
\dim_new:N  \l__block_wd_dim
\dim_new:N  \l__x_coor_dim
\dim_new:N  \l__y_coor_dim
\dim_new:N  \l__block_x_unit_dim
\dim_new:N  \l__block_y_unit_dim
\int_new:N  \l__remain_int
\int_new:N  \l__temp_int
\bool_new:N \l__random_bool
\bool_new:N \l__transpose_bool

\keys_define:nn { fancyblock }
  {
    at          . tl_set:N   = \l__at_tl,
    name        . tl_set:N   = \l__name_tl,
    random      . bool_set:N = \l__random_bool,
    random      . default:n  = true,
    transpose   . bool_set:N = \l__transpose_bool,
    transpose   . default:n  = true,
    x           . dim_set:N  = \l__block_x_unit_dim,
    y           . dim_set:N  = \l__block_y_unit_dim,
    unit        . code:n     =
      {
        \dim_set:Nn \l__block_x_unit_dim { #1 }
        \dim_set:Nn \l__block_y_unit_dim { #1 }
      },
    block       . code:n     =
      {
        \tl_put_right:Nn \l__block_style_tl { ,#1 }
      },
    anchor      . choice:,
    anchor / l  . code:n     = { \tl_set:Nn \l__anchor_tl { west } },
    anchor / r  . code:n     = { \tl_set:Nn \l__anchor_tl { east } },
    anchor / t  . code:n     = { \tl_set:Nn \l__anchor_tl { north } },
    anchor / b  . code:n     = { \tl_set:Nn \l__anchor_tl { south } },
    anchor / lb . code:n     = { \tl_set:Nn \l__anchor_tl { south~west } },
    anchor / bl . code:n     = { \tl_set:Nn \l__anchor_tl { south~west } },
    anchor / lt . code:n     = { \tl_set:Nn \l__anchor_tl { north~west } },
    anchor / tl . code:n     = { \tl_set:Nn \l__anchor_tl { north~west } },
    anchor / rb . code:n     = { \tl_set:Nn \l__anchor_tl { south~east } },
    anchor / br . code:n     = { \tl_set:Nn \l__anchor_tl { south~east } },
    anchor / rt . code:n     = { \tl_set:Nn \l__anchor_tl { north~east } },
    anchor / tr . code:n     = { \tl_set:Nn \l__anchor_tl { north~east } },
  }

\NewDocumentCommand { \randomblock } { O{} m }
  {
    \generate_num_matrix:n { #2 }
    \fancyblock[#1]{\clist_use:Nn \l__matrix_clist {,}}
  }

\NewDocumentCommand { \randomrectangle } { O{} m m }
  {
    \seq_clear:N \l_tmpa_seq
    \int_step_inline:nn { #2 }
      {
        \seq_put_right:Nn \l_tmpa_seq { #3 }
      }
    \generate_num_matrix:x
      {
        \seq_use:Nn \l_tmpa_seq { , }
      }
    \fancyblock[#1]{\clist_use:Nn \l__matrix_clist {,}}
  }

\cs_new_protected:Nn \generate_num_matrix:n
  {
    \clist_clear_new:N \l__matrix_clist
    \clist_map_inline:nn { #1 }
      {
        \generate_num_seq:n { ##1 }
        \clist_put_right:Nx \l__matrix_clist
          {
            { { \clist_use:Nn \l__row_clist {,} } }
          }
      }
  }
\cs_generate_variant:Nn \generate_num_matrix:n { x }

\cs_new_protected:Nn \generate_num_seq:n
  {
    \clist_clear_new:N \l__row_clist
    \int_set:Nn \l__remain_int { #1 }
    \int_while_do:nn { \l__remain_int > 0 }
      {
        \int_set:Nn \l__temp_int {
          \int_rand:n { \l__remain_int }
        }
        \int_add:Nn \l__remain_int { -\l__temp_int }
        \clist_put_right:Nx \l__row_clist { \int_use:N \l__temp_int }
      }
  }

\NewDocumentCommand { \fancyblock } { O{} m }
  {
    \tl_clear:N \l__block_style_tl
    \keys_set:nn { fancyblock }
      {
        at        = { (0, 0) },
        block     = { draw, thick },
        unit      = 1cm,
        transpose = false,
        anchor    = lb,
        name      = block,
        random,
        #1
      }
    \draw_block_matrix:x { #2 }
  }

\cs_new_protected:Nn \draw_block_matrix:n
  {
    \clist_set:Nn \l_tmpa_clist { #1 }
    \bool_if:NTF \l__transpose_bool
      {
        \dim_zero:N \l__x_coor_dim
      }
      {
        \dim_zero:N \l__y_coor_dim
        \clist_reverse:N \l_tmpa_clist
      }
    \matrix [anchor=\l__anchor_tl] (\l__name_tl) at \l__at_tl {
      \clist_map_inline:Nn \l_tmpa_clist
        {
          \draw_row:n { ##1 }
          \bool_if:NTF \l__transpose_bool
            {
              \dim_add:Nn \l__x_coor_dim { \l__block_x_unit_dim }
            }
            {
              \dim_add:Nn \l__y_coor_dim { \l__block_y_unit_dim }
            }
        }\\
    };
  }
\cs_generate_variant:Nn \draw_block_matrix:n { x, v, f }

\cs_new_protected:Nn \draw_row:n
  {
    \bool_if:NTF \l__transpose_bool
      {
        \dim_zero:N \l__y_coor_dim
      }
      {
        \dim_zero:N \l__x_coor_dim
      }
    \clist_map_inline:nn { #1 }
      {
        \draw_block:n { ##1 }
      }
  }

\cs_new_protected:Nn \draw_block:n
  {
    \tl_clear_new:N \l__draw_block_tl
    \parse_args:n { #1 }
    \definecolor{random}{RGB}{
      \int_rand:n { 255 },
      \int_rand:n { 255 },
      \int_rand:n { 255 }
    }
    \tl_set:Nx \l_tmpb_tl
      {
        \bool_if:NTF \l__random_bool
          { fill=random }
          { }
      }
    \tl_set:Nx \l__draw_block_tl
      {
        \exp_not:N \path[
          \l__block_style_tl,
          \l_tmpb_tl,
          \seq_use:Nn \l__block_style_seq { , }]
          (\dim_use:N \l__x_coor_dim, \dim_use:N \l__y_coor_dim) --
        \bool_if:NTF \l__transpose_bool
          {
              ++(0, \dim_use:N \l__block_wd_dim) --
              ++(\dim_use:N \l__block_x_unit_dim, 0) --
              ++(0, \dim_eval:n { -\l__block_wd_dim }) -- cycle;
          }
          {
              ++(\dim_use:N \l__block_wd_dim, 0) --
              ++(0, \dim_use:N \l__block_y_unit_dim) --
              ++(\dim_eval:n { -\l__block_wd_dim }, 0) -- cycle;
          }
      }
    \tl_use:N \l__draw_block_tl
    \bool_if:NTF \l__transpose_bool
      {
        \dim_add:Nn \l__y_coor_dim { \l__block_wd_dim }
      }
      {
        \dim_add:Nn \l__x_coor_dim { \l__block_wd_dim }
      }
  }

\cs_new_protected:Nn \parse_args:n
  {
    \seq_clear_new:N \l__block_style_seq
    \fp_set:Nn \l__block_wd_fp { 1 }
    \parse_next_arg: #1\stop
  }

\cs_new_protected:Nn \parse_next_arg:
  {
    \peek_meaning_ignore_spaces:NTF [
      { \parse_style:w }
      {
        \peek_meaning_ignore_spaces:NTF |
          { \parse_fill:w }
          { \parse_len:w }
      }
  }

\cs_new_protected:Npn \parse_style:w [#1]
  {
    \seq_put_right:Nn \l__block_style_seq { #1 }
    \parse_next_arg:
  }

\cs_new_protected:Npn \parse_fill:w |#1|
  {
    \seq_put_right:Nn \l__block_style_seq { fill=#1 }
    \parse_next_arg:
  }

\cs_new_protected:Npn \parse_len:w #1\stop
  {
    \tikz@checkunit{#1}
    \legacy_if:nTF { tikz@isdimension }
      { \dim_set:Nn \l__block_wd_dim { #1 } }
      {
        \bool_if:NTF \l__transpose_bool
          {
            \dim_set:Nn \l__block_wd_dim { \l__block_y_unit_dim * #1 }
          }
          {
            \dim_set:Nn \l__block_wd_dim { \l__block_x_unit_dim * #1 }
          }
      }
  }

\makeatother
\ExplSyntaxOff

\begin{document}
\begin{tikzpicture}
\fancyblock[name=a]{
  {2, 2, 1},
  {1, 2, 1},
  {3, 1, 1}
}
\path (a.south) node [below] {\verb|\fancyblock|};

\fancyblock[
  at={(8, 0)}, name=b, random=false,
  transpose, y=0.5cm,
  block={
    very thick,
    line width=1pt,
    draw=teal,
  }
]{
  {2, 1, 1},
  {1, [fill=red]2, |green|1},
  {1, 3, {[line width=2pt, draw=black]|teal|1}}
}
\path (b.south) node [below] {\verb|\fancyblock| with options};

\randomblock[at={([yshift=1cm]a.north west)}, name=c]{4, 5, 4}
\path (c.south) node [below] {\verb|\randomblock|};

\randomrectangle[at={(c.south -| b.center)}, name=d, anchor=b]{4}{7}
\path (d.south) node [below] {\verb|\randomrectangle|};

\end{tikzpicture}
\end{document}
1
  • Wow, that's impressive. And the random colors make the standard ones look boring! Sep 22 '20 at 3:14
6

Based on my answer here: Can TikZ create pixel art images?

\documentclass{article}
\usepackage{xcolor}
\usepackage{stackengine}
\newlength\blocksize
\setlength\blocksize{1ex}
\newcommand\block[2]{\kern-\fboxrule\fboxsep=0pt%
  \fbox{\color{#1}\rule{%
    \dimexpr#2\blocksize+\numexpr#2-1\relax\fboxrule\relax}{\blocksize}}}
\newcommand\gr[1][1]{\block{green}{#1}}
\newcommand\rd[1][1]{\block{red}{#1}}
\newcommand\bl[1][1]{\block{blue}{#1}}
\newcommand\wh[1][1]{\block{white}{#1}}
\setstackgap{S}{-\fboxrule}
\begin{document}
\Shortstack[l]{
\rd\gr[2]\gr\rd[3]\\
\gr\bl[3]\gr\gr\\
\gr\bl\rd[2]\wh\wh\gr}
\end{document} 

enter image description here

7
  • Thanks & sorry that I didn't find your very related previous answer. I like the simplicity of this code. The additional piece I'm looking for would convert {1,3,1}, for instance, to \wh \gr[3] \wh. I might be able to manage that; since the colors are fixed by length, there's no need for the numeric input. That is, every \gr would be your \gr[3]. Sep 21 '20 at 17:57
  • @BrianHopkins If, in fact, the combination always appears in the same way, you can infact use \newcommand\convert{\wh\gr[3]\wh}, and simply invoke it as \convert. Sep 21 '20 at 18:08
  • No, it's not that regular. I need to be able to have any integer composition as the input. But every 3 will be green, for instance, so maybe I can just change your \gr definition to \newcommand\gr{\block{green}{3}}? Sep 21 '20 at 18:10
  • @BrianHopkins ...or, you could just take the existing definition of \gr and change the default value from [1] to [3]. Sep 21 '20 at 19:57
  • 1
    @BrianHopkins What I am suggesting is \newcommand\gr[1][3]{\block{green}{#1}}, if you wish \gr to create a block that defaults to 3 units wide. Sep 22 '20 at 9:52
6

Here is a simple TikZ solution. The RoB (rows of boxes) environment has two arguments: the * determines whether the rows are drawn from top to bottom, or bottom-to-top; the optional argument [...] is the size of the box -- the default is 10pt. The \boxrow macro takes an argument of comma-separated values, <width of box>/<fill color>. There is nothing complicated here: the two counters, xbpos and ybpos, keep track of the implicit coordinates.

\documentclass{article}

%\usepackage{xcolor}
\usepackage{xparse}
\usepackage{tikz}

\newcounter{ybpos}
\newcounter{xbpos}
\newlength{\boxsize}

%% |=====8><-----| %%

% * draws rows top-to-bottom; optional argument for size, default=10pt
\NewDocumentEnvironment{RoB}{sO{10pt}}{%
    \IfBooleanTF{#1}{\def\ttob{-1}}{\def\ttob{1}}%
    \setcounter{ybpos}{0}
    \setlength{\boxsize}{#2}
    \begin{tikzpicture}[outer sep=0pt]
}{%
    \end{tikzpicture}
}

\NewDocumentCommand{\boxrow}{m}{% csv: width in units of \boxsize/color
    \setcounter{xbpos}{0}
    \foreach \xbwd/\boxcolor in {#1}{%
        \node[draw,
            thick,
            fill=\boxcolor,
            minimum height=\boxsize,
            minimum width=\xbwd*\boxsize,
            anchor=south west] at (\thexbpos*\boxsize,\ttob*\theybpos*\boxsize) {};
        \addtocounter{xbpos}{\xbwd}
    }
    \stepcounter{ybpos}%
}

%% |=====8><-----| %%

\begin{document}

\begin{RoB}
    \boxrow{2/red,1/white,3/green,1/purple}
    \boxrow{2/blue,1/red,2/green,2/yellow}
    \boxrow{1/white,2/red,1/brown,3/orange}
\end{RoB}

\bigskip

\begin{RoB}*[18pt]
    \boxrow{2/red,1/white,3/green,1/purple}
    \boxrow{3/blue,1/red,1/green,2/yellow}
    \boxrow{1/white,2/red,1/brown,3/orange}
\end{RoB}

\end{document}

rows of boxes example

Update

My bad! I completely missed the dependence of color on size. You can change the colors to suit (noted in the code). All else works the same as in the first answer.

\documentclass{article}

%\usepackage{xcolor}
\usepackage{xparse}
\usepackage{tikz}

\newcounter{ybpos}
\newcounter{xbpos}
\newlength{\boxsize}

%% |=====8><-----| %%

% * draws rows top-to-bottom; option argument for size, default=10pt
\NewDocumentEnvironment{RoB}{sO{10pt}}{%
    \IfBooleanTF{#1}{\def\ttob{-1}}{\def\ttob{1}}%
    \setcounter{ybpos}{0}
    \setlength{\boxsize}{#2}
    \begin{tikzpicture}[outer sep=0pt]
}{%
    \end{tikzpicture}
}

\NewDocumentCommand{\boxrow}{m}{% csv: width in units of \boxsize/color
    \setcounter{xbpos}{0}
    \foreach \xbwd in {#1}{%
        %% Change the order of colors to suit...
        \def\boxcolor{\ifcase\xbwd \or white\or red\or green\or purple\fi}
        \node[draw,
            thick,
            fill=\boxcolor,
            minimum height=\boxsize,
            minimum width=\xbwd*\boxsize,
            anchor=south west] at (\thexbpos*\boxsize,\ttob*\theybpos*\boxsize) {};
        \addtocounter{xbpos}{\xbwd}
    }
    \stepcounter{ybpos}%
}

%% |=====8><-----| %%

\begin{document}

\begin{RoB}
    \boxrow{1,2,1,3,1,2,1}
    \boxrow{2,1,3,1,4}
    \boxrow{4,1,3,1,2}
\end{RoB}

\medskip

\begin{RoB}*[1cm]
    \boxrow{1,2,1,3,1,2,1}
    \boxrow{2,1,3,1,4}
    \boxrow{4,1,3,1,2}
\end{RoB}

\end{document}

new row colors

Another update...

I refer Gentle Reader to https://en.wikipedia.org/wiki/Cuisenaire_rods. This code differs from my previous two answers in that keyval.sty is used to communicate options to the environment. Note the following changes:

  1. There is a new option, boxsize that is used to specify the size (height) of the boxes. See the code below for examples.

  2. There are three color schemes. In the code, they are \cuisenairei, \cuisenaireii and \cuisenaireiii. They are available as options to the environment -- see examples below. The option i (therefore \cuisenairei) is the default; using the third scheme would require setting colorset=iii for example.

  3. You can use the number 0 to specify a placeholder (not drawn) box, making possible arbitrary spacing. See the last example below.

  4. The colors are quick and relatively accurate approximations, though you are free to alter them to suit your own purposes.

  5. The color black is used for any non-specified colors. It is also used for one of the rod colors in some of the schemes (7 in \cuisenairei, for example).

  6. Another option to the RoB environment: You can specify that the numbers can be shown in the colored rectangles. Simply writing shownums or shownums=true will work.

Apologies for the somewhat more prolix code.

    \documentclass{article}

\usepackage[margin=0.5in]{geometry}
\usepackage{xparse}
\usepackage{tikz}
\usepackage{keyval}

\newcounter{ybpos}
\newcounter{xbpos}
\newlength{\boxsize}
\newif\ifshownums

\def\colorset{i}
\setlength{\boxsize}{10pt}

\makeatletter
\define@key{cuisen}{colorset}{\def\colorset{#1}}
\define@key{cuisen}{boxsize}{\setlength{\boxsize}{#1}}
\define@key{cuisen}{shownums}[true]{\csname shownums#1\endcsname}
\makeatother

%% |=====8><-----| %%
%% Default is \cuisinairei
%% https://en.wikipedia.org/wiki/Cuisenaire_rods
%% Standard
%% 1-10
\def\cuisenairei{\ifcase\xbwd x\or white\or red\or green!50\or purple\or yellow\or green!50!black\or black\or brown\or blue\or orange\else black\fi}

%% to 10,12,16 all others black
\def\cuisenaireii{\ifcase\xbwd x\or white\or pink\or blue!50\or red\or yellow\or purple\or black\or brown\or blue\or orange\or black\or green\or black\or black\or black\or brown!50\else black\fi}

%%
%% to 12
%% 1 is white.
%% The first three primes (2,3,5) are basic colors: red, blue and yellow.
%% Primes >5 are a shade of gray
%% The remaining non-primes result from mixing the colors of their factors.
\definecolor{mauve}{rgb}{0.89,0.685,1}
\def\cuisenaireiii{\ifcase\xbwd x\or white\or red!50\or blue!50\or red\or yellow\or violet\or gray!80\or red!85!black\or blue!80!red\or yellow!60!orange\or black!60\or mauve\else black\fi}

% * draws rows top-to-bottom; optional argument for size, default=10pt
\NewDocumentEnvironment{RoB}{sO{}}{%
    \IfBooleanTF{#1}{\def\ttob{-1}}{\def\ttob{1}}%
    \setcounter{ybpos}{0}
    \setkeys{cuisen}{#2}
    \begin{tikzpicture}[outer sep=0pt]
}{%
    \end{tikzpicture}%
}

\NewDocumentCommand{\boxrow}{m}{% 
    \setcounter{xbpos}{0}
    \foreach \xbwd in {#1}{%
        %% Change the order of colors to suit...
        \expandafter\def\expandafter\boxcolor\expandafter{\csname cuisenaire\colorset\endcsname}
        \if x\boxcolor
            \node[minimum width=\boxsize,
                minimum height=\boxsize,
                anchor=south west] at (\thexbpos*\boxsize,\ttob*\theybpos*\boxsize) {};
            \stepcounter{xbpos}
        \else
            \node[draw,
                inner sep=0pt,
                thick,
                fill=\boxcolor,
                minimum height=\boxsize,
                minimum width=\xbwd*\boxsize,
                anchor=south west] at (\thexbpos*\boxsize,\ttob*\theybpos*\boxsize)
                    {\ifshownums\tiny\xbwd\fi};
            \addtocounter{xbpos}{\xbwd}
        \fi
    }%
    \stepcounter{ybpos}%
}

%% |=====8><-----| %%

\parindent0pt

\begin{document}

\begin{RoB}*[colorset=i]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\end{RoB}
\begin{RoB}*[colorset=i,boxsize=9pt]
\boxrow{10}
\boxrow{1,9}
\boxrow{2,8}
\boxrow{3,7}
\boxrow{4,6}
\boxrow{5,5}
\boxrow{6,4}
\boxrow{7,3}
\boxrow{8,2}
\boxrow{9,1}
\boxrow{10}
\end{RoB}

\medskip

\begin{RoB}[colorset=ii,shownums]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\boxrow{12}
\boxrow{16}
\end{RoB}
\begin{RoB}[colorset=ii,boxsize=9pt,shownums]
\boxrow{16}
\boxrow{1,3,12}
\boxrow{2,4,10}
\boxrow{3,5,8}
\boxrow{4,12}
\boxrow{5,1,10}
\boxrow{6,4,6}
\boxrow{7,9}
\boxrow{8,5,3}
\boxrow{9,7}
\boxrow{10,1,5}
\boxrow{12,1,3}
\boxrow{16}
\end{RoB}

\medskip

\begin{RoB}*[colorset=iii]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\boxrow{11}
\boxrow{12}
\end{RoB}
\begin{RoB}*[colorset=iii,boxsize=9pt]
\boxrow{12}
\boxrow{1,11}
\boxrow{2,10}
\boxrow{3,9}
\boxrow{4,8}
\boxrow{5,7}
\boxrow{6,6}
\boxrow{7,5}
\boxrow{8,4}
\boxrow{9,3}
\boxrow{10,2}
\boxrow{11,1}
\boxrow{12}
\end{RoB}

\medskip

\noindent
\hspace{1.25in}
\begin{RoB}*[boxsize=1cm]
\boxrow{1,0,0,0,1}
\boxrow{0}
\boxrow{0,0,1}
\boxrow{0,0,1}
\boxrow{0}
\boxrow{0,3}
\end{RoB}

\end{document}

new rows with optional numbers

Yet another update...

There are two differences here: 1) the use of the TikZ \draw command in place of \node; and 2) rounded corners for the rectangles (the same option could be applied to \node commands) -- gives a feeling for `rods'.

\documentclass{article}

\usepackage[margin=0.5in]{geometry}
\usepackage{xparse}
\usepackage{tikz}
\usepackage{keyval}

\newcounter{ybpos}
\newcounter{xbpos}
\newlength{\boxsize}
\newif\ifshownums

\def\colorset{i}
\setlength{\boxsize}{10pt}

\makeatletter
\define@key{cuisen}{colorset}{\def\colorset{#1}}
\define@key{cuisen}{boxsize}{\setlength{\boxsize}{#1}}
\define@key{cuisen}{shownums}[true]{\csname shownums#1\endcsname}
\makeatother

%% |=====8><-----| %%
%% Default is \cuisinairei
%% https://en.wikipedia.org/wiki/Cuisenaire_rods
%% Standard
%% 1-10
\def\cuisenairei{\ifcase\xbwd x\or white\or red\or green!50\or purple\or yellow\or green!50!black\or black\or brown\or blue\or orange\else black\fi}

%% to 10,12,16 all others black
\def\cuisenaireii{\ifcase\xbwd x\or white\or pink\or blue!50\or red\or yellow\or purple\or black\or brown\or blue\or orange\or black\or green\or black\or black\or black\or brown!50\else black\fi}

%%
%% to 12
%% 1 is white.
%% The first three primes (2,3,5) are basic colors: red, blue and yellow.
%% Primes >5 are shades of gray
%% The remaining non-primes result from mixing the colors of their factors.
\definecolor{mauve}{rgb}{0.89,0.685,1}
\def\cuisenaireiii{\ifcase\xbwd x\or white\or red!50\or blue!50\or red\or yellow\or violet\or gray!80\or red!85!black\or blue!80!red\or yellow!60!orange\or black!60\or mauve\else black\fi}

% * draws rows top-to-bottom; optional argument for keyval
\NewDocumentEnvironment{RoB}{sO{}}{%
    \IfBooleanTF{#1}{\def\ttob{-1}}{\def\ttob{1}}%
    \setcounter{ybpos}{0}%
    \setkeys{cuisen}{#2}%
    \begin{tikzpicture}[outer sep=0pt]
}{%
    \end{tikzpicture}%
}

\NewDocumentCommand{\boxrow}{m}{% 
    \setcounter{xbpos}{0}%
    \foreach \xbwd in {#1}{%
        \expandafter
            \def
        \expandafter
            \boxcolor
        \expandafter{\csname cuisenaire\colorset\endcsname}
        \if x\boxcolor
            \path (\thexbpos*\boxsize,\ttob*\theybpos*\boxsize)
                rectangle
                ++(\boxsize,\boxsize);
            \stepcounter{xbpos}
        \else
            \draw[thick,fill=\boxcolor,
                rounded corners=2pt](\thexbpos*\boxsize,\ttob*\theybpos*\boxsize)
                rectangle node[inner sep=0pt,font=\tiny] {\ifshownums\xbwd\fi}
                ++(\xbwd*\boxsize,\boxsize);
            \addtocounter{xbpos}{\xbwd}
        \fi
    }%
    \stepcounter{ybpos}%
}


%% |=====8><-----| %%

\parindent0pt

\begin{document}

\begin{RoB}*[colorset=i]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\end{RoB}
\begin{RoB}*[colorset=i,boxsize=9pt]
\boxrow{10}
\boxrow{1,9}
\boxrow{2,8}
\boxrow{3,7}
\boxrow{4,6}
\boxrow{5,5}
\boxrow{6,4}
\boxrow{7,3}
\boxrow{8,2}
\boxrow{9,1}
\boxrow{10}
\end{RoB}

\medskip

\begin{RoB}[colorset=ii,shownums]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\boxrow{12}
\boxrow{16}
\end{RoB}
\begin{RoB}[colorset=ii,boxsize=9pt,shownums]
\boxrow{16}
\boxrow{1,3,12}
\boxrow{2,4,10}
\boxrow{3,5,8}
\boxrow{4,12}
\boxrow{5,1,10}
\boxrow{6,4,6}
\boxrow{7,9}
\boxrow{8,5,3}
\boxrow{9,7}
\boxrow{10,1,5}
\boxrow{12,1,3}
\boxrow{16}
\end{RoB}

\medskip

\begin{RoB}*[colorset=iii]
\boxrow{1}
\boxrow{2}
\boxrow{3}
\boxrow{4}
\boxrow{5}
\boxrow{6}
\boxrow{7}
\boxrow{8}
\boxrow{9}
\boxrow{10}
\boxrow{11}
\boxrow{12}
\end{RoB}
\begin{RoB}*[colorset=iii,boxsize=9pt]
\boxrow{12}
\boxrow{1,11}
\boxrow{2,10}
\boxrow{3,9}
\boxrow{4,8}
\boxrow{5,7}
\boxrow{6,6}
\boxrow{7,5}
\boxrow{8,4}
\boxrow{9,3}
\boxrow{10,2}
\boxrow{11,1}
\boxrow{12}
\end{RoB}

\medskip

\noindent
\hspace{1.25in}
\begin{RoB}*[boxsize=1cm]
\boxrow{1,0,0,0,1}
\boxrow{0}
\boxrow{0,0,1}
\boxrow{0,0,1}
\boxrow{0}
\boxrow{0,3}
\end{RoB}

\end{document}

rounded corners for 'rods'

5
  • I've updated the answer to make the color of the rectangles dependent upon their width. I should have noticed that sooner.
    – sgmoye
    Sep 22 '20 at 14:51
  • Wonderful, thanks very much. Sep 23 '20 at 12:23
  • I've an update or two coming...
    – sgmoye
    Sep 23 '20 at 14:16
  • Can't help myself: another update. This allows for showing the numbers in the colored rectangles. Last one. Promise. Maybe...
    – sgmoye
    Sep 23 '20 at 20:29
  • Ha, I'd encourage you to stop, but your updates have been very helpful. Sep 23 '20 at 21:41
4

You can use pic.

\documentclass[tikz,margin=3mm]{standalone}
\usepackage{color}

\tikzset{
    pics/cube/.style args={#1-#2}{
      code = {
    \draw [black,fill=#2](0,0)--(#1,0)--(#1,1)--(0,1)--cycle;  
      }
      }
      }

\begin{document}

\begin{tikzpicture}
\pic at (0,0) {cube=1-white};
\pic at (1,0) {cube=1-white};
\pic at (2,0) {cube=3-green};
\pic at (0,1) {cube=1-white};
\pic at (1,1) {cube=2-red};
\pic at (3,1) {cube=2-red};
\pic at (0,2) {cube=1-white};
\pic at (1,2) {cube=3-green};
\pic at (4,2) {cube=1-white};
\end{tikzpicture}

\end{document}

enter image description here

3
  • Thanks, that looks good. I'm hoping, though, for something that can take {{1,3,1},{1,2,2},{1,1,3}} as input and produce the picture without having to think through the coordinates. Sep 21 '20 at 17:49
  • Your welcome. A question, are 5 blocks fixed in a row?
    – user31034
    Sep 21 '20 at 18:11
  • No, I want to be able to input an integer composition (up to, say, length 8) and generate the corresponding color-coded rectangle. So (4,4) would make two 1x4 purple rectangles in a row, (2,2,2,1,1) would make three 1x2 red triangles then two white squares all in a row, etc. Sep 21 '20 at 18:21

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.