gt
gt copied to clipboard
Floating table
Summary
This PR addresses issue #157, specifically the ability to create latex floating environment tables with the use of the table
+ tabular
environments rather than the longtable
environment. Currently, I believe this is a major limitation of gt
because tables are often broken up across pages, and most people expect tables to operate in a similar manner to figures.
I added an option to tab_option
called latex.use.longtable
which defaults to FALSE. I then use a few ifelse
clauses in utils_render_latex.R
to get the proper output. Specification of the positioning of the float works well from the quarto code chunk option tbl-pos
.
If I run the following code:
head(state.x77) |>
as.data.frame() |>
gt() |>
tab_options(latex.use.longtable = FALSE) |>
as_latex()
Here is the latex output with the option set to FALSE:
"\\begin{table}\n\\begin{tabular}{rrrrrrrr}\n\\toprule\nPopulation & Income & Illiteracy & Life Exp & Murder & HS Grad & Frost & Area \\\\ \n\\midrule\\addlinespace[2.5pt]\n3615 & 3624 & 2.1 & 69.05 & 15.1 & 41.3 & 20 & 50708 \\\\ \n365 & 6315 & 1.5 & 69.31 & 11.3 & 66.7 & 152 & 566432 \\\\ \n2212 & 4530 & 1.8 & 70.55 & 7.8 & 58.1 & 15 & 113417 \\\\ \n2110 & 3378 & 1.9 & 70.66 & 10.1 & 39.9 & 65 & 51945 \\\\ \n21198 & 5114 & 1.1 & 71.71 & 10.3 & 62.6 & 20 & 156361 \\\\ \n2541 & 4884 & 0.7 & 72.06 & 6.8 & 63.9 & 166 & 103766 \\\\ \n\\bottomrule\n\\end{tabular}\n\\end{table}"
Here is the latex output with the option set to TRUE:
"\\begin{longtable}{rrrrrrrr}\n\\toprule\nPopulation & Income & Illiteracy & Life Exp & Murder & HS Grad & Frost & Area \\\\ \n\\midrule\\addlinespace[2.5pt]\n3615 & 3624 & 2.1 & 69.05 & 15.1 & 41.3 & 20 & 50708 \\\\ \n365 & 6315 & 1.5 & 69.31 & 11.3 & 66.7 & 152 & 566432 \\\\ \n2212 & 4530 & 1.8 & 70.55 & 7.8 & 58.1 & 15 & 113417 \\\\ \n2110 & 3378 & 1.9 & 70.66 & 10.1 & 39.9 & 65 & 51945 \\\\ \n21198 & 5114 & 1.1 & 71.71 & 10.3 & 62.6 & 20 & 156361 \\\\ \n2541 & 4884 & 0.7 & 72.06 & 6.8 & 63.9 & 166 & 103766 \\\\ \n\\bottomrule\n\\end{longtable}\n"
I have also supplied an example qmd document in tests/gt-examples/03-latex/latex-15-floating.qmd
.
I am submitting this PR currently as a draft as I have not fully stress tested it nor have I written a proper testthat
test (not very experienced with those). I am happy to proceed with doing so, if the maintainers think I am on the right path here, but wanted to get feedback before continuing.
Related GitHub Issues and PRs
- Ref: #157
Checklist
- [x] I understand and agree to the Code of Conduct.
- [x] I have listed any major changes in the NEWS.
- [x] I have added
testthat
unit tests totests/testthat
for any new functionality.
Fixes: https://github.com/rstudio/gt/issues/157
Correction, I did not commit the qmd file because these are apparently ignored by the project.
Commit 51da31ed02ea03e7a9cab77d3e56cc46937e6e60 add tests on the latex format for the new case.
I have decided to use the tabular*
environment rather than the tabular
environment. This environment makes it possible to easily adjust the width of the table to conform to the expectation of the table.width
argument in tab_options
. By default floating environment tables will be full width (e.g. 100%) but users can adjust this expectation using either pixels or percentages. This change should not affect any other formatting issues because longtable
and tabular*
both use the same formatting for the interior of the table.
I have also cleaned up all of the issues with testthat
failing because it expected a longtable header and the default is now a tabular.
I have a qmd file that I have used for testing. It won't upload, so I provide it below.
---
title: "latex-15-floating"
format:
pdf:
keep-tex: true
---
```{r}
#| label: setup
#| include: false
library(gt)
```
Lorem ipsum dolor sit amet, ac purus ut. Sit lectus ac, diam, turpis elementum, tempus. Diam eros eget et placerat lectus odio pulvinar ac. Et tristique phasellus feugiat et. Pretium sociosqu. Nisi, sed et dis vel lacus congue aenean turpis, nec. Convallis, et cursus non ligula at at quis diam pulvinar. Nec tempor tempus quis at tincidunt blandit vitae, eros. Neque aliquet vel non auctor posuere, mi aptent. In in dictum ad, nisl eros mollis sed.
Nullam curabitur, sapien. Tellus habitant, neque. Quis vivamus tortor nulla aliquet aliquet. Aliquet leo, tristique viverra pellentesque lectus lacus quis aliquam egestas. Nunc sed et malesuada magnis, luctus. Et, ultricies vitae ad sed. Habitant ante aliquet hendrerit nisl in ac arcu massa, et. Convallis, faucibus ipsum nisl amet aliquam elementum. Imperdiet est habitasse, diam sapien dapibus himenaeos. A erat tellus mollis, tristique in sem erat cras. Cubilia id in sed non risus neque fringilla a mollis magnis nec. Purus egestas, bibendum. At potenti sem curabitur leo elit class in, sem massa et congue.
Sed dapibus feugiat iaculis erat nibh lobortis sagittis eleifend convallis in. Tristique ac ipsum curabitur amet et leo interdum! Donec mus pharetra. Sed, metus non mauris phasellus semper ut pulvinar fermentum sodales. Bibendum mollis dignissim sed, in dui. Pellentesque sed in elit montes sed tincidunt euismod, eros sit ultrices! Etiam at metus ut aenean libero amet. Feugiat facilisis posuere justo quis faucibus vitae nullam nulla. Scelerisque sapien hendrerit lobortis, pulvinar non nostra amet vehicula ac sed. Ex eget eleifend luctus fusce. Tincidunt in pulvinar eget rhoncus eu eget vitae lacus ornare nunc lacinia amet lacus! Eros interdum suscipit aliquet donec tincidunt phasellus est hendrerit.
Taciti ac, nec non ornare amet litora nascetur nunc elementum. Sollicitudin ut sit conubia et habitasse, integer. Egestas egestas cubilia id facilisi. Tempor ut nec facilisi congue, varius tortor. Aliquam viverra a ipsum aptent eu eu. Id purus metus volutpat malesuada et. In nec consequat eros sed nec at mauris ligula. Ut sapien, eget, in in, potenti torquent, semper mollis ligula vel. Sem quisque varius eros lobortis ut ac mus, dui. Senectus mattis eget, himenaeos pellentesque vitae varius adipiscing, taciti justo. Mauris morbi nunc, laoreet a, non, iaculis libero varius dis tincidunt tortor a.
Nibh, amet sit sociis, eros in. Ac odio eros luctus at, mauris, tincidunt. Tristique phasellus etiam vel quis, netus tincidunt, tristique eros. Scelerisque massa integer ac aliquet porta non taciti. In ante, consectetur nullam urna, nunc volutpat pellentesque libero tortor nisi! Ante amet habitasse in est dis pellentesque. Nec turpis quis, natoque sagittis ac vestibulum morbi velit mattis. Purus convallis ac luctus eu purus et egestas.
Consectetur at at magna viverra mauris. Leo augue in nullam laoreet. Eu adipiscing suspendisse eros dis torquent integer et sed tortor ac habitasse et nec. Ullamcorper aliquam maecenas platea et ut integer bibendum justo. Gravida cum est et porttitor ut purus in. Urna magnis ad at senectus in laoreet ut tempor, varius velit. Ac varius lorem, facilisi, blandit, cum ante mauris eu dui enim pharetra. Felis sed odio faucibus netus eget sed vestibulum. Nostra non libero suscipit sed sed egestas. Proin, nulla. Felis suscipit nec pellentesque id gravida ipsum massa. Sagittis consequat quis consequat. Dictumst lectus molestie, est, ut vitae consectetur. Leo mauris eget congue.
Sed dapibus feugiat iaculis erat nibh lobortis sagittis eleifend convallis in. Tristique ac ipsum curabitur amet et leo interdum! Donec mus pharetra. Sed, metus non mauris phasellus semper ut pulvinar fermentum sodales. Bibendum mollis dignissim sed, in dui. Pellentesque sed in elit montes sed tincidunt euismod, eros sit ultrices! Etiam at metus ut aenean libero amet. Feugiat facilisis posuere justo quis faucibus vitae nullam nulla. Scelerisque sapien hendrerit lobortis, pulvinar non nostra amet vehicula ac sed. Ex eget eleifend luctus fusce. Tincidunt in pulvinar eget rhoncus eu eget vitae lacus ornare nunc lacinia amet lacus! Eros interdum suscipit aliquet donec tincidunt phasellus est hendrerit.
```{r}
#| label: tbl-state
#| tbl-cap: Some State Information
#| tbl-pos: "!t"
#| echo: false
temp <- head(state.x77) |>
as.data.frame()
temp <- cbind(State=row.names(head(state.x77)), temp)
temp |>
subset(select=c("State","Population","Income","Frost","Murder")) |>
gt(rowname_col = "State") |>
tab_spanner(label = "Bad Things", columns = c("Frost", "Murder")) |>
fmt_number(decimals = 0, columns = c("Population", "Income")) |>
cols_width(State ~ pct(30)) |>
tab_source_note(md("*Source:* This is a test.")) |>
tab_options(latex.use.longtable = FALSE)
```
See @tbl-state.
aciti ac, nec non ornare amet litora nascetur nunc elementum. Sollicitudin ut sit conubia et habitasse, integer. Egestas egestas cubilia id facilisi. Tempor ut nec facilisi congue, varius tortor. Aliquam viverra a ipsum aptent eu eu. Id purus metus volutpat malesuada et. In nec consequat eros sed nec at mauris ligula. Ut sapien, eget, in in, potenti torquent, semper mollis ligula vel. Sem quisque varius eros lobortis ut ac mus, dui. Senectus mattis eget, himenaeos pellentesque vitae varius adipiscing, taciti justo. Mauris morbi nunc, laoreet a, non, iaculis libero varius dis tincidunt tortor a.
Nibh, amet sit sociis, eros in. Ac odio eros luctus at, mauris, tincidunt. Tristique phasellus etiam vel quis, netus tincidunt, tristique eros. Scelerisque massa integer ac aliquet porta non taciti. In ante, consectetur nullam urna, nunc volutpat pellentesque libero tortor nisi! Ante amet habitasse in est dis pellentesque. Nec turpis quis, natoque sagittis ac vestibulum morbi velit mattis. Purus convallis ac luctus eu purus et egestas.
Consectetur at at magna viverra mauris. Leo augue in nullam laoreet. Eu adipiscing suspendisse eros dis torquent integer et sed tortor ac habitasse et nec. Ullamcorper aliquam maecenas platea et ut integer bibendum justo. Gravida cum est et porttitor ut purus in. Urna magnis ad at senectus in laoreet ut tempor, varius velit. Ac varius lorem, facilisi, blandit, cum ante mauris eu dui enim pharetra. Felis sed odio faucibus netus eget sed vestibulum. Nostra non libero suscipit sed sed egestas. Proin, nulla. Felis suscipit nec pellentesque id gravida ipsum massa. Sagittis consequat quis consequat. Dictumst lectus molestie, est, ut vitae consectetur. Leo mauris eget congue.
Sed dapibus feugiat iaculis erat nibh lobortis sagittis eleifend convallis in. Tristique ac ipsum curabitur amet et leo interdum! Donec mus pharetra. Sed, metus non mauris phasellus semper ut pulvinar fermentum sodales. Bibendum mollis dignissim sed, in dui. Pellentesque sed in elit montes sed tincidunt euismod, eros sit ultrices! Etiam at metus ut aenean libero amet. Feugiat facilisis posuere justo quis faucibus vitae nullam nulla. Scelerisque sapien hendrerit lobortis, pulvinar non nostra amet vehicula ac sed. Ex eget eleifend luctus fusce. Tincidunt in pulvinar eget rhoncus eu eget vitae lacus ornare nunc lacinia amet lacus! Eros interdum suscipit aliquet donec tincidunt phasellus est hendrerit.
Taciti ac, nec non ornare amet litora nascetur nunc elementum. Sollicitudin ut sit conubia et habitasse, integer. Egestas egestas cubilia id facilisi. Tempor ut nec facilisi congue, varius tortor. Aliquam viverra a ipsum aptent eu eu. Id purus metus volutpat malesuada et. In nec consequat eros sed nec at mauris ligula. Ut sapien, eget, in in, potenti torquent, semper mollis ligula vel. Sem quisque varius eros lobortis ut ac mus, dui. Senectus mattis eget, himenaeos pellentesque vitae varius adipiscing, taciti justo. Mauris morbi nunc, laoreet a, non, iaculis libero varius dis tincidunt tortor a.
Nibh, amet sit sociis, eros in. Ac odio eros luctus at, mauris, tincidunt. Tristique phasellus etiam vel quis, netus tincidunt, tristique eros. Scelerisque massa integer ac aliquet porta non taciti. In ante, consectetur nullam urna, nunc volutpat pellentesque libero tortor nisi! Ante amet habitasse in est dis pellentesque. Nec turpis quis, natoque sagittis ac vestibulum morbi velit mattis. Purus convallis ac luctus eu purus et egestas.
Consectetur at at magna viverra mauris. Leo augue in nullam laoreet. Eu adipiscing suspendisse eros dis torquent integer et sed tortor ac habitasse et nec. Ullamcorper aliquam maecenas platea et ut integer bibendum justo. Gravida cum est et porttitor ut purus in. Urna magnis ad at senectus in laoreet ut tempor, varius velit. Ac varius lorem, facilisi, blandit, cum ante mauris eu dui enim pharetra. Felis sed odio faucibus netus eget sed vestibulum. Nostra non libero suscipit sed sed egestas. Proin, nulla. Felis suscipit nec pellentesque id gravida ipsum massa. Sagittis consequat quis consequat. Dictumst lectus molestie, est, ut vitae consectetur. Leo mauris eget congue.
The file that is produced is attached:
@AaronGullickson Apologies for not commenting earlier but thank you for the work done here! We have another PR underway that focuses on LaTeX output (https://github.com/rstudio/gt/pull/1595). It would be good to have that merged first before reviewing this one. (After that one is merged, the merge conflict here could be also addressed.)
@rich-iannone Sounds good. One important thing to consider when reviewing is which case (floating table or longtable) should be the default. I set it up here with floating table as the default, because I think that will be a more common case - i.e. if a table is not too long for a page, it should float by default. However, this will be a breaking change for existing documents in the sense that someone with a multipage table will now have it running off the page. I think its worth it for the long term benefit, but wanted to raise the issue here.
I made some changes to how the wrapping table environment is injected as described in the comments on commit 0e4503d1418260695e7d132ce8ddb258012435f6. Basically, longtable should be surrounded with \begingroup and \endgroup but tabular should be surrounded by \begin{table} and \end{table}. This approach is much saner and makes it easier for both approaches to use things like the font size changes.
I have also updated tests and I think its ready to go. Its failing something with word, but I think that is unrelated to anything I am doing.
I am actually going to make one more change - I want to see if I can give the user the ability to specify the float position with a table option, rather than depend on Quarto.
Ok, never mind about the floating position. When using captions from a code chunk, pandoc injects caption information into the table immediately after the curly closing bracket of \begin{table} which breaks the pre-existing floating position and you just end up with unintentional text in the table. So its better to specify floating position with the code chunk option tbl-pos
which works correctly.
I added a news item and all checks are passing. It should be ready for review.
@AaronGullickson May I ask a question about this PR as an interested user who would love for {gt} to include a float option?
I believe that the tbl-pos
chunk option only works in Quarto. That won't be available to users who want to keep working with RMarkdown or people who want to use {gt} to generate tables that they can upload to Overleaf or use with a local install of TeX Live. Is it possible for those users to specify a floating position without editing the code by hand each time they generate a table?
@kbrevoort Thats a good point. I did try to add that as another tab_option
but found that it will not work correctly in Quarto. Let my try it again and see if it works in R Markdown. I can just add some documentation to the option to tell Quarto users not to do it this way, I suppose.
Just wanted to chime in here and offer that the check_quarto()
function could be used at render time to determine whether the rendering is occurring in a Quarto environment. This could be used in a conditional to enable options that are specific to Quarto.
I added a latex.tbl.pos
option that can be used for floating tables. I used the check_quarto()
function to ensure it does not mess up quarto output. I think the PR should be ready unless you think there is anything more it needs.
Fixed a couple of issues, but some tests are failing in an area I didn't touch so not sure what is going on there.
Also not sure where to do the license thing.
Fixed it. Forgot tests checked the total number of options. Had to increment expectations.
Thank you @AaronGullickson. This new option latex.use.longtable = TRUE
removes the \begin{table}
environment (which is great) and changes tabular*
into a longtable
tabular. Would it be straightforward to add a similar option, something like latex.float
(that defaults to TRUE), which, if set to FALSE, will simply remove the \begin{table}
and caption but keep the tabular*
?
In #157, I was thinking of use-cases where it's up to the user to generate the correct captions and environment for a non-floating table.
Thank you @AaronGullickson. This new option
latex.use.longtable = TRUE
removes the\begin{table}
environment (which is great) and changestabular*
into alongtable
tabular. Would it be straightforward to add a similar option, something likelatex.float
(that defaults to TRUE), which, if set to FALSE, will simply remove the\begin{table}
and caption but keep thetabular*
?In #157, I was thinking of use-cases where it's up to the user to generate the correct captions and environment for a non-floating table.
It should be easier to set something like this once this PR is merged.
The option to convert from a longtable to a table environment is a great feature that will make gt much more compatible with latex! I've noticed that adding a title using tab_header()
will cause an error when knitting to pdf. Inserting a title:
head(state.x77) |>
as.data.frame() |>
gt() |>
tab_options(latex.use.longtable = FALSE) |>
tab_header(title = "this is a title") |>
gtsave("latex-table.tex")
essentially creates an unnumbered caption that is placed within the tabular environment which causes the error:
\begin{table}[!t]
\fontsize{12.0pt}{14.4pt}\selectfont
\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}rrrrrrrr}
\caption*{
{\large this is a title}
} \\
\toprule
Population & Income & Illiteracy & Life Exp & Murder & HS Grad & Frost & Area \\
\midrule\addlinespace[2.5pt]
3615 & 3624 & 2.1 & 69.05 & 15.1 & 41.3 & 20 & 50708 \\
365 & 6315 & 1.5 & 69.31 & 11.3 & 66.7 & 152 & 566432 \\
2212 & 4530 & 1.8 & 70.55 & 7.8 & 58.1 & 15 & 113417 \\
2110 & 3378 & 1.9 & 70.66 & 10.1 & 39.9 & 65 & 51945 \\
21198 & 5114 & 1.1 & 71.71 & 10.3 & 62.6 & 20 & 156361 \\
2541 & 4884 & 0.7 & 72.06 & 6.8 & 63.9 & 166 & 103766 \\
\bottomrule
\end{tabular*}
\end{table}
placing the caption just outside the tabular environment, for instance just after\begin{table}
solves the issue:
\begin{table}[!t]
\caption*{
{\large this is a title}
}
\fontsize{12.0pt}{14.4pt}\selectfont
\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}rrrrrrrr}
\toprule
Population & Income & Illiteracy & Life Exp & Murder & HS Grad & Frost & Area \\
\midrule\addlinespace[2.5pt]
3615 & 3624 & 2.1 & 69.05 & 15.1 & 41.3 & 20 & 50708 \\
365 & 6315 & 1.5 & 69.31 & 11.3 & 66.7 & 152 & 566432 \\
2212 & 4530 & 1.8 & 70.55 & 7.8 & 58.1 & 15 & 113417 \\
2110 & 3378 & 1.9 & 70.66 & 10.1 & 39.9 & 65 & 51945 \\
21198 & 5114 & 1.1 & 71.71 & 10.3 & 62.6 & 20 & 156361 \\
2541 & 4884 & 0.7 & 72.06 & 6.8 & 63.9 & 166 & 103766 \\
\bottomrule
\end{tabular*}
\end{table}
If this is a known issue, please mark my comment as outdated.
@AaronGullickson if you want, could you fix merge conflicts. If @rich-iannone agrees, we could take this early for the upcoming 0.12.0 release to be able to test it more thoroughly
@olivroy I have fixed the conflicts. I also added some code by @nielsbock that addresses a captioning problem for floating table, namely that tab_header
did not put the caption in the correct place for floating tables. That now works. I believe the PR is good to go. I guess I need to resign the license?
Maybe some \endhead \endfirsthead \endfoot \endlastfoot could be placed strategically From Ch 3 https://mirrors.ibiblio.org/CTAN/macros/latex/required/tools/longtable.pdf
I don't know LaTeX that much, and this feels a bit like trial and error to me.
@olivroy There are a couple of open issues about headers & captions not repeating on tables that span multiple pages (#1630, #1061). The reason I haven't submitted a PR on that myself is my sense is that, while having the column headers repeat on each page would be very easy, repeating the captions (i.e., "Table 1: My Table Title (continued)" on subsequent pages) would be trickier. I'm confident it's doable (and if someone wants to get to it before I do, great!) but it's probably worthy of its own PR.
It's also a very different issue than what @AaronGullickson is addressing. This PR provides an alternative environment to longtable with many benefits for users (myself included). But one thing the new floating environment can't do is span multiple pages. It's not my call, but I recommend not holding up this PR because it doesn't fix an unrelated issue. (We can work on #1630 and #1061, and hopefully, the change that @kuriwaki was asking for while people kick the tires of the new environment.)
I hope that helps. @AaronGullickson, thanks for implementing a floating environment. I'm looking forward to trying it out once it's merged!
@kbrevoort thanks for the explanations!
@AaronGullickson thanks for sticking with it
@rich-iannone I recommend merging this and possibly have longtable be the default at some point. The fact that long tables can now be broken up across pages instead of overflowing is a nice improvement! (EDIT: given the resolution of https://github.com/rstudio/gt/pull/1588#discussion_r1673389794 if possible)
@AaronGullickson if you could make that tab_header()
fix, then I think we could proceed with merging this PR.
Thanks for all of this helpful feedback. I am away for a few days built will make the corrections first thing Monday.
@AaronGullickson when you take this on, please merge my PR first https://github.com/AaronGullickson/gt/pull/4. I took care of resolving some merge conflicts.
I think I have made all the requested changes. I changed the option name from latex.use.longtable
to latex.use_longtable
. I also uncommented the line I commented out in one of the Rmd files. It renders fine for me locally using either the table
or longtable
environment.
One last issue to consider is the default environment. Currently, this PR uses a floating table environment as the default. I would argue that this is the best approach because tables that are not longer than a page should be floated for the best results. Thus, longtable is only really needed for long tables, as the name implies. This does however change the environment from its previous default (and only environment) which was longtable, which could cause some confusion when users re-render existing documents (for example).
Thanks again @AaronGullickson for all your time and patience with this. We're finally going to merge this PR; will do so after all CI runs pass!