14

The goal

My goal is to align side-by-side figures, tables, and text in minipages according to the top, middle and bottom of the largest such minipage. This makes it different (I believe) from the related links and references at the bottom, which detail the baseline of the surrounding text. I don't know which figure is largest.

For example, consider the following figure:

enter image description here

Notice that A and C are aligned to the bottom of C and that the fourth image is aligned to the middle of C, the largest image.

The algorithm for this is quite straightforward:

  • put all of the minipage environments in a box, and measure the height
  • then set each minipage using \begin{minipage}[b][MAX HEIGHT][<alignment>] so that each one is 'anchored' to the bottom, each one has the same height (cruicial), and then the position of each contents can be specified (t, c, or b).

The problem

The problem I'm having is when I try to caption these objects, the numbering falls down. I get, for example, the following:

screenshot

Having read Is there a switch to turn off LaTeX counters from inside a box?, I thought I could fix it with \savecounters@ and restorecounters@.

The question

How can I modify my code so that the numbering of both figures and subfigures is as expected? Solutions would hopefully scale naturally to tables.

I'm very open to alternatives that accomplish the same idea; I have tried with the adjustbox package, for example, but no luck so far.

The MWE

% arara: pdflatex
% !arara: indent: {overwrite: yes}
\documentclass{article}
\usepackage{graphicx}
\usepackage[showframe=true,textwidth=12cm]{geometry}
\usepackage{amsmath}
\usepackage{subcaption}
\usepackage{environ}

\newsavebox{\vabox}
\makeatletter
\NewEnviron{verticallyaligned}{%
    % temporarily set \vamaxheight to nothing during the measurements
    \let\vamaxheight\relax
    % temporarily disable figure numbering
    \savecounters@
    % measure the height of the body
    \begin{lrbox}{\vabox}
        \BODY%
    \end{lrbox}%
    % turn figure numbering back on
    \restorecounters@
    % set the height of the minipage box
    \newlength{\vamaxheight}
    \setlength{\vamaxheight}{\ht\vabox}
    % output the body, which now contains the new height :)
    \noindent\makebox[\linewidth][c]{\mbox{}\hfill\BODY\hfill\mbox{}}%
}

\begin{document}

\begin{verticallyaligned}
    \noindent
    \begin{minipage}[b][\vamaxheight][b]{.10\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-a}
        \captionof{figure}{}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.15\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-b}
        \captionof{figure}{}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.20\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-c}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][c]{.10\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.20\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image}
    \end{minipage}%
\end{verticallyaligned}

\begin{figure}[!htb]
    \begin{verticallyaligned}
        \begin{subfigure}[b][\vamaxheight][t]{.10\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-a}
            \caption{}
        \end{subfigure}%
        \hfill
        \begin{subfigure}[b][\vamaxheight][t]{.15\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-b}
            \caption{}
        \end{subfigure}%
    \end{verticallyaligned}
    \caption{}
\end{figure}

\begin{figure}[!htb]
    \begin{verticallyaligned}
        \begin{subfigure}[b][\vamaxheight][c]{.10\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-a}
            \caption{}
        \end{subfigure}%
        \hfill
        \begin{subfigure}[b][\vamaxheight][c]{.15\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-b}
            \caption{}
        \end{subfigure}%
    \end{verticallyaligned}
    \caption{}
\end{figure}
\end{document}

References

  • Have you looked at floatrow? – cfr Feb 26 '15 at 22:14
  • It has been my experience that centered boxes aren't always precisely centered. Anyway, one can determine the distance from the top to the baseline (height) and the distance from the baseline to the bottom (depth) using \settoheight or \settodepth or sometimes simply \height of \depth. For example, \raisebox{-\height}{...} will align tops and \raisebox{\depth}{...} will align bottoms of pretty much anything. – John Kormylo Feb 27 '15 at 4:00
  • @cfr I'm open to the idea--I need to be able to specify width, vertical alignment, have subfigures, subtables, and to be able to put figures next to tables and text – cmhughes Feb 27 '15 at 8:32
  • @JohnKormylo I'd very much like to see a demonstration in the context of my example code, if you have the time – cmhughes Feb 27 '15 at 17:48
  • 1
    I just noticed your bonus. That is very generous of you. – Steven B. Segletes Mar 3 '15 at 23:56
