23

This is a follow-up to an earlier question regarding left-aligning subfigure captions.

Having achieved the left-alignment goal, I would now like the caption of the subfigure to be bottom-aligned, while having the subfigure itself be top-aligned. As an example, in the figure below, subfigures (a) and (b) would share an uppermost edge, and their captions would share a lowermost edge, and similarly for subfigures (c) and (d). This is most appropriate for the overall layout style of my document and also the content of the subfigures themselves.

enter image description here

\documentclass{article}
\usepackage[font=footnotesize]{subcaption}
\usepackage[draft]{graphicx}

\begin{document}
\begin{figure}[!ht]
\captionsetup[subfigure]{justification=justified,singlelinecheck=false}
\centering
\begin{subfigure}[b]{0.3\textwidth}
\includegraphics[scale=0.8]{fig/workflow-S}
\caption{workflow $S$}
\end{subfigure}
\begin{subfigure}[b]{0.5\textwidth}
\includegraphics[scale=0.8]{fig/workflow-S-run}
\caption{run of $S$}
\end{subfigure}
\vskip 8pt
\begin{subfigure}[b]{0.3\textwidth}
\includegraphics[scale=0.8]{fig/workflow-S'}
\caption{workflow $S'$}
\end{subfigure}
\begin{subfigure}[b]{0.5\textwidth}
\includegraphics[scale=0.8]{fig/workflow-S'-run}
\caption{run of $S'$}
\end{subfigure}
\caption{Here's a figure}
\end{figure}
\end{document}

I've experimented with the minipage settings for subfigure, specifically using [t] instead of [b], but it seems to have no effect. I'm unsure as to whether a position of [t] would produce the right effect anyway, or whether it would shift the captions up as well.

Why is [t] not working here, and what's the right way to produce the layout I need?

The solution should generalise to an arbitrary number of subfigures per row, subfigures which are text or tables rather than images, and should not require any hard-coded sizes.

1
  • 1
    (+1) for asking two separate questions.
    – Janosh
    Nov 27, 2014 at 14:27

2 Answers 2

11
+50

Here's a flexible solution; in a figure environment you define with \xsubfigure the objects you need and then arrange them as you like. Each command has as a first argument a symbolic key to be used in \makerow; the second argument defines the objects via a key-value syntax (add \label in the body of caption, if needed).

\documentclass{article}
\usepackage[font=footnotesize]{subcaption}
\usepackage[demo]{graphicx}

\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\xsubfigure}{ m m }
 {% #1 is a symbolic key, #2 is a list of key-value pairs
  \roly_xsubfigure:nn { #1 } { #2 }
 }
\NewDocumentCommand{\makerow}{ m }
 {% #1 is a list of symbolic keys
  \roly_makerow:n { #1 }
 }

% define the keys
\keys_define:nn { roly/subfigures }
 {
  width .tl_set:N = \l_roly_subfig_width_tl,
  body .tl_set:N = \l_roly_subfig_body_tl,
  caption .tl_set:N = \l_roly_subfig_caption_tl,
 }

% the needed variables
\dim_new:N \l_roly_row_height_dim
\box_new:N \l_roly_body_box

% this is the inner command that stores the properties
\cs_new_protected:Npn \roly_xsubfigure:nn #1 #2
 {
  \prop_if_exist:cTF { l_roly_subfig_#1_prop }
   {
    \prop_clear:c { l_roly_subfig_#1_prop }
   }
   {
    \prop_new:c { l_roly_subfig_#1_prop }
   }
  \keys_set:nn { roly/subfigures } { #2 }
  \prop_put:cnV { l_roly_subfig_#1_prop } { width } \l_roly_subfig_width_tl
  \prop_put:cnV { l_roly_subfig_#1_prop } { body } \l_roly_subfig_body_tl
  \prop_put:cnV { l_roly_subfig_#1_prop } { caption } \l_roly_subfig_caption_tl
 }

% this is the inner command for producing a row
\cs_new_protected:Npn \roly_makerow:n #1
 {
  % get the heights of the objects on a row
  \dim_zero:N \l_roly_row_height_dim
  \clist_map_inline:nn { #1 }
   {
    \hbox_set:Nn \l_roly_body_box
     {
      \prop_item:cn { l_roly_subfig_##1_prop } { body }
     }
    \dim_compare:nT { \box_ht:N \l_roly_body_box > \l_roly_row_height_dim }
     {
      \dim_set:Nn \l_roly_row_height_dim { \box_ht:N \l_roly_body_box }
     }
   }
  % produce a line
  \clist_map_inline:nn { #1 }
   {
    % a subfigure is set here
    \begin{subfigure}[t]{ \prop_item:cn { l_roly_subfig_##1_prop } { width } }
    \raggedright
    \vspace{0pt} % for top alignment
    % the body is set in a suitably dimensioned parbox
    \parbox[t][\l_roly_row_height_dim]{\textwidth}{
      \prop_item:cn { l_roly_subfig_##1_prop } { body }
    }
    % add the caption
    \caption{ \prop_get:cn { l_roly_subfig_##1_prop } { caption } }
    \end{subfigure}
    \hspace{2em} % some space between the objects in a row
   }
   \unskip\\ % end up the row
 }
\ExplSyntaxOff
\begin{document}

\begin{figure}
\captionsetup[subfigure]{justification=justified,singlelinecheck=false}
\centering

\xsubfigure{A}{
  width=0.3\textwidth,
  body={\includegraphics[width=3cm,height=2cm]{fig/workflow-S}},
  caption={workflow $S$ and some text added to go to the next line}
}
\xsubfigure{B}{
  width=0.5\textwidth,
  body={\includegraphics[width=6cm,height=2.5cm]{fig/workflow-S-run}},
  caption={run of $S$}
}
\xsubfigure{C}{
  width=0.3\textwidth,
  body={\includegraphics[width=2cm,height=1.5cm]{fig/workflow-S'}},
  caption={workflow $S'$}
}
\xsubfigure{D}{
  width=0.5\textwidth,
  body={\includegraphics[width=4cm,height=1cm]{fig/workflow-S'-run}},
  caption={run of $S'$}
}

\makerow{A,B}

\medskip

\makerow{C,D}

\caption{Here's a figure}
\end{figure}
\end{document}

Note that if you're not satisfied with the arrangement, you can rearrange the subfigures by saying, for instance,

\makerow{A,D}

\medskip

\makerow{C,B}

In the example I set height and width for every \includegraphics, as I don't have your images, but you can use the keys you prefer.

enter image description here

5
  • This is excellent, thanks. Well worth the 50 bounty!
    – Roly
    Jan 14, 2014 at 9:57
  • This has been a while, but if i copy the above code verbatim, I get the error "Missing number, treated as zero". Is there any explanation to this? Oct 13, 2015 at 15:56
  • @user1018513 I tried again (copying from scratch), but I get no error. Are you sure your TeX distribution is up-to-date?
    – egreg
    Oct 13, 2015 at 15:58
  • Very nice solution! I got an error but was able to fix it by replacing \prop_get:cn with \prop_item:cn
    – Michael
    Jan 11, 2017 at 7:35
  • @Michael Which is what it should be nowadays. Fixed
    – egreg
    Jan 11, 2017 at 7:54
7

Solution Idea

Put the images inside boxes with a fixed height (use \vtop to create top aligned boxes). Use these boxes as images to be included in subfigure.

The two images which are placed side-by-side are to be given identical heights as the first argument of \imagebox. The second argument is the \includegraphics command.

Solution Code

\documentclass{article}
\usepackage{caption}
\usepackage[font=footnotesize]{subcaption}
\usepackage[draft]{graphicx}

\def\imagebox#1#2{\vtop to #1{\null\hbox{#2}\vfill}}

\begin{document}
\begin{figure}[!ht]
  \captionsetup[subfigure]{justification=justified,singlelinecheck=false}
  \centering
  \begin{subfigure}[b]{0.3\textwidth}
    \imagebox{37.5mm}{\includegraphics[scale=0.8]{fig/workflow-S}}
    \caption{workflow $S$}
  \end{subfigure}
  \begin{subfigure}[b]{0.5\textwidth}
    \imagebox{37.5mm}{\includegraphics[scale=0.8]{fig/workflow-S-run}}
    \caption{run of $S$}
  \end{subfigure}
  \vspace{5.0mm}
  \begin{subfigure}[t]{0.3\textwidth}
    \imagebox{27.5mm}{\includegraphics[scale=0.8]{fig/workflow-S'}}
    \caption{workflow $S'$}
  \end{subfigure}
  \begin{subfigure}[t]{0.5\textwidth}
    \imagebox{27.5mm}{\includegraphics[scale=0.8]{fig/workflow-S'-run}}
    \caption{run of $S'$}
  \end{subfigure}
  \caption{Here's a figure}
\end{figure}
\end{document}

Output

enter image description here

Limitations

I wish I could come up with a more general solution. Manually setting (needs trial and error) the heights for all the pairs is a bother. If I could find a solution where something in the preamble (or a macro definition) could take care of the whole document that would have been perfect.

If you have only a few figures to be aligned like this, this solution should work. But if you have hundreds of figures like this, you will want to write a more general macro with two arguments where the two arguments will be the two images to be top aligned. The macro will first measure the height of the two, take the bigger one, then create two subfigures, where \imagebox is called inside using the bigger height previously found.

2
  • Yes, it's not so nice having the guestimate the desired height for reach row. (And I'd want a solution which generalises to n images per row, I guess.) Still, your approach could be a useful stop-gap. I have maybe dozen figures of this nature, so this is workable, although awkward. Thanks.
    – Roly
    Jan 8, 2014 at 8:32
  • 1
    I've experimented with your solution but the manual image sizes is impractical. I've therefore started a bounty on more general solution; I'm not sure if the question is really "widely applicable to a large audience", but I believe there should be a canonical solution.
    – Roly
    Jan 10, 2014 at 15:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .