7

I am quite new to LaTeX and in the paper I am writing, I need to plot many grids with filled rectangles (some cells in the grid should be left white, others will be black). As I don't like to write the same piece of code again and again, I thought about defining a new command for tikz (is that even possible?).

\newcommand{\cell}[2]{
    \fill[black!](#1*0.5, (-#2)*0.5)rectangle(#1*0.5 + 0.5, (-#2)*0.5+0.5)
}

This should plot a dark rectangle at (0.5*x, -0.5*y, 0.5*(x+1), -0.5(y+1)) and is used as:

  \begin{tikzpicture}
        \cell{1}{1};
        \draw[step=0.5cm,gray,very thin] (2.0,-2.0) grid (0.0, 0.0);
  \end{tikzpicture}

However it does not seem to work as I get the following error message:

! Package tikz Error: Giving up on this path. Did you forget a semicolon?
See the tikz package documentation for explanation.
Type H <return> for immediate help.

It occurs in the line that I use the \cell command. What am I doing wrong?

  • 1
    And putting semicolon in definition? \fill[black!](#1*0.5, (-#2)*0.5)rectangle(#1*0.5 + 0.5, (-#2)*0.5+0.5); – skpblack Jul 19 '17 at 21:20
  • Unfortunatly it did not change anything not even the error message. – WodkaRHR Jul 19 '17 at 21:22
  • the error is unconnected to the use of a macro. \cell{1}{1} expands to \fill[black!](1*0.5, (-1)*0.5)rectangle(1*0.5 + 0.5, (-1)*0.5+0.5) and you get the same error if you use that directly. The full error, which you do not quote, highlights the position of the syntax error as (-1)* – David Carlisle Jul 19 '17 at 21:32
  • Does that mean I can not use multiplication for the coordinates? Or is there any other "right" syntax to use? – WodkaRHR Jul 19 '17 at 21:36
  • Rather than (-1)*? simply use -?. – Andrew Jul 20 '17 at 0:14
5

If you have a lot of these diagrams then rather than just defining a macro that will shade a single cell I recommend defining a macro that will draw the entire grid. Macros make it much easier to read your LaTeX code and to edit or change it later on. This way something like

 \Blocks{2}{(1,1)}

will draw a 2x2 grid with a shaded box in position (1,1) and

 \Blocks{4}{(1,1),(2,2),(3,3),(4,4)}

will draw a 4x4 grid with the SW-NE diagonal shaded.

In terms of the mechanics, you do not need to use coordinate calculations because tikz package has this built in:

\draw[fill=black](1,1) rectangle ++(1,1);

will draw a rectangle with lower left corner at position (1,1) and top right corner at position (2,2).

Putting this together, I would write your macro like this:

\documentclass[border=2mm]{standalone}
\usepackage{tikz}
\usepackage{xparse}

\NewDocumentCommand\Blocks{ omm }
{% usage \Blocks[cols]{rows}{list of shaded cells}
   \begin{tikzpicture}
     \def\rowsForBlocks{#2}
     \IfNoValueTF{#1}{\def\colsForBlocks{\rowsForBlocks}}
                     {\def\colsForBlocks{#1}}
     % draw the grid
     \foreach \row in {0,...,\colsForBlocks} {
        \draw[gray](\row,0)-- ++(0,\rowsForBlocks);
     }
     \foreach \col in {0,...,\rowsForBlocks} {
        \draw[gray](0,\col)-- ++(\colsForBlocks,0);
     }
     % shade the specified boxes
     \foreach \cell in {#3} {
         \draw[fill=black!50] \cell rectangle ++ (-1,-1);
     }
   \end{tikzpicture}
}

\begin{document}

\Blocks{2}{(1,1)}

\Blocks[3]{2}{(1,1),(2,2),(3,1)}

\Blocks{4}{(1,1),(2,2),(3,3),(4,4)}

\end{document}

This produces:

enter image description here

Note that I have given \Blocks an optional argument (using \NewDocumentCommand from xparse). By default, \Block will draw a square grid of the specified size, however, the optional argument allows you to draw rectangular grids. As an example, in the code above

\Blocks[3]{2}{(1,1),(2,2),(3,1)}

draws a grid with 2 rows and 3 columns.

EDIT

As requested, here are some more details as to how this works. The \Blocks macro takes three arguments:

  • the number of columns in the grid (optional: defaults to # rows)
  • the number of rows in the grid
  • a comma separated list of cells to be shaded

These arguments are specified by the { omm } in

\NewDocumentCommand\Blocks{ omm }{...} 

Here, o means optional argument, which is enclosed by [...] when it is given, and m means mandatory argument, which must always be specified even only as {} (so \Blocks{3}{} would draw an "empty" 3x3 grid). See the xparse manual for more details.

If there are r rows and c columns then the grid is drawn using Cartesian coordinates from the origin (0,0) to (r,c) and the cell labelled (x,y) has vertices (x-1,y-1), (x-1,y), (x,y-1) and (x,y). This is the easiest and most natural labeling to use because tikz uses Cartesian coordinates. If, instead, you want to use the half integer lattice the cleanest way to do this is to add scale=0.5 to the options for the tikzpicture environment (see below).

The following lines from the top of the \Blocks macro:

     \def\rowsForBlocks{#2}
     \IfNoValueTF{#1}{\def\colsForBlocks{\rowsForBlocks}}
                     {\def\colsForBlocks{#1}}

sets the number of rows and columns from the arguments to \Blocks, which are then used to draw the grid. The \IfNoValueTF{#1} checks to see whether or not the optional first argument is given -- the TF stands for True or False, indicating \def\colsForBlocks{\rowsForBlocks} is executed when the optional argument #1 is not given and \def\colsForBlocks{#1} is executed when it is (again, see the xparse manual for details). After this, the lines

     % draw the grid
     \foreach \row in {0,...,\colsForBlocks} {
        \draw[gray](\row,0)-- ++(0,\rowsForBlocks);
     }
     \foreach \col in {0,...,\rowsForBlocks} {
        \draw[gray](0,\col)-- ++(\colsForBlocks,0);
     }

simply draws the corresponding grid by drawing the lines that make up its rows and columns. Finally, the lines

     % shade the specified boxes
     \foreach \cell in {#3} {
         \draw[fill=black!50] \cell rectangle ++ (-1,-1);
     }

loop over the third argument, #3, which is a comma separated list of (x,y) coordinates for the shaded cells. The ++ (-1,-1) says that the cell corresponding to (x,y) is equal to the rectangle with "outside corners" corresponding to the Cartesian coordinates (x,y) and (x,y)+(-1,-1)=(x-1,y-1).

In the OP, the square corresponding to (x,y) has "outer corners" with Cartesian coordinates (x,-y) and (x,-y-1). To achieve this is a little cumbersome using tikz but one way to do this is by changing the syntax so that coordinates are separated by braces. For example,

\Blocks[3]{2}{{0,0},{1,0},{2,0}}

The advantage of using braces is that it allows us to extract x and y from a \cell by treating {x,y} as a pgf array, which we can do using:

\pgfmathsetmacro\x{{\cell}[0]}% extract x coordinate
\pgfmathsetmacro\y{{\cell}[1]}% extract y coordinate

(Note that pgf arrays are 0-based.) Once we have \x and \y we can draw the OP's cell using:

\draw[fill=black!50] (\x,-\y) rectangle ++ (1,-1);

Strictly speaking we should use

\draw[fill=black!50] (0.5*\x,-0.5*\y) rectangle ++ (0.5,-0.5);

but, as shown below, it is "nicer" to do this by rescaling the environment (if there was text in these diagrams this might not be the best way but, as currently stated, this is not the case).

In the comments the OP said that it would be good if the macro could also change the height of the cells as well. We can do this by adding a second optional argument to \Blocks. To be able to use the two optional arguments independently we should delimit the second optional argument by something other than [...]. I normally use <...> when I want two optional arguments, so the definition of \Blocks will now look like:

 \NewDocumentCommand\Blocks{ D<>{0.5} o m m }{...}

The D<>{0.5} says that there is a second optional argument delimited by <...> with a default value of 0.5 -- this will become scale=#1 in the tikzpicture environment. So, by default, the scale will be 50% which gives the half integer lattice points used by the OP. Putting this together, here is a new version of the code using the OP's coordinates for the cells:

\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}

\NewDocumentCommand\Blocks{ D<>{0.5} o m m }
{% usage \Blocks<scale>[cols]{rows}{list of shaded cells}
   \begin{tikzpicture}[scale=#1]
     \def\rowsForBlocks{#3}
     \IfNoValueTF{#2}{\def\colsForBlocks{\rowsForBlocks}}
                     {\def\colsForBlocks{#2}}
     % draw the grid
     \foreach \row in {0,...,\colsForBlocks} {
        \draw[gray](\row,0)-- ++(0,-\rowsForBlocks);
     }
     \foreach \col in {0,...,\rowsForBlocks} {
        \draw[gray](0,-\col)-- ++(\colsForBlocks,0);
     }
     % shade the specified boxes
     \foreach \cell in {#4} {
       \pgfmathsetmacro\x{{\cell}[0]}% extract x coordinate from cell
       \pgfmathsetmacro\y{{\cell}[1]}% extract y coordinate from cell
       \draw[fill=black!50] (\x,-\y) rectangle ++ (1,-1);
     }
   \end{tikzpicture}
}

\begin{document}

\Blocks<1>{2}{{0,0}}                     % scale = 1 = 1.0 = 100%
\Blocks[3]{2}{{0,0},{1,0},{2,0}}         % default scale = 0.5 = 50%
\Blocks<0.3>[3]{2}{{0,0},{1,1},{2,0}}    % scale = 0.3 = 30%
\Blocks<0.1>{4}{{0,0},{1,1},{2,2},{3,3}} % scale = 0.1 = 10 %

\end{document}

Here is the new output:

enter image description here

  • @egreg Thanks, you are right of course. I should use xparse but I was being lazy! I'll fix it. – Andrew Jul 20 '17 at 6:26
  • Nice. I have to confess that, due to my ignorance, \detokenize is not in my standard toolbox... – Andrew Jul 20 '17 at 6:30
  • 1
    See What does ifx\\#1\\ stand for? for other ways to distinguish between empty or not empty argument. – egreg Jul 20 '17 at 6:33
  • Well thank you, I really appreciate this and will definitley use this! – WodkaRHR Jul 20 '17 at 9:11
  • I do not quite get everything this macro does - not even all the syntax. Could you either explain or modify, so that a cell's width and height are 0.5 (or even definable in the macro itself) and also that the grid is ordered the way a computer would draw it, meaning the coordinates would refer to: [0,0] [1,0] [2,0] \newline [1,0] [1,1] [2,1] .... – WodkaRHR Jul 20 '17 at 12:26
6

You have to enable coordinate computations: see section 13.5 in the manual of TikZ/PGF.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\newcommand{\cell}[2]{%
  \fill [black!50] ($0.5*(#1,-#2)$) rectangle ($0.5*(#1,-#2)+(0.5,0.5)$)%
}

\begin{document}

\begin{tikzpicture}
  \cell{1}{1};
  \cell{3}{2};
  \cell{0}{3};
  \draw[step=0.5cm,gray,very thin] (2.0,-2.0) grid (0.0, 0.0);
\end{tikzpicture}

\end{document}

enter image description here

  • 3
    This solves the OP's problem but really mangles tikz! :) Instead of loading the calc library it is enough to use \fill [black!50] (0.5*#1,-0.5*#2) rectangle ++(0.5,0.5) in the definition of \cell. – Andrew Jul 20 '17 at 0:22
5

If you are drawing many of these grids, it may be convenient to specify only two things; grid size (\N below) and filled cells as (col,row). The following code uses only these two parameters to draw two grids of size 4 and 10 and fill some chosen cells. Further defining a new command can be more convenient for this.

Note:

TikZ grids are known to be not very accurate, (see my answer to a question on TikZ grids ), so, mixing grids with absolute coordinates can lead to errors. This is why I manually draw the grid using a loop.

\documentclass{article}
\usepackage{tikz}
\begin{document}

\def\N{4}                            % (1) grid size
\begin{tikzpicture}[very thin]
  \foreach \n in {0,...,\N}
    \draw (0,.5*\n)--(.5*\N,.5*\n) (.5*\n,0)--(.5*\n,.5*\N);
  \foreach \c in {(1,2),(2,4),(4,3)} % (2) fill cells (col,row)
    \draw[fill=black!50,scale=.5] \c rectangle ++(-1,-1);
\end{tikzpicture}

\bigskip

\def\N{10}
\begin{tikzpicture}[very thin]
  \foreach \n in {0,...,\N}
    \draw (0,.5*\n)--(.5*\N,.5*\n) (.5*\n,0)--(.5*\n,.5*\N);
  \foreach \c in {(2,5),(4,2),(8,6)}
    \draw[fill=black,scale=.5] \c rectangle ++(-1,-1);
\end{tikzpicture}

\end{document}

enter image description here

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.