8
+300

Here I introduce \pushfigure[alignment]{width}{content}{captioningof} to push figures onto a "stack", and then \popfigures to set them all on a line. In all cases, captioning should be done with \captionof for any figure being "pushed". Bottom alignment is the default.

The \pushfigure phase of the operation saves all the parameters, and measures the height (plus any depth) of the content. It saves the maximum height+depth of all the contents that have been pushed.

The \popfigures phase performs a \stackinset of the figure atop a rule of the maximum-content-height, using the saved alignment for the figure. This achieves proper vertical alignment of the figures. Any captioning that was specified is applied below the vertical \rule, which will keep all captions at the same height, regardless of content alignment.

An \hfill is inserted at the beginning and after each figure popped. All parameters (counters/lengths) are reset at the end of the "pop", to prepare for the next sequence of "push"es.

For this MWE, I reduced the value of \abovecaptionskip to 6pt, which I find more visually pleasing when employing subfigures.

EDITED to demonstrate tabular and \parbox content. REEDITED to account for the fact that bottom aligned tabular and \parbox boxes can still have depth, associated with descending letters on the bottom line.

% arara: pdflatex
% !arara: indent: {overwrite: yes}
\documentclass{article}
\usepackage{graphicx}
\usepackage[showframe=true,textwidth=12cm]{geometry}
\usepackage{amsmath}
\usepackage{subcaption}
\usepackage{stackengine,ifthen}
\newcounter{figstack}
\newcounter{figindex}
\newlength\fight
\newcommand\pushfigure[4][b]{%
  \stepcounter{figstack}%
  \expandafter\def\csname %
    figalign\romannumeral\value{figstack}\endcsname{#1}%
  \expandafter\def\csname %
    figwd\romannumeral\value{figstack}\endcsname{#2}%
  \expandafter\def\csname %
    figcontent\romannumeral\value{figstack}\endcsname{#3}%
  \expandafter\def\csname %
    figcap\romannumeral\value{figstack}\endcsname{#4}%
  \setbox0=\hbox{%
    \begin{minipage}{#2}#3\end{minipage}}%
  \ifdim\dimexpr\ht0+\dp0\relax>\fight\global\setlength{\fight}{%
    \dimexpr\ht0+\dp0\relax}\fi%
}
\newcommand\popfigures{%
  \setcounter{figindex}{0}%
  \hfill%
  \whiledo{\value{figindex}<\value{figstack}}{%
    \stepcounter{figindex}%
    \def\tmp{\csname figwd\romannumeral\value{figindex}\endcsname}%
    \begin{minipage}[t]{\tmp}%
      \centering%
      \stackinset{c}{}%
        {\csname figalign\romannumeral\value{figindex}\endcsname}{}%
        {\csname figcontent\romannumeral\value{figindex}\endcsname}%
        {\rule{0pt}{\fight}}\par%
      \csname figcap\romannumeral\value{figindex}\endcsname%
    \end{minipage}%
    \hfill%
  }%
  \setcounter{figstack}{0}%
  \setlength{\fight}{0pt}%
  \hfill%
}
\setlength\abovecaptionskip{6pt}
\begin{document}
\pushfigure{.1\textwidth}
           {\includegraphics[width=\textwidth]{example-image-a}}%
           {\captionof{figure}{}}
\pushfigure[t]{.15\textwidth}
           {\includegraphics[width=\textwidth]{example-image-b}}%
           {\captionof{figure}{}}
\pushfigure[t]{.2\textwidth}
           {\includegraphics[width=\textwidth]{example-image-c}}%
           {}
\pushfigure[c]{.1\textwidth}
           {\includegraphics[width=\textwidth]{example-image}}%
           {}
\pushfigure[c]{.2\textwidth}
           {\includegraphics[width=\textwidth]{example-image}}%
           {}
\noindent\popfigures

\begin{figure}[!htb]
\captionsetup{labelformat=parens}
  \pushfigure[t]{.1\textwidth}
             {\includegraphics[width=\textwidth]{example-image-a}}
             {\captionof{subfigure}{}}
  \pushfigure{.15\textwidth}
%             {\includegraphics[width=\textwidth]{example-image-b}}
             {\begin{tabular}[b]{ccc}a&b&c\\dd&e&fff\\%
               a&b&c\\x&yy& z\end{tabular}}
             {\captionof{subfigure}{}}
\popfigures\par
\captionsetup{labelformat=default}
\caption{}
\end{figure}

\begin{figure}[!htb]
\captionsetup{labelformat=parens}
  \pushfigure[c]{.1\textwidth}
             {\includegraphics[width=\textwidth]{example-image-a}}
             {\captionof{subfigure}{}}
  \pushfigure[c]{.15\textwidth}
%             {\includegraphics[width=\textwidth]{example-image-b}}
             {\parbox{\textwidth}{%
                This is text and a bit more text and some more text}}
             {\captionof{subfigure}{}}
\popfigures\par
\captionsetup{labelformat=default}
\caption{}
\end{figure}
\end{document}

enter image description here


VERSION FOR TABLES

Directly analogous to figure version, but with \poptables (sub)captions placed above the figures. Note, though, that if table captions are multiline, the captions will be bottom-aligned with respect to other captions in the same row.

% arara: pdflatex
% !arara: indent: {overwrite: yes}
\documentclass{article}
\usepackage{graphicx}
\usepackage[showframe=true,textwidth=12cm]{geometry}
\usepackage{amsmath}
\usepackage{subcaption}
\usepackage{stackengine,ifthen}
\newcounter{tblstack}
\newcounter{tblindex}
\newlength\tblht
\newcommand\pushtable[4][b]{%
  \stepcounter{tblstack}%
  \expandafter\def\csname %
    tblalign\romannumeral\value{tblstack}\endcsname{#1}%
  \expandafter\def\csname %
    tblwd\romannumeral\value{tblstack}\endcsname{#2}%
  \expandafter\def\csname %
    tblcontent\romannumeral\value{tblstack}\endcsname{#3}%
  \expandafter\def\csname %
    tblcap\romannumeral\value{tblstack}\endcsname{#4}%
  \setbox0=\hbox{%
    \begin{minipage}{#2}#3\end{minipage}}%
  \ifdim\dimexpr\ht0+\dp0\relax>\tblht\global\setlength{\tblht}{%
    \dimexpr\ht0+\dp0\relax}\fi%
}
\newcommand\poptables{%
  \setcounter{tblindex}{0}%
  \hfill%
  \whiledo{\value{tblindex}<\value{tblstack}}{%
    \stepcounter{tblindex}%
    \def\tmp{\csname tblwd\romannumeral\value{tblindex}\endcsname}%
    \begin{minipage}[b]{\tmp}%
      \centering%
      \csname tblcap\romannumeral\value{tblindex}\endcsname\par%
      \stackinset{c}{}%
        {\csname tblalign\romannumeral\value{tblindex}\endcsname}{}%
        {\csname tblcontent\romannumeral\value{tblindex}\endcsname}%
        {\rule{0pt}{\tblht}}%
    \end{minipage}%
    \hfill%
  }%
  \setcounter{tblstack}{0}%
  \setlength{\tblht}{0pt}%
  \hfill%
}
\setlength\abovecaptionskip{6pt}
\begin{document}
\pushtable{.1\textwidth}
           {\includegraphics[width=\textwidth]{example-image-a}}%
           {\captionof{table}{}}
\pushtable[t]{.15\textwidth}
           {\includegraphics[width=\textwidth]{example-image-b}}%
           {\captionof{table}{}}
\pushtable[t]{.2\textwidth}
           {\includegraphics[width=\textwidth]{example-image-c}}%
           {}
\pushtable[c]{.1\textwidth}
           {\includegraphics[width=\textwidth]{example-image}}%
           {}
\pushtable[c]{.2\textwidth}
           {\includegraphics[width=\textwidth]{example-image}}%
           {}
\noindent\poptables

\bigskip\begin{table}[!htb]
\captionsetup{labelformat=default}
\caption{}
\captionsetup{labelformat=parens}
  \pushtable[t]{.1\textwidth}
             {\includegraphics[width=\textwidth]{example-image-a}}
             {\captionof{subtable}{}}
  \pushtable{.15\textwidth}
%             {\includegraphics[width=\textwidth]{example-image-b}}
             {\begin{tabular}[b]{ccc}a&b&c\\dd&e&fff\\%
               a&b&c\\x&yy& z\end{tabular}}
             {\captionof{subtable}{}}
\poptables\par
\end{table}

\begin{table}[!htb]
\captionsetup{labelformat=default}
\caption{}
\captionsetup{labelformat=parens}
  \pushtable[c]{.1\textwidth}
             {\includegraphics[width=\textwidth]{example-image-a}}
             {\captionof{subtable}{}}
  \pushtable[c]{.15\textwidth}
%             {\includegraphics[width=\textwidth]{example-image-b}}
             {\parbox{\textwidth}{%
                This is text and a bit more text and some more text}}
             {\captionof{subtable}{}}
\poptables\par
\end{table}
\end{document}

enter image description here


Now the tricky part is if one wants table and figures on the same row. The macro \popfigures achieves its vertical alignment by using top-aligned minipages. The \poptables does it by using bottom-aligned minipages. Neither of these approaches will work if a row must accommodate both top and bottom captioning.

That problem has not yet been solved.

  • 1
    Very clever solution! – Gonzalo Medina Feb 27 '15 at 22:36
  • Very, very nice, thank you :) The algorithm you've used makes perfect sense--my original plan was to write to the aux file, but you've shown that it isn't necessary, brilliant! I might not have mentioned that I'd like this to be able to handle text as well; e.g \pushfigure[t]{.1\textwidth}{here is some text here \par is some text here is some text here is some text}, but this doesn't seem to work... Is there an easy fix? Thanks again! – cmhughes Feb 28 '15 at 8:50
  • @cmhughes I made a revision so as to handle that is not the same width as the specified width. But to answer your particular question, \parbox and tabular data are quite amenable as content. Please see revision. The key is that content, being inside a minipage of a specified width, needs to be itself constrained to a width (usually \textwidth, which will occupy the full minipage). – Steven B. Segletes Feb 28 '15 at 12:29
  • @cmhughes typo: I made a revision so as to handle content that is not the same width as the specified width. – Steven B. Segletes Feb 28 '15 at 12:38
  • @cmhughes I revised again to account for descender-induced box depth of tabular and \parbox content. – Steven B. Segletes Feb 28 '15 at 19:20
4

The subcaption package is doing something dodgy. For example, you can't use \captionof{subfigure} inside a subfigure (anywhere else is fine).

I overcame the problems of my previous solution by permanently replacing subfigure with minipage and creating my own \caption from scratch.

sideways tabular

% arara: pdflatex
% !arara: indent: {overwrite: yes}
\documentclass{article}
\usepackage{graphicx}
\usepackage[showframe=true,textwidth=12cm]{geometry}
\usepackage{amsmath}
\usepackage{subcaption}
\usepackage{environ}

\newlength{\reservemaxheight}
\newcommand{\oldcaption}{}% reserve name
\makeatletter
\newcommand{\newcaption}[2][\@empty]{% same as caption
  \refstepcounter{subfigure}%
  \ifx#1\@empty\addcontentsline{lof}{subfigure}{\hbox to\@tempdima {\thesubfigure\hfil }{\ignorespaces #2}}%
  \else\addcontentsline{lof}{subfigure}{\hbox to\@tempdima {\thesubfigure\hfil }{\ignorespaces #1}}%
  \fi
  \rule{0pt}{1.5\baselineskip}(\thesubfigure) #2}
\makeatother

\def\newsubfigure{\let\oldcaption=\caption
  \let\caption=\newcaption
  \minipage}%
\def\endnewsubfigure{\endminipage
  \let\caption=\oldcaption}%
\AtBeginDocument{% can't replace it any earlier
  \let\subfigure=\newsubfigure
  \let\endsubfigure=\endnewsubfigure}

\newsavebox{\vabox}
\makeatletter
\NewEnviron{verticallyaligned}{%
    % temporarily set \vamaxheight to nothing during the measurements
    \let\vamaxheight\relax
    % temporarily disable figure numbering
    \savecounters@
    % measure the height of the body
    \begin{lrbox}{\vabox}
        \BODY%
    \end{lrbox}%
    % turn figure numbering back on
    \restorecounters@
    % set the height of the minipage box
    \let\vamaxheight\reservemaxheight
    \setlength{\vamaxheight}{\ht\vabox}%
    \addtolength{\vamaxheight}{-\dp\vabox}% change back to positive
    % output the body, which now contains the new height :)
    \noindent\makebox[\linewidth][c]{\mbox{}\hfill\BODY\hfill\mbox{}}%
}
\makeatother

\begin{document}

\begin{verticallyaligned}
    \noindent
    \begin{minipage}[b][\vamaxheight][b]{.10\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-a}
        \captionof{figure}{}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.15\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-b}
        \captionof{figure}{}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.20\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image-c}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][c]{.10\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image}
    \end{minipage}%
    \hfill
    \begin{minipage}[b][\vamaxheight][t]{.20\textwidth}
        \centering
        \includegraphics[width=\textwidth]{example-image}
    \end{minipage}%
\end{verticallyaligned}

\begin{figure}[!htb]
    \begin{verticallyaligned}
        \begin{subfigure}[b][\vamaxheight][t]{.10\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-a}
            \caption{test}
        \end{subfigure}%
        \hfill
        \begin{subfigure}[b][\vamaxheight][t]{.15\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-b}
            \caption[short caption]{long caption}
        \end{subfigure}%
    \end{verticallyaligned}
    \caption{}
\end{figure}

\begin{figure}[!htb]
    \begin{verticallyaligned}
        \begin{subfigure}[b][\vamaxheight][c]{.10\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-a}
            \caption{}
        \end{subfigure}%
        \hfill
        \begin{subfigure}[b][\vamaxheight][c]{.15\textwidth}
            \centering
            \includegraphics[width=\textwidth]{example-image-b}
            \caption{}
        \end{subfigure}%
    \end{verticallyaligned}
    \caption{}
\end{figure}
\listoffigures
\end{document}
  • The 4th figure in the top row does not appear to be center aligned. Am I seeing that right? – Steven B. Segletes Feb 28 '15 at 19:40
  • The largest minipage in the top row is B, including the caption, so the 4th figure is centered with that (including the caption). The interesting thing is that A is supposed to be bottom aligned with B, but is obviously lower. – John Kormylo Feb 28 '15 at 19:56
  • If you want I can redo this using total heights and \raisebox, or even aligning the captions separately. I thought you just wanted to fix the caption numbers. – John Kormylo Feb 28 '15 at 19:59
  • I see. I had not thought about aligning including the captions (in my answer, the captions are not part of the vertical-alignment calculation). Thanks for the explanation. – Steven B. Segletes Feb 28 '15 at 20:21
  • If they are in the minipage, they will be counted. – John Kormylo Mar 1 '15 at 0:09

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.