4

The following LaTeX code was saved in the file ~/test.tex.

\documentclass[tikz,border=1cm]{standalone}
\newcommand{\grid}
{%
   \foreach \x in {1,2}
      \foreach \y in {1,2}
         \draw (\x,\y) rectangle ++(1,1);%
}
\begin{document}
\begin{tikzpicture}
\matrix
{
   \grid & \grid \\
   \grid & \grid \\
};
\end{tikzpicture}
\end{document}

The code creates a TikZ picture which contains four 2x2 grids of squares arranged in a 2x2 matrix.

Then the following commands were executed in the Terminal.

> cd ~
> lualatex test

Consequently, the file ~/test.pdf was created. When opened in a PDF viewer, the file displayed as follows.

TikZ matrix

  1. Why is there space between the matrix' rows, despite the fact that the TikZ & PGF manual (in its current version, 3.1.9a) describes the alignment of a matrix' cell pictures thus (section 20.3.1 Alignment of Cell Pictures, p. 320)?

    the second row is placed below the first row such that the bottom of the first row touches the top of the second row (unless a row sep is used to add a bit of space). Then the bottom of the second row touches the top of the third row, and so on.

    (The emphasis was added by me.)

  2. How can the space be removed? I tried passing the matrix the row sep=0pt option, to no avail.

Evan Aad
  • 11,066
  • 2
    Change \foreach \y in {1,2} to \foreach \y in {0,1}. – Jasper Habicht Nov 28 '22 at 21:11
  • @JasperHabicht Oh, I see. Thanks! – Evan Aad Nov 28 '22 at 21:14
  • The problem is essentially that you draw the squares starting from coordinate (1,1) upwards, so you leave a space between y = 0 and y = 1. – Jasper Habicht Nov 28 '22 at 21:15
  • @JasperHabicht Then why weren't the columns too separated by a space? – Evan Aad Nov 28 '22 at 21:15
  • Good point. I think, the contents of the matrix cells are centered. Maybe the manual (20.3.1 Alignment of Cell Pictures) has some explanation ... – Jasper Habicht Nov 28 '22 at 21:18
  • “(To be more precise, the height of the row is the maximum -value of any of the bounding boxes and the depth of the row is the negated minimum -value of the bounding boxes.)” I guess it assume the origin to be inside the bounding box (even if a few sentences earlier the opposite is states to be possible). It looks like the vertical alignment is done similar to a normal TeX box which has a height and a depth (above and below the baseline). Apparently, the depth is assumed to be not below zero. – Qrrbrbirlbel Nov 28 '22 at 21:27
  • @Qrrbrbirlbel Yes, it seems that the stuff inside the matrix cells is handled as if it was \tikz[baseline]{\grid}, so the baseline is taken into account somehow here. – Jasper Habicht Nov 28 '22 at 21:31
  • @JasperHabicht Is this a bug then? The manual gave me the impression that the alignment of the rows is analogous to that of the columns. – Evan Aad Nov 28 '22 at 21:34
  • 1
    I would not consider it a bug. If the baseline is taken into account, it is obvious that this would primarily affect vertical spacing. I'd rather argue that the manual is not too specific as to how alignment is calculated inside the matrix cells. So, one could still file an issue and ask the package maintainers to add some clarifications about this. – Jasper Habicht Nov 28 '22 at 21:36
  • @JasperHabicht Is there a way to instruct the matrix to apply the same logic to the vertical dimension as it does to the horizontal dimension without changing the code as you suggest in your first comment? – Evan Aad Nov 28 '22 at 21:42
  • The same logic? If you also want horizontal gaps, just add \path (0,0); before the first \foreach macro inside the defintion of \grid. If you want no gaps but consistent code, change \foreach \y in {1,2} to \foreach \y in {0,1} and \foreach \x in {1,2} to \foreach \x in {0,1}. – Jasper Habicht Nov 28 '22 at 21:49
  • @JasperHabicht So the answer is "no". – Evan Aad Nov 28 '22 at 21:50
  • At least, I am unaware of a way using simple options to change this behaviour. – Jasper Habicht Nov 28 '22 at 21:58
  • @Qrrbrbirlbel This will work if I care for the spaces. But what if I don't want them? Is my only option to change the sets of iterated values in the \foreach loops? – Evan Aad Nov 28 '22 at 22:20

