Drawing arbitrary shaped grids in TikZ

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{matrix}

\begin{document}
\begin{tikzpicture}[line cap=rect]

\tikzset{
  square matrix/.style={
    matrix of nodes,
    column sep=-\pgflinewidth, row sep=-\pgflinewidth,
    nodes={
      rectangle,
      draw=gray,
      minimum height=#1,
      anchor=center,
      align=center,
      text width=#1,
      text height=2ex,
      text depth=0.5ex,
      inner sep=0pt,
      outer sep=0pt,
    }
  },
  square matrix/.default=1.2em
}

\matrix(m)[square matrix]
{
&&&&                      1 & 2       & 2       & 1 \\
\ldots & 1 & 1    & 1   & 2 & $\star$ & $\star$ & 3       & 1 \\
\ldots & 1 & $x'$ & $x$ & 2 & $x'$    & $\star$ & $\star$ & 2 \\
\ldots & 1 & 1    & 1   & 1 & 2       & $x$     & $\star$ & 2 \\
&&&&&                         1       & 2       & 2       & 1 \\
&&&&&                         1       & $x'$    & 1 \\
&&&&&                         1       & $x$     & 1 \\
&&&&&                         1       & 1       & 1 \\
&&&&&                         \vdots  & \vdots  & \vdots \\
};
\draw[white,very thick] (m-9-6.south west) -- (m-9-8.south east);
\draw[white,very thick] (m-2-1.north west) -- (m-4-1.south west);
\draw[thick] (m-2-1.north west) -- (m-2-4.north east) -- (m-1-5.north west) -- (m-1-8.north east) --
            (m-1-8.south east) -- (m-2-9.north east) -- (m-5-9.south east) -- (m-5-9.south west) --
            (m-9-8.south east);
\draw[thick] (m-4-1.south west) -- (m-4-5.south east) -- (m-9-6.south west);
\node[anchor=south] (x) at (m-2-1.north east) {X};
\draw[->] (x.east) --  +(2em,0);
\node[anchor=west] (xx) at (m-7-8.east) {X};
\draw[->] (xx.south) --  +(0,-2em);
\end{tikzpicture}
\end{document}

enter image description here


This is a alternative answer; it seems you want to do cellular automata. I like this following method for grids. It's very clear code and good looking, I think, but at once it gives you maximum control over every aspect and boundary combination. It's nicely combined with other methods. It does not require a matrix environment.

1)

You'll have to make a folder. You have 6 different tiles I see; you need to make 6 + 1 = 7 .tex document in that case.

Each of the tile documents has code of of this kind:

\documentclass{standalone}%
\usepackage[usenames,dvipsnames]{xcolor}% this must be loaded prior \usepackage{tikz}
% general package
\usepackage{amsfonts,amsmath}%
\usepackage{tikz}%
% specific package

\begin{document}
\begin{tikzpicture}[auto]
\tikzstyle{every node}=[font=\Large]

\draw [-,line width=1pt,red,solid] (0,0) edge (0,1);
\draw [-,line width=1pt,red,solid] (0,1) edge (1,1);
\draw[dotted](0.5,0.5) node {$x$};
\draw [-,line width=1pt,black,solid] (1,1) edge (1,0);
\draw [-,line width=1pt,black,solid] (1,0) edge (0,0);

\end{tikzpicture}
\end{document}

Simply replace the line parameters and the text inside $ ... $

Lets suppose you make two tiles, one called anicon.tex, the other ohicon.tex and compile them

The final main document is where you work:

\documentclass{article}%
\usepackage[usenames,dvipsnames]{xcolor}%
% general package
\usepackage{amsfonts,amsmath}%
\usepackage{tikz}%
% specific package
\usepackage{float}

% a grid point
\def\gp#1#2{\node at (#1) {\includegraphics{\csname #2\endcsname}};}

\begin{document}
\begin{figure}[H]
\begin{center}
\begin{tikzpicture}[auto]

            \gp{0,4}{R}
            \gp{0,3}{x}\gp{1,3}{R}
\gp{-1,2}{R}\gp{0,2}{x}
            \gp{0,1}{R}\gp{1,1}{R}

