6

I have some code to automatically draw the grid for a polyomino (a shape constructed from edge-to-edge connected squares) based on the user input of its binary matrix. The binary matrix is for the bounding rectangle of the polyomino where 1's represent filled squares of the polyomino, while 0's represent empty squares (i.e., squares not belonging to the polyomino). I would like to draw the boundary of the polyomino with lines that are thicker than the grid lines. Ideally I would like the line thickness of both grid lines and boundary lines to be adjustable (the grid line width already is). See the code below and corresponding figure.

\documentclass{article}
\usepackage{tikz}
\usepackage{xcolor}

\newcommand{\drawPolyomino}[3]{ \begin{tikzpicture}[scale=#2]

    % Draw the squares
    \foreach[count=\jR from 0] \row in {#1} {
        \pgfmathtruncatemacro\j{abs(\jR - 3)}
        \foreach[count=\i from 0] \cell in \row {
            \ifnum\cell=1
                \draw[#3] (\i, \j) rectangle ++(1,1);
            \fi
        }
    }
\end{tikzpicture}

}

\begin{document}

\begin{figure} \centering \drawPolyomino{{0,1,0,0,0},{1,1,1,1,0},{0,0,1,1,1},{0,0,0,1,0}}{1.0}{thin} % Input the polyomino, followed by arguments for scale and line width respectively. \caption{A polyomino constructed from an input binary matrix.} \end{figure}

\end{document}

Grid for a polyomino

Marcus
  • 367

2 Answers2

9

Storing the matrix you provide makes it much easier to retrieve them later in a loop. (It's basically a two-dimensional array now.)

That way, one could check for each drawn rectangle whether another rectangle is around it and if not (or the array is basically out of bounds → check against \relax) a border segment is drawn. (This also means that storing that matrix, i.e. using the store matrix key, should always be done in a scope where no other store matrix key is used. Otherwise the \relax cheat won't work anymore.)

This does not construct a closed path. Putting the whole construct on one path allows us to fill/shade the whole shape without actually having to use the outline. It also helps putting the edges on top of the shape.

This is using the same matrix storage key as in another answer which might interpret rows and columns differently than you (?). Somewhere along I got a bit confused but the polyomino/picture key with its yscale=-1 seems to have that fixed.

Code

\documentclass[tikz]{standalone}
\tikzset{
  polyomino/rect/.style={thin},
  polyomino/border/.style={very thick, line cap=rect},
  polyomino/picture/.style={yscale=-1},
  polyomino/.code=\pgfqkeys{/tikz/polyomino}{#1},
  store matrix/.style={
    /utils/exec=\def\listrow{0},
    /utils/rows/.style={
      /utils/exec=\def\listcol{0}%
                  \edef\listrow{\the\numexpr\listrow+1\relax},
      /utils/cols/.estyle={
        /utils/exec=\edef\noexpand\listcol{\noexpand\the\numexpr\noexpand\listcol+1\relax},
        /tikz/matrix/storage \listrow-\noexpand\listcol/.initial={####1}
      },
      /utils/cols/.list={##1},
    },
    /utils/rows/.list={#1}
  },
  matrix check/.code args={#1:#2=#3}{%
    \pgfkeysgetvalue{/tikz/matrix/storage \pgfinteval{#1}-\pgfinteval{#2}}{#3}%
    \ifx#3\relax \def#3{0}\fi},
}

\newcommand{\drawPolyomino}[2][]{ \begin{tikzpicture}[store matrix={#2},polyomino/picture,polyomino={#1}] % Draw the squares \draw[polyomino/rect] foreach \col in {1,...,\listcol}{ foreach \row in {1,...,\listrow}{ [matrix check/.list={\row:\col=\cell, \row:\col-1=\cellPrev, \row:\col+1=\cellNext, \row+1:\col=\cellAbove, \row-1:\col=\cellBelow}] \ifnum\cell=1 (\col , \row ) rectangle +(1,1) \ifnum\cellPrev=0 (\col , \row ) edge[polyomino/border] +(up:1) \fi \ifnum\cellAbove=0 (\col , \row+1) edge[polyomino/border] +(right:1)\fi \ifnum\cellBelow=0 (\col , \row ) edge[polyomino/border] +(right:1)\fi \ifnum\cellNext=0 (\col+1, \row ) edge[polyomino/border] +(up:1) \fi \fi }}; \end{tikzpicture}}

\begin{document} \drawPolyomino{ {0,1,0,0,0}, {1,1,1,1,0}, {0,0,1,1,1}, {0,0,0,1,0} } \drawPolyomino[ rect/.append style={draw=none, top color=green!50, bottom color=blue!50}, border/.append style={ultra thick, draw=red, dash=on 3mm off 4mm phase 0mm}]{ {0,1,0,0,0}, {1,1,1,1,0}, {0,0,1,1,1}, {0,0,0,1,0} } \end{document}

Output

enter image description here enter image description here

Qrrbrbirlbel
  • 119,821
  • Wow, this looks amazing. Unfortunately when I run Latex I get the error message: ! Illegal parameter number in definition of \drawPolyomino. 2 l.44 \end{tikzpicture}} – Marcus Mar 28 '23 at 16:25
  • @Marcus You're right. There were actually two mistakes that were introduced when copying the document over. I've fixed them and implemented the whole shape as one path (which will consist of many rectangles touching each other) which alows you to use shading for the whole diagram without having to use the whole border as one path. – Qrrbrbirlbel Mar 28 '23 at 16:35
  • @Marcus works for me. – user691586 Mar 28 '23 at 16:41
  • I've tried refreshing the screen and copied and compiled twice. I get the same error. Is it possible you have some non-standard packages on your system that it relies on? What else can I do. Your code is perfect if I can get it to work! – Marcus Mar 28 '23 at 16:45
  • I updated it and things work. Thank you so much! I'll check now to see if this does exactly what I need. – Marcus Mar 28 '23 at 18:57
  • So how do I now use this code to place a given figure in a document? When I place the \drawPolyomino command within a figure environment it creates 2 separate cropped windows (one for the picture and one for the caption). I'm guessing I need to put the \begin{tikzpicture} ... \end{tikzpicture} environment inside the figure environment but I'm not sure how to do that. – Marcus Mar 28 '23 at 19:07
  • @Marcus It should work just as before: \begin{figure} \centering \drawPolyomino{{0,1,0,0,0}, {1,1,1,1,0}, {0,0,1,1,1}, {0,0,0,1,0}} \caption{A polyomino constructed from an input binary matrix.} \end{figure}. Don't forget to use \usepackage{tikz} in the preamble (which is done by the standalone class in my answer). – Qrrbrbirlbel Mar 28 '23 at 19:10
  • Got it! It was only the Standalone that made it look funny. Thanks again! This is really helpful. – Marcus Mar 28 '23 at 19:14
  • Just a quick question. I don't know if this is how you would do it, but in order to add any additional Tikzpicture components, e.g. nodes, I changed the number of arguments to 3 and then added '{#3};' within the \begin{tikzpicture} ... \end{tikzpicture} environment , so I have an extra option of adding extra stuff with the last {}. – Marcus Mar 29 '23 at 11:35
  • 1
    @Marcus You could do that. You could also create your own environment instead of a macro or you replace the tikzpicture environment with a scope environment inside the definition of \drawPolyomino and use that only *inside* atikzpicture` environment where you can do something before or after. – Qrrbrbirlbel Mar 29 '23 at 12:41
3

You could draw it twice and use [fill=white] to erase the thick borders in the interior. Note that lines are centered on the edges, so you will lose nearly half the outer thickness while filling.

\documentclass{article}
\usepackage{xcolor}
\usepackage{tikz}

\newcommand{\drawPolyomino}[2]{% #1=binary array, #2=scale \begin{tikzpicture}[scale=#2]

    % Draw the squares
    \foreach[count=\jR from 0] \row in {#1} {
        \pgfmathtruncatemacro\j{abs(\jR - 3)}
        \foreach[count=\i from 0] \cell in \row {
            \ifnum\cell=1
                \draw[very thick] (\i, \j) rectangle ++(1,1);
            \fi
        }
    }
    \foreach[count=\jR from 0] \row in {#1} {
        \pgfmathtruncatemacro\j{abs(\jR - 3)}
        \foreach[count=\i from 0] \cell in \row {
            \ifnum\cell=1
                \draw[thin, fill=white] (\i, \j) rectangle ++(1,1);
            \fi
        }
    }
\end{tikzpicture}%

}

\begin{document}

\begin{figure} \centering \drawPolyomino{{0,1,0,0,0},{1,1,1,1,0},{0,0,1,1,1},{0,0,0,1,0}}{1.0} % Input the polyomino, followed by arguments for scale \caption{A polyomino constructed from an input binary matrix.} \end{figure}

\end{document}

demo

John Kormylo
  • 79,712
  • 3
  • 50
  • 120
  • 1
    Similar to my answer using edges and just one path this can be done at once: \newcommand{\drawPolyomino}[2]{\tikz[scale=#2]\draw[very thick]foreach[count=\jR from0,evaluate=\jR as\j using abs(\jR-3)]\row in{#1}{foreach[count=\i from0]\cell in\row{\ifnum\cell=1 (\i,\j)rectangle+(1,1)(\i,\j)edge[thin,fill=white,to path=rectangle(\tikztotarget)]+(1,1)\fi}};} – Qrrbrbirlbel Mar 28 '23 at 19:05