1 Answers1

4

PGF treats every matrix cell as a TeX box, these have a height (above the baseline) and a depth (below the baseline).

Similar as with

\raisebox{1em}{Foo}\par
Bar

The second paragraph with only Bar doesn't get moved up to be the normal vertical space below Foo.

Similar things are happening with your cells. The baseline (y = 0) will be part of your matrix row one way or the other.

My advice? Don't have y = 0 outside of your cells' bounding box.

In your example, you could just add shift=(down:1) to all cells so that the original y = 0 is part of the cells bounding box again.
(I'm using (down:1) and not yshift=-1cm because your \grid macro also uses the xy coordinate system and not the canvas one.

\documentclass[tikz,border=1cm]{standalone}
\newcommand{\grid}{%
   \foreach \x in {1,2}
      \foreach \y in {1,2}
         \draw (\x,\y) rectangle ++(1,1);%
}
\begin{document}
\begin{tikzpicture}
\matrix[cells={shift=(down:1)}]{
   \grid & \grid \\
   \grid & \grid \\
};
\end{tikzpicture}
\end{document}

If you're feeling adventurous:
Here is a fix that applies a few patches so that maybe half the cases work again.

Firstly, with \pgf@matrix@init@row I initialize every row to have a height of −32000 pt (similar initialization with 16000pt are done all across PGF/TikZ even though TeX would allow a bit more). Originally, they both had 0pt.

Secondly, I'll patch \pgf@matrix@finish@line which now checks whether the minimal y value of the row is above 0pt, meaning you have a “negativ depth” (similar to Foo from the example). This depth is subtracted from the row seperation between the current and the next row.

Lastly, the macro \pgf@end@matrix which does a lot of work after collecting all cells to actual place the matrix node and manage some things needs another patch so that after the last row the same problem doesn't appear again.

For some reason, only half the vertical space needs to be removed.


Caveats:

  1. Don't use empty rows.
  2. This doesn't deal with “negative heights”, meaning when y = 0 is above the bounding box.
  3. Don't trust this answer to not introduce other problems down the line.

Code

\documentclass[tikz,border=1cm]{standalone}
\makeatletter
\def\pgf@matrix@init@row{%
  \relax%
  \global\advance\pgfmatrixcurrentrow by 1\relax%
  \global\pgfmatrixcurrentcolumn=0\relax%
  \expandafter\gdef\csname pgf@matrix@maxy\the\pgfmatrixcurrentrow\endcsname{-16000pt}% ← almost mindimen
  \expandafter\gdef\csname pgf@matrix@miny\the\pgfmatrixcurrentrow\endcsname{16000pt}%  ← almost maxdimen
}%
\usepackage{etoolbox}
\preto{\pgf@matrix@finish@line}{% row has negative depth, remove that from column seperation
  \ifdim\csname pgf@matrix@miny\the\pgfmatrixcurrentrow\endcsname>0pt
    \pgf@matrix@addtolength\pgf@y{-\csname pgf@matrix@miny\the\pgfmatrixcurrentrow\endcsname}%
  \fi}
\patchcmd{\pgf@end@matrix}{\egroup\egroup}{%
\egroup % last row has negative depth? remove that (why .5?)
  \ifdim\csname pgf@matrix@miny\the\pgfmatrixcurrentrow\endcsname>0pt
    \vskip-.5\dimexpr\csname pgf@matrix@miny\the\pgfmatrixcurrentrow\endcsname\relax
  \fi
\egroup}{}{}
\makeatother
\newcommand{\grid}{%
   \foreach \x in {1,2}
      \foreach \y in {1,2}
         \draw (\x,\y) rectangle ++(1,1);%
}
\begin{document}
\tikz
\matrix[draw=red, cells=black]{
   \grid & \grid \\
   \grid & \grid \\
%   & \\ % empty lines are bad!
   \grid \\
};
\end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821
  • The manual gives Mark Wibrow a shoutout, thanking him for the work on matrices, but his profile says he hasn't been here for over a year. ☹ PGF implements its matrix by using TeX's &&/##/\cr which I never have dealt with … – Qrrbrbirlbel Nov 28 '22 at 22:41