\draw[->,thick] (-1,0) -- (2,0);
\draw[solid](-1.5,0) node {$\textbf{F}$};
\draw[->,thick] (-1,3) -- (-2,3);
\draw[solid](-2.5,3) node {$\textbf{E}$};

\end{tikzpicture}
\end{center}
\caption{FIGURE CAPTION GOES HERE}
    \label{fig:FIGURE NAME GOES HERE}
\end{figure}
\end{document}

You can very rapidly construct arbitrary and good looking automata diagrams

You can change color to white of any box side to make it transparent (or make a highlighted side) and recompile with a click and then a click in your main document. All boxes automatically update at once, so you don't have to change them manually if you decide to change a function rule.

resulticon

2)

If you want to, you can further save filesize and reduce number of files to 2 documents, and also reduce the amount you need to type to the minimum possible.

In this example, we have 4 different functions = 4 different boxes, but they are side by side in one file. Our texture file is named iconset and we crop parts of it and rearrange them in our automata file:

% iconset
\documentclass{standalone}%
\usepackage[usenames,dvipsnames]{xcolor}
% general package
\usepackage{amsfonts,amsmath}%
\usepackage{tikz}%
% specific package

\begin{document}
    \begin{tikzpicture}[auto]
    \tikzstyle{every node}=[font=\large]

\draw [-,line width=1pt,red,solid] (0,0) edge (0,1);
\draw [-,line width=1pt,red,solid] (0,1) edge (1,1);
\draw[dotted](0.5,0.5) node {$X$};
\draw [-,line width=1pt,black,solid] (1,1) edge (1,0);
\draw [-,line width=1pt,black,solid] (1,0) edge (0,0);

\draw [-,line width=1pt,red,solid] (0,1) edge (0,2);
\draw [-,line width=1pt,red,solid] (0,2) edge (1,2);
\draw[dotted](0.5,1.5) node {$\textbf{a}$};
\draw [-,line width=1pt,black,solid] (1,2) edge (1,1);
\draw [-,line width=1pt,black,solid] (1,1) edge (0,1);

\draw [-,line width=1pt,red,solid] (-1,1) edge (-1,2);
\draw [-,line width=1pt,red,solid] (-1,2) edge (0,2);
\draw[dotted](-0.5,1.5) node {$\textbf{b}$};
\draw [-,line width=1pt,black,solid] (0,2) edge (0,1);
\draw [-,line width=1pt,black,solid] (0,1) edge (-1,1);

\draw [-,line width=1pt,red,solid] (-1,0) edge (-1,1);
\draw [-,line width=1pt,red,solid] (-1,1) edge (0,1);
\draw[dotted](-0.5,0.5) node {$Y$};
\draw [-,line width=1pt,black,solid] (0,1) edge (0,0);
\draw [-,line width=1pt,black,solid] (0,0) edge (-1,0);

    \end{tikzpicture}
\end{document}

Our work figure is this file

% automata
\documentclass{article}%
\usepackage[usenames,dvipsnames]{xcolor}%
% general package
\usepackage{amsfonts,amsmath}%
\usepackage{tikz}%
% specific package
\usepackage{float}

% a grid point
\def\ai#1{\node at (#1) {%
    \includegraphics[trim = 0mm 10.1mm 10.1mm 0mm, clip]{iconset}};}
\def\bi#1{\node at (#1) {%
    \includegraphics[trim = 10.1mm 10.1mm 0mm 0mm, clip]{iconset}};}
\def\Yi#1{\node at (#1) {%
    \includegraphics[trim = 0mm 0mm 10.1mm 10.1mm, clip]{iconset}};}
\def\Xi#1{\node at (#1) {%
    \includegraphics[trim = 10.1mm 0mm 0mm 10.1mm, clip]{iconset}};}

\begin{document}
\begin{figure}[H]
\begin{center}
\begin{tikzpicture}[auto]

          \Yi{0,4}
          \ai{0,3} \Xi{1,3}
\Xi{-1,2} \bi{0,2}
          \Xi{0,1} \bi{1,1}

\draw[->,thick] (-1,0) -- (2,0);
\draw[solid](-1.5,0) node {$\textbf{F}$};
\draw[->,thick] (-1,3) -- (-2,3);
\draw[solid](-2.5,3) node {$\textbf{E}$};

\end{tikzpicture}
\end{center}
\caption{FIGURE CAPTION GOES HERE}
    \label{fig:FIGURE NAME GOES HERE}
\end{figure}
\end{document}

This method is typically used in a game engine because it is the most processor efficient, requires least memory, has smallest filesize, and makes for the cleanest cost.


I would like to present you my experiment. I have noticed this fine arbitrary shape problem when I read about Hexagon Mesh solved in D3js, http://bl.ocks.org/mbostock/5249328. In this example, I tried to draw major border (used versus unused table cells), group cells according to their shown text, layer them and try one or two other things along the way. I am generating a TikZ picture from the original data in Lua which is loaded in TeX afterwards. I define styles at a TikZ level.

I use four basic layers (that's also an order of appearance): cells with text+fills, basic lines, lines forming groups and border lines. I define input data (data) and form several text groups (types). Dots (\ldots and \vdots) and unassigned texts (Icu!) are processed differently. Dots have one end of the cell opened, the unassigned text is typesetted before any other material. For layering I use ordering of typeset material only. I highlighted the last text group by white colour.

After splitting data to rows/columns/cells I am checking relations among cells. After that I am able to generate individual TikZ nodes and lines with their styles. The key element are lines between the cells. To be able to draw a perfect line in squares I am using correction of 0.5\pgflinewidth. The generated code is rather large, but I wanted to try different approach.

This is the mal-arbitrary.lua file, which we process by texlua mal-arbitrary.lua command. The mal-result.tikz file is generated along the way which is loaded by TeX in mal-basic.tex. I enclose both the files and a preview of the resulting table.

data=[[
&&&&                      1 & 2       & 2       & 1 \\
\ldots & 1 & 1    & 1   & 2 & $\star$ & $\star$ & 3       & 1 \\
\ldots & 1 & $x'$ & $x$ & 2 & $x'$    & $\star$ & $\star$ & 2 \\
\ldots & 1 & 1    & 1   & 1 & 2       & $x$     & $\star$ & 2 \\
&&&&&                         Icu!       & 2       & 2       & 1 \\
&&&&&                         1       & $x'$    & 1 \\
&&&&&                         1       & $x$     & 1 \\
&&&&&                         1       & 1       & 1 \\
&&&&&                         \vdots  & \vdots  & \vdots \\]]
--print(data)
types={ {"\\ldots"}, {"\\vdots"}, {"1"}, {"2"}, {"3"}, {"$\\star$"}, {"$x$","$x'$"} }   


print("I'm generating a TikZ file...")
-- Parsing data
data=string.gsub(data, "%s+", "")
data=string.gsub(data, "\\+$", "") -- delete \\ at the end of file
data=data.."\\\\" -- add \\ for later use
row=0
malmatrix={}
string.gsub(data, "(.-)\\\\", function (s)
   s=s.."&"
   --print(s)
   row=row+1; column=0
   string.gsub(s, "(.-)&", function (t)
      --io.write(t)
      column=column+1
      label=column.."-"..row
      if t~="" then malmatrix[label]={column,row,t} end
   end)
   --print(); print()
   end)

-- testing specific cell on-the-fly (ineffective?)
function testtype(cell)
for typesrow,maltype in pairs(types) do
for _,malcell in pairs(maltype) do
if malcell==cell then return typesrow end
end -- for
end -- for
return 100 -- not present
end -- function testype

function savefile()
tobesaved="\\draw["..bonus.."] ($("..k.."-"..l.."."..fromcorner..")+"..correctiona.."$) -- ($("..k.."-"..l.."."..tocorner..")+"..correctionb.."$);\n"
end

function testme(tempk, templ)
k=tempk; l=templ
for counter,pos in pairs{ {-1,0},{1,0},{0,-1},{0,1} } do
newx=k+pos[1]
newy=l+pos[2]
tested=malmatrix[newx.."-"..newy] -- watch for neighbours in four directions (west, east, south, north)
--print(k,l, newx,newy)
--if tested then print("",tested,tested[1], tested[2], tested[3]) end
celltype=testtype(malmatrix[k.."-"..l][3])

bonus="border"
if (celltype==1 and counter<=2) or (celltype==2 and counter>=3) then bonus="malwhite" end

veryextra=nil
if tested then 
   celltypeB=testtype(tested[3])  
if celltype~=celltypeB and celltype>2 then bonus="extra"..celltype  else bonus="commonline" end
else 
if celltype>2 then veryextra="extra"..celltype end
end

--if celltype=="x" then bonus="border" end

-- not effective, but it is doing its job
correctiona="(0,0.5\\pgflinewidth)"
correctionb="(0,-0.5\\pgflinewidth)"
if counter==1 then fromcorner="north west"; tocorner="south west" end -- right
if counter==2 then fromcorner="north east"; tocorner="south east" end -- left
   if counter>2 then 
   correctiona="(0.5\\pgflinewidth,0)"
   correctionb="(-0.5\\pgflinewidth,0)"
   end
if counter==3 then fromcorner="south east"; tocorner="south west" end -- below
if counter==4 then fromcorner="north east"; tocorner="north west" end -- above
savefile()

if not tested then -- no, there is an empty space
-- major border
forlater=forlater..tobesaved
else
if bonus~="commonline" then
forlatercommon=forlatercommon..tobesaved
else
saveme:write(tobesaved)
end -- if
end -- if
if veryextra then bonus=veryextra; savefile() end

end -- for
end -- of function testme


saveme=io.open("mal-result.tikz","w")
-- correction, flipping values
oldmatrix=malmatrix
malmatrix={}
for _,hodnota in pairs(oldmatrix) do
newvalue=row-oldmatrix[hodnota[1].."-"..hodnota[2]][2]+1
malmatrix[hodnota[1].."-"..newvalue]={hodnota[1],newvalue,hodnota[3]}
end
oldmatrix=nil

-- drawing nodes with fills, drawing is not necessary
for _,hodnota in pairs(malmatrix) do
saveme:write("\\node["..testtype(malmatrix[hodnota[1].."-"..hodnota[2]][3]).."] ("..hodnota[1].."-"..hodnota[2]..") at ("..hodnota[1]..","..hodnota[2]..") {"..hodnota[3].."};\n")
end

-- drawing inner lines
saveme:write("\n")
forlater=""
forlatercommon=""

-- unassigned nodes to be processed first, please
for _,hodnota in pairs(malmatrix) do
if testtype(hodnota[3])==100 then 
    testme(hodnota[1], hodnota[2])
    end -- if
end -- of for, malmatrix

-- simple simulation/handling of layering node types
for layer=1,#types do
for _,hodnota in pairs(malmatrix) do
if testtype(hodnota[3])==layer then -- that's not effective, it is computed on-the-fly several times
    testme(hodnota[1], hodnota[2])
    end -- if
end -- of for, malmatrix
end -- of for, layer

-- drawing inner lines
saveme:write("\n"..forlatercommon)

-- drawing borders
saveme:write("\n"..forlater)

-- finish the job
saveme:close()

This is the TeX file processed by any major LaTeX engine, e.g. lualatex mal-basic.tex:

\documentclass[a4paper]{article}
\pagestyle{empty}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\def\malwidth{7.7mm}
\def\mallinewidth{3pt}
\def\mallinewidthb{1.5pt}
\begin{tikzpicture}[every node/.style={minimum width=\malwidth, minimum height=\malwidth, draw=none, text height=2ex, text depth=0.5ex}, x=\malwidth, y=\malwidth, inner sep=0pt, outer sep=0pt, 
   border/.style={line width=\mallinewidth, red, draw},
   malwhite/.style={line width=\mallinewidth, white},
   commonline/.style={draw=brown}, % try none
   100/.style={fill=brown},
   1/.style={fill=none},
   2/.style={fill=none},
   3/.style={fill=cyan},
   4/.style={fill=magenta},
   5/.style={fill=orange},
   6/.style={fill=yellow},
   7/.style={fill=green},
extra100/.style={line width=\mallinewidthb},
extra3/.style={line width=\mallinewidthb},
extra4/.style={line width=\mallinewidthb},
extra5/.style={line width=\mallinewidthb},
extra6/.style={line width=\mallinewidthb},
extra7/.style={line width=\mallinewidthb,white},
   ]
\input mal-result.tikz
\end{tikzpicture}
\end{document}

Arbitrary shape: a result of our efforts