How can I set the background color of the rows and columns of a matrix node in Tikz?
I misunderstood the question with my first answer. I'm going to leave that up there anyway, but now that I (think I) understand the question, here's a different answer.
I'll put the picture first:
Here's the code:
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{matrix,fit,calc}
\pgfdeclarelayer{back}
\pgfsetlayers{back,main}
\begin{document}
\begin{tikzpicture}
\matrix [matrix of nodes, row sep=2mm, column sep=1mm, nodes={draw, thick, circle, inner sep=1pt}] (ma)
{ & 1 & &[2mm]|[gray]|1\\
& & 2 &|[gray]|2\\
|[gray]|2 & & &|[gray]|2\\[4mm]
3 & & & 3\\
};
\node[inner sep=0pt,fit=(ma-1-2) (ma-1-4)] (ma-row-1) {};
\node[inner sep=0pt,fit=(ma-2-3) (ma-2-4)] (ma-row-2) {};
\node[inner sep=0pt,fit=(ma-3-1) (ma-3-4)] (ma-row-3) {};
\node[inner sep=0pt,fit=(ma-4-1) (ma-4-4)] (ma-row-4) {};
\node[inner sep=0pt,fit=(ma-3-1) (ma-4-1)] (ma-col-1) {};
\node[inner sep=0pt,fit=(ma-1-2)] (ma-col-2) {};
\node[inner sep=0pt,fit=(ma-2-3)] (ma-col-3) {};
\node[inner sep=0pt,fit=(ma-1-4) (ma-2-4) (ma-3-4) (ma-4-4)] (ma-col-4) {};
\coordinate (ma-col-edge-1) at (ma.west);
\coordinate (ma-col-edge-2) at ($(ma-col-1.west)!.5!(ma-col-2.east)$);
\coordinate (ma-col-edge-3) at ($(ma-col-2.west)!.5!(ma-col-3.east)$);
\coordinate (ma-col-edge-4) at ($(ma-col-3.west)!.5!(ma-col-4.east)$);
\coordinate (ma-col-edge-5) at (ma.east);
\coordinate (ma-row-edge-1) at (ma.north);
\coordinate (ma-row-edge-2) at ($(ma-row-1.south)!.5!(ma-row-2.north)$);
\coordinate (ma-row-edge-3) at ($(ma-row-2.south)!.5!(ma-row-3.north)$);
\coordinate (ma-row-edge-4) at ($(ma-row-3.south)!.5!(ma-row-4.north)$);
\coordinate (ma-row-edge-5) at (ma.south);
\begin{pgfonlayer}{back}
\foreach \i in {1,...,4}
\foreach \j in {1,...,4} {
\pgfmathparse{Mod(\i + \j,2) ? "red" : "blue"}
\colorlet{sqbg}{\pgfmathresult}
\pgfmathparse{int(\i+1)}
\edef\ii{\pgfmathresult}
\pgfmathparse{int(\j+1)}
\edef\jj{\pgfmathresult}
\fill[sqbg] (ma-col-edge-\i |- ma-row-edge-\j) rectangle (ma-col-edge-\ii |- ma-row-edge-\jj);
}
\end{pgfonlayer}
\end{tikzpicture}
\end{document}
(It is probably longer than it needs to be.) The problem is that without digging deep in to the internals of PGF, it's difficult (if not impossible) to know what size the matrix is going to be until it is all laid out, making it difficult to put in the backgrounds as each node is put in place. So we don't. We wait until the matrix has been computed and put in the backgrounds afterwards. To ensure that they are genuine backgrounds, we use PGF's layering capabilities. This ensures that the backgrounds are put behind the matrix.
The next step is to get them the right size. We can't simply put a rectangle around each node for two reasons: not every square has a node, and the nodes don't necessarily fill their places. Nonetheless, the positions of the nodes still give us enough information to figure out where to put the rectangles so that they do line up. The way that we find this information is as follows. We start by putting a rectangle around all the nodes in a particular row or column (this uses the fit
library; this also has to be done manually as not all the entries in the matrix have nodes so not all the labels are used - if there were an easy way to test a label to see if it is associated to a node then this could be automated). Looking at a row, then the lower edge of the rectangle tells us the minimum coordinate of all the nodes in that row. Similarly, the upper edge of the rectangle containing the row below tells us the maximum coordinate of all the nodes in that row. We split the difference and plant a coordinate node at that point. This tells us where the horizontal line dividing these two rows should be. We repeat this for each row and column.
Then for each cell, we look at the dividing lines on each side and use those to fill a rectangle (of the appropriate colour). Say that we are in cell (2,3). Then we look at the coordinate between rows 1 and 2 and the coordinate between columns 2 and 3. We then intersect the horizontal line through the first with the vertical line through the second. This gives us the coordinate of the upper-left corner of that cell. In a similar fashion, we get the coordinate of the lower-right corner of that cell. This is enough to draw the rectangle. (Doing these calculations requires the calc
library.)
Update: Jake wanted a more automatic method, so here it is. The command \labelcells{matrix label}{num of rows}{num of cols}
puts a rectangular node on each matrix cell with label matrix label-cell-i-j
and size such that the rectangles exactly tile the matrix. These can then be used to paint the backgrounds, or whatever.
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{matrix,fit,calc}
\makeatletter
\newcommand{\labelcells}[3]{%
% #1 = matrix name
% #2 = number of rows
% #3 = number of columns
\foreach \labelcell@i in {1,...,#2} {
\def\labelcell@rownodes{}
\foreach \labelcell@j in {1,...,#3} {
\pgfutil@ifundefined{pgf@sh@ns@#1-\labelcell@i-\labelcell@j}{}{
\xdef\labelcell@rownodes{\labelcell@rownodes\space(#1-\labelcell@i-\labelcell@j)}
}
}
\node[inner sep=0pt,fit=\labelcell@rownodes] (#1-row-\labelcell@i) {};
}
\foreach \labelcell@j in {1,...,#3} {
\def\labelcell@colnodes{}
\foreach \labelcell@i in {1,...,#2} {
\pgfutil@ifundefined{pgf@sh@ns@#1-\labelcell@i-\labelcell@j}{}{
\xdef\labelcell@colnodes{\labelcell@colnodes\space(#1-\labelcell@i-\labelcell@j)}
}
}
\node[inner sep=0pt,fit=\labelcell@colnodes] (#1-col-\labelcell@j) {};
}
\coordinate (#1-col-edge-1) at (#1.west);
\foreach \labelcell@i in {2,...,#3} {
\pgfmathparse{int(\labelcell@i - 1)}
\edef\labelcell@j{\pgfmathresult}
\coordinate (#1-col-edge-\labelcell@i) at ($(#1-col-\[email protected])!.5!(#1-col-\[email protected])$);
}
\pgfmathparse{int(#3+1)}
\edef\labelcell@j{\pgfmathresult}
\coordinate (#1-col-edge-\labelcell@j) at (#1.east);
\coordinate (#1-row-edge-1) at (#1.north);
\foreach \labelcell@i in {2,...,#2} {
\pgfmathparse{int(\labelcell@i - 1)}
\edef\labelcell@j{\pgfmathresult}
\coordinate (#1-row-edge-\labelcell@i) at ($(#1-row-\[email protected])!.5!(#1-row-\[email protected])$);
}
\pgfmathparse{int(#2+1)}
\edef\labelcell@j{\pgfmathresult}
\coordinate (#1-row-edge-\labelcell@j) at (#1.south);
\foreach \labelcell@i in {1,...,#2}
\foreach \labelcell@j in {1,...,#3} {
\pgfmathparse{int(\labelcell@i+1)}
\edef\labelcell@ii{\pgfmathresult}
\pgfmathparse{int(\labelcell@j+1)}
\edef\labelcell@jj{\pgfmathresult}
\node[inner sep=0pt,fit=(#1-col-edge-\labelcell@i |- #1-row-edge-\labelcell@j) (#1-col-edge-\labelcell@ii |- #1-row-edge-\labelcell@jj)] (#1-cell-\labelcell@i-\labelcell@j) {};
}
}
\makeatother
\pgfdeclarelayer{back}
\pgfsetlayers{back,main}
\begin{document}
\begin{tikzpicture}
\matrix [matrix of nodes, row sep=2mm, column sep=1mm, nodes={draw, thick, circle, inner sep=1pt}] (ma)
{ & 1 & &[2mm]|[gray]|1\\
& & 2 &|[gray]|2\\
|[gray]|2 & & &|[gray]|2\\[4mm]
3 & & & 3\\
};
\labelcells{ma}{4}{4}
\begin{pgfonlayer}{back}
\foreach \i in {1,...,4}
\foreach \j in {1,...,4} {
\pgfmathparse{Mod(\i + \j,2) ? "red" : "blue"}
\colorlet{sqbg}{\pgfmathresult}
\fill[sqbg] (ma-cell-\i-\j.north west) rectangle (ma-cell-\i-\j.south east);
}
\end{pgfonlayer}
\end{tikzpicture}
\end{document}
You can use every odd column
, every even column
and their analogous for rows (described in Section 17.3.3 Cell Styles and Options of the pgf manual)
A little example:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{matrix}
\begin{document}
\begin{tikzpicture}
\matrix (mymtrx) [matrix of nodes,%
minimum size=10mm,%
every odd column/.style={nodes={fill=red!60}},%
every even column/.style={nodes={fill=blue!30}},%
execute at empty cell=\node {\vphantom{23}};%
]
{
8 & 327 & & -35 \\
65 & & & -3 \\
& 125 & 64 & 38 \\
};
\end{tikzpicture}
\end{document}
Here is an alternative solution, by explicitly comparing the x and y values of the node borders and finding the min/max for each row and column. The code is basically just a bunch of nested \foreach
loops (made unreadable by added ck@
's to avoid name clashes). The drawback of this method is that it doesn't take added whitespace (e.g. via \\[4mm]
) into account. It wouldn't be too hard to add that though (by comparing with xmin
/ymax
of the next column/row).
\documentclass{minimal}
\usepackage{tikz}
\usetikzlibrary{matrix}
\pgfdeclarelayer{back}
\pgfsetlayers{back,main}
\makeatletter
% draw a checkerboard in the background of a matrix
% #1: name of the matrix
% #2: rows in the matrix
% #3: columns in the matrix
% #4: row sep
% #5: column sep
% #6: first color
% #7: second color
\newcommand\checkermatrix[7]{
\def\ck@rows{#2}
\def\ck@cols{#3}
\begin{pgfonlayer}{back}
\foreach \ck@row in {1,...,\ck@rows} {
% find minimum and maximum y coordinate for the row
\pgfextracty\pgf@ya{\pgfpointanchor{#1}{north}}
\edef\ck@ymin{\the\pgf@ya}
\pgfextracty\pgf@ya{\pgfpointanchor{#1}{south}}
\edef\ck@ymax{\the\pgf@ya}
\foreach \ck@col in {1,...,\ck@cols} {
\pgfutil@ifundefined{pgf@sh@ns@#1-\ck@row-\ck@col}{}{
\pgfextracty\pgf@ya{\pgfpointanchor{#1-\ck@row-\ck@col}{south}}
\pgfmathparse{min(\ck@ymin,\the\pgf@ya)}
\xdef\ck@ymin{\pgfmathresult}
\pgfextracty\pgf@ya{\pgfpointanchor{#1-\ck@row-\ck@col}{north}}
\pgfmathparse{max(\ck@ymax,\the\pgf@ya)}
\xdef\ck@ymax{\pgfmathresult}
}
}
% adjust for row separation
\pgfmathsetmacro{\ck@ymin}{\ck@ymin - #4/2}
\pgfmathsetmacro{\ck@ymax}{\ck@ymax + #4/2}
% loop through nodes in the row
\foreach \ck@col in {1,...,\ck@cols} {
% find x coordinates of the boundary
\pgfextractx\pgf@xa{\pgfpointanchor{#1}{east}}
\edef\ck@xmin{\the\pgf@xa}
\pgfextractx\pgf@xa{\pgfpointanchor{#1}{west}}
\edef\ck@xmax{\the\pgf@xa}
\foreach \ck@rrow in {1,...,\ck@rows} {
\pgfutil@ifundefined{pgf@sh@ns@#1-\ck@rrow-\ck@col}{}{
\pgfextractx\pgf@xa{\pgfpointanchor{#1-\ck@rrow-\ck@col}{west}}
\pgfmathparse{min(\ck@xmin,\the\pgf@xa)}
\xdef\ck@xmin{\pgfmathresult}
\pgfextractx\pgf@xa{\pgfpointanchor{#1-\ck@rrow-\ck@col}{east}}
\pgfmathparse{max(\ck@xmax,\the\pgf@xa)}
\xdef\ck@xmax{\pgfmathresult}
}
}
% adjust for col separation
\pgfmathsetmacro{\ck@xmin}{\ck@xmin - #5/2}
\pgfmathsetmacro{\ck@xmax}{\ck@xmax + #5/2}
% define color
\pgfmathparse{Mod(\ck@row + \ck@col,2) ? "#6" : "#7"}
\colorlet{sqbg}{\pgfmathresult}
\fill[sqbg] (\ck@xmin*1pt,\ck@ymin*1pt) rectangle (\ck@xmax*1pt, \ck@ymax*1pt);
}
}
\end{pgfonlayer}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\matrix [matrix of nodes,
row sep=2mm,
column sep=1mm,
nodes={draw, thick, circle, inner sep=1pt},
cells={fill=red}] (ma)
{
& 1 & &[2mm]|[gray]|1\\
& & 2 &|[gray]|2\\
|[gray]|2 & & &|[gray]|222\\[4mm]
3 & & & 3\\
};
\checkermatrix{ma}{4}{4}{2mm}{1mm}{red}{blue}
\end{tikzpicture}
\end{document}