表格标题看起来紧贴着表格,并不是错觉。LaTeX 的标准文类把 \abovecaptionskip 设为 10pt,把 \belowcaptionskip 设为 0pt(article、report、book 完全相同),而这些默认值是 为插图 调好的。既然惯例是把表格标题放在表格上方,那么有留白的一侧朝上,为零的一侧正好贴住表格——这就是「总觉得局促」的真正来源。本页讲 tabular 外围 的事:通过 table 环境完成放置与加标题、如何处理超出版心的表格、把表格转成横向,以及用 threeparttable 给表格加注。表格内容本身的写法属于「tabular 基础」和「列规格、对齐与宽度」。
为什么表格会“浮动”——table 环境与 tabular 的区别
因为表格不能在中途分页。tabular 造出的是 一个大盒子,而盒子无法在页面边界处切开。如果硬要把它排在源码所在的位置而剩余空间不够,就会留下大空白,或者把版面挤坏。因此 LaTeX 把表格和插图当作 浮动体,将它们从正文流中分离,送到能放得下的地方。使用这一机制的装置就是 table 环境:把 tabular 放进去,用 \caption{…} 给它「表 1」这样的编号标题,再用 \label{…} 和 \ref{…} 让正文可以引用。于是正文里只写 按编号引用——「如表~\ref{tab:sales} 所示」——无论表格漂到多少页之后,编号都会自动正确。其中 ~ 是不可断开的连接空格,防止词与编号在换行处分开。
\usepackage{booktabs}
\usepackage{float} % needed only for the [H] specifier
\begin{table}[htbp]
\centering
\caption{Quarterly sales}
\label{tab:sales} % \label always AFTER \caption
\begin{tabular}{@{}lrr@{}}
\toprule
Quarter & Sales & YoY \\
\midrule
Q1 & 1{,}200 & +5\% \\
Q2 & 1{,}350 & +8\% \\
\bottomrule
\end{tabular}
\end{table}
% in the body: as Table~\ref{tab:sales} shows ...放置选项 [htbp] 与 [H] 的区别
[htbp] 是 允许的位置集合,[H] 则是 停止浮动的指令。在 \begin{table} 之后的方括号里,可以列出 h(此处)、t(页面顶端)、b(页面底端)、p(浮动体专用页)。什么都不写时,article 和 book 的默认值都是 tbp——也就是说 h 一开始就不在候选之列。关键在于,书写顺序不会改变 LaTeX 的尝试顺序:它总是按 t→b→p 尝试,所以 [htbp] 和 [bpht] 的作用完全相同。请把方括号理解为 权限的列举,而不是偏好的表达。
开头的感叹号 !(如 [!ht])表示「仅对这个浮动体,忽略内部限制 后再尝试放置」,并不表示「就放在这里」。所谓「内部限制」是有具体数值的:在 article 中,默认 \topfraction = 0.7(浮动体可占页面顶部的比例)、\bottomfraction = 0.3、\textfraction = 0.2(正文至少要占的比例)、\floatpagefraction = 0.5(生成浮动体专用页的下限),并且每页最多顶部 2 个、底部 1 个、合计 3 个浮动体(\topnumber、\bottomnumber、\totalnumber)。表格迟迟不出现,通常就是卡在其中某一条上,而 ! 只是放宽这些判断。若真的想固定在原位,请用 float 宏包的 [H]:在 \usepackage{float} 之后写 \begin{table}[H],表格就停止浮动,直接排在源码所在的位置(结果可能在页面底部留下大片空白)。把 H 理解成「不会浮动的浮动体」,能少踩很多坑。
| 指定符 | 含义 | 相关默认值 |
|---|---|---|
h | 此处,即源码位置。不允许单独使用 h,会自动补上 t | 不包含在默认的 tbp 中 |
t | 正文页的顶端 | \topfraction = 0.7,\topnumber = 2 |
b | 正文页的底端 | \bottomfraction = 0.3,\bottomnumber = 1 |
p | 不含正文的浮动体专用页 | \floatpagefraction = 0.5 |
! | 仅对该浮动体忽略上述比例与数量限制。它不是位置指定 | 同样作用于 \textfraction = 0.2 与 \totalnumber = 3 |
H | 停止浮动并固定在原位。需要 float 宏包 | 不是标准 LaTeX 的指定符 |
标题在表上、在图下——而 \label 要写在 \caption 之后
这两点占了标题相关事故的大半。\caption{…} 给浮动体一个带编号的标题,在 article 中标为「Table 1:」,在 book 中标为「Table 1.1:」这样的 连续编号。表格与插图使用不同计数器,编号不会混在一起。带可选参数 \caption[短标题]{完整标题} 时,表目录 中显示短标题,正文中显示完整标题。而且 推进计数器的是 \caption,所以 \label 必须写在 \caption 之后;顺序颠倒时 \ref 会指向前一个编号,而且不会报错。至于位置,惯例是 表格标题在上、插图标题在下:读表格要先看标题再进入数据,看插图则是先看图再读说明。在 LaTeX 中,把 \caption 写在 tabular 之前 就在上方,写在 之后 就在下方。
这时开头那组实测值就派上用场了。标准文类中 \abovecaptionskip 为 10pt,\belowcaptionskip 为 0pt,也就是说留白 只在标题的外侧,标题与浮动体主体之间什么都没有。对于标题在下方的插图,这没有问题;但对于标题在上方的表格,标题就会贴住表格。补救有两种。在原生 LaTeX 中,声明 \setlength{\belowcaptionskip}{6pt}。若使用 caption 宏包,则写 \usepackage[skip=6pt]{caption},或用 \captionsetup[table]{skip=6pt} 只针对表格设置。标题本身的样式——字体、分隔符、宽度——同样由 caption 宏包负责,详见「标题与子图」页面。
让内容居中用的是 \centering。在浮动体内部,惯用的是 \centering 而不是 center 环境(\begin{center}…\end{center}):center 会在前后加入额外的垂直空白,而 \centering 只是一个声明,不会带来多余留白。若想让表格靠右而不是居中,可在内容前放上 \hfill,横向的剩余空间就会全部集中到左侧。顺带一提,浮动体内部的 \centering 是安全的,但把 \centering 写进 p{width} 列则是另一回事:如果该列是表格的最后一列,行终止符 \\ 会失效,并报出 ! Extra alignment tab has been changed to \cr.。写在列指定里的 \centering 该如何处理,属于「列规格、对齐与宽度」的范围。
LaTeX 表格宽度超出版心时
在缩小之前,请先 量一量宽度都花在哪里。l、c、r 类型的列不会换行,因此列一多,表格就会超过 \textwidth。但超出的部分通常不是文字,而是 列间的空白。举个实例:一张 12 列、1 行表头、1 行数据的 booktabs 表格实测宽 412.95pt,而默认 article 的 \textwidth 是 345pt,因此溢出 68pt。把同一张表以 \tabcolsep = 0pt 重排,宽度变为 268.95pt——相差 正好 144pt,即 2 × 12 列 × 6pt。换言之,表格宽度的 35% 是留白而不是文字。
弄清这一点后,处理的优先顺序就变了。常见建议「把字号调小一级」,对同一张表来说,\small 得到 392.77pt,\footnotesize 得到 372.55pt,都够不到 345pt。原因是 \tabcolsep 是 一个长度,并不随字号缩放,所以缩小字体后那 144pt 的留白原封不动。而只要用 \setlength{\tabcolsep}{3pt} 把它减半,表格就变成 340.95pt——在正文字号下正好放得下。\small 与 \tabcolsep = 4pt 并用则是 344.77pt。请按这个顺序考虑:先留白,再字号,最后才是缩放。
\usepackage{graphicx} % for \resizebox
% 1. trim the padding first -- \tabcolsep is 6pt on EACH side of EVERY column
\begingroup
\setlength{\tabcolsep}{3pt}
\begin{tabular}{@{}lrrrrrrrrrrr@{}} % @{} also removes the outer 2 x 6pt
...
\end{tabular}
\endgroup
% 2. only then consider a smaller size, or scaling as a last resort
\resizebox{\textwidth}{!}{%
\begin{tabular}{lrrrrr}
...
\end{tabular}%
}在列指定两端加上 @{},同样能去掉最外侧的 \tabcolsep,让表格与正文左边缘对齐(实测正好是 2 × 6pt = 12pt)。这是 booktabs 表格中常见的写法。若这些都还不够,最后的手段是 graphicx 的 \resizebox{宽}{高}{内容},把其中之一设为 ! 就会按比例缩放。但它 是对内容做「缩放」:把前面那张表交给 \resizebox{\textwidth}{!},倍率为 345 ÷ 412.95 = 0.836,于是 10pt 的正文字变成约 8.4pt,\toprule 的 0.8pt 变成 0.67pt。字形和线条都不再与周围正文相称,所以请把它当作 临时补救。若用 adjustbox 代替 \resizebox,还能写成环境形式 \begin{adjustbox}{width=\textwidth}…\end{adjustbox}。想让行距略微宽松,可用 \renewcommand{\arraystretch}{1.2}(默认为 1);在 10pt 文档中,一张三行的表格会因此增高 0.2 × 3 × 12pt = 7.2pt。
如果到这一步仍然放不下,那就是该换工具的信号了。会把列自动伸缩到目标宽度的 tabularx 的 X 列,以及 tabularray 的 tblr,由「高级表格环境」页面负责。若问题是 行数太多、一页放不下,那就不是宽度而是长度的问题,答案是可以跨页断开的 longtable——见「跨页表格」页面。这两者都值得在本页的缩放和旋转之前先行考虑。
把表格转成横向——sidewaystable 与 rotatebox
实在放不进竖向页面的大表,可以旋转 90 度占满一整页:用 rotating 宏包的 sidewaystable 环境 代替 table 即可。标题和编号会一起旋转,并且仍会正确出现在表目录中。在双面排版中,宏包会自动选择旋转方向,使跨页阅读更自然。插图对应的环境是 sidewaysfigure。若只想 按任意角度旋转表格的一部分,可使用同样来自 graphicx / rotating 的 \rotatebox{角度}{内容};典型用法是用 \rotatebox{90}{long column name} 把某个表头单元格竖起来(正角度为逆时针)。
\usepackage{rotating}
\usepackage{booktabs}
\begin{sidewaystable}
\centering
\caption{A wide table turned 90 degrees}
\label{tab:wide}
\begin{tabular}{@{}lrrrrrrrr@{}}
\toprule
Region & Q1 & Q2 & Q3 & Q4 & Total & Prev & Diff & Ratio \\
\midrule
East & 120 & 135 & 128 & 142 & 525 & 500 & +25 & 105\% \\
\bottomrule
\end{tabular}
\end{sidewaystable}有一条边界需要注意。rotating 处理的是 盒子(box)的旋转,而盒子总是待在一页之内,因此它无法排出 跨多页的横向正文。需要那种效果时,请使用 lscape(把正文横排),或旋转 PDF 页面本身的 pdflscape。pdflscape 尤其适合屏幕阅读的文档,因为 PDF 阅读器会自动把页面转正。既要横向又要跨页的表格,就要把 pdflscape 与 longtable 组合起来。
给表格加注——threeparttable
在 tabular 中使用 \footnote,注释要么跳到页脚,要么根本不输出。解决这个问题的是 Donald Arseneau 的 threeparttable。顾名思义,它把表格视为 三部分:标题(caption)、主体(tabular)与注释(tablenotes),并把 注释按与表格相同的宽度排在表格正下方。正如宏包自身的说明所写,threeparttable 本身不会浮动,所以若需要编号和放置控制,就要把整体包进 table(双栏排版下需要通栏时则用 table*)。
\usepackage{booktabs}
\usepackage{threeparttable}
\begin{table}[htbp]
\centering
\begin{threeparttable}
\caption{Yield by variety}
\label{tab:yield}
\begin{tabular}{@{}lrr@{}}
\toprule
Variety & Yield\tnote{a} & Price\tnote{b} \\
\midrule
Koshihikari & 540 & 380 \\
Akitakomachi & 520 & 360 \\
\bottomrule
\end{tabular}
\begin{tablenotes}
\footnotesize
\item[a] Kilograms per 10 a.
\item[b] Wholesale price per kilogram.
\end{tablenotes}
\end{threeparttable}
\end{table}只需在 threeparttable 环境中依次放置 \caption、tabular、tablenotes。注释的对应是 手动 的:在单元格里放上 \tnote{a} 之类的标记,并在 tablenotes 中用同样的字母写 \item[a] …。作者把不做自动编号的理由留在了 .sty 文件开头:「目前注释没有任何自动的部分……我选择这种方式,是因为用 \footnote 自动编号会非常难用,尤其是因为 许多表格会反复引用同一条注释。如果有人有方便而优雅的自动方案,我愿闻其详!」这个宏包至今仍保持着那样的设计。
| 选项 | 效果 |
|---|---|
para | 把多条注释不换行地连排成一个段落 |
flushleft | 去掉注释的悬挂缩进 |
online | 把 \item 标记按正常字号输出,而不作上标 |
normal | 恢复默认格式(在单个表格中覆盖文档级设置) |
这些选项既可以像 \usepackage[para]{threeparttable} 那样应用于整个文档,也可以像 \begin{tablenotes}[flushleft] 那样按表指定。threeparttable 本身不设置标题样式,因此想要像样的标题外观时,请与 caption 宏包配合使用。若要给跨页长表(longtable)加注,请使用姊妹宏包 threeparttablex。
- 表格不出现在预期位置 — 先把
[htbp]当作 权限集合 而非顺序来重新审视。若仍不动,说明撞上了\topfraction之类的限制,可试[!ht];若真要固定,用float的[H]。 - 标题贴住表格 —
\belowcaptionskip默认为 0pt。用\setlength{\belowcaptionskip}{6pt}或caption宏包的skip=。 - 表格超出版心 — 先动
\tabcolsep和@{},再考虑\small,最后才用\resizebox。如果列可以换行,就用p{width}或tabularx。 - 行数太多 — 这是长度而非宽度的问题,用
longtable;若还需要横向,就与pdflscape并用。 - 想给单元格加脚注 — 不要用
\footnote,改用threeparttable的\tnote与tablenotes。