Skip to content

Fix Issue1656 #1716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 19, 2024
Merged

Fix Issue1656 #1716

merged 7 commits into from
Jun 19, 2024

Conversation

kbrevoort
Copy link
Collaborator

@kbrevoort kbrevoort commented Jun 19, 2024

Summary

The Issue

Issue #1656 describes Latex behavior in which a column spanner with a long label can override user-selected column widths. For example, consider the following MWE

`

title: "gt_tester.qmd"
format: pdf

#| include: false

library(tidyverse)
#library(gt)
devtools::load_all('~/RCodeFiles/gt')

set.seed(1234)
dt <- tibble(x = c('a', 'b', 'c', 'd', 'e'),
             y = runif(5),
             z = runif(5),
             m = runif(5),
             n = runif(5),
             w = runif(5))
#| echo: false

gt(dt, 
   rowname_col = 'x',
   row_group_as_column = FALSE) |>
  fmt_number(decimals = 3) |>
  tab_row_group(label = 'Only row group',
                rows = 1:2) |>
  cols_width(everything() ~ '2cm') |>
  tab_spanner(label = 'Spanner with a long title that should be wrapped', columns = c('y', 'z')) |>
  tab_spanner(label = 'Spanner2', columns = c('n', 'w')) |>
  tab_spanner(label = 'Another long spanner that needs to wrap even more than the other', columns = c('z', 'm')) |>
  summary_rows(fns = list("mean")) 

`
In this example, each column width is set to 2cm. Yet, some of the spanners contain long labels, Latex overrides the user-requested widths to accommodate them. As a result, the result using the current {gt} version looks as follows.

issue1656-current-gt

The long spanner labels are not wrapped, and the columns are now so wide that the table runs off the page. While Issue1656 presents this as an issue with using column spanners, the same problem results any time a multicolumn function is used that spans more than a single column (when a multicolumn command spans a single column -- which does happen in {gt}'s Latex output -- any specified column width is retained by Latex).

The code in this pull request fixes this using the workaround suggested by @bzkrouse (who filed issue #1656), in which the width of the multicolumn is explicitly specified when it is available. I think that's the best approach. As a result, the same Quarto file above will generate a PDF that looks like this.

issue1656-pull-request

Note that the long column spanner labels now wrap, and the column widths are retained

How the new code works

I've moved a block of code that used to be included in create_heading_component_l, which calculated the width of each column, into a new function create_colwidth_df_l. This function is called near the start of as_latex. It returns a data.frame that has one row for each column in the table and three variables that measure the column width. This data.frame is passed to create_heading_component_l and other functions that use multicolumns.

For each row in colwidth_df, widths are represented in one of three ways. The column width can be unspecified, unspec == 1L. If the column width can be represented in points (the length used by Latex), this is stored in variable pt. The last option is if the width has been expressed as a percentage that cannot be converted to a specific point value, in which case the width is output as a percentage of the available space (represented in Latex as \linewidth). One and only one of the three variables (unspec, pt, and lw) has a nonzero value. (Note, the values stored in colwidth_df are not necessarily those supplied by the user. For example, if the user sets a column's width to "25%" and sets the table width to 6 inches, colwidth stores the width as 1.5 inches converted to points.). When outputting a multicolumn statement, {gt} combines the column widths from this table and uses them to set the width of the combined cells.

I also made a semi-related change that's worth noting. In the MWE above, if row_group_as_column == TRUE, the current {gt} code results in an error ("Extra alignment tab has been changed to \cr."). I've corrected this behavior in this pull request and added a multirow command that allows the group label to overlap the rows of the group, as in the screenshot below.

issue1656-group-col

As part of this change, I have added the multirow Latex package to the list of required Latex packages.

Related GitHub Issues and PRs

Fixes: #1656

Checklist

Kenneth Brevoort and others added 3 commits June 17, 2024 01:25
When available, assign an explicit width to multicolumn statements to comply with user-specified column widths.
@CLAassistant
Copy link

CLAassistant commented Jun 19, 2024

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 3 committers have signed the CLA.

✅ rich-iannone
❌ Kenneth Brevoort
❌ kbrevoort


Kenneth Brevoort seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@rich-iannone
Copy link
Member

This looks great @kbrevoort ! I’ll review this shortly.

Copy link
Member

@rich-iannone rich-iannone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@rich-iannone
Copy link
Member

This looks great! Let me know if this is finalized (i.e., no more commits coming) and I'll then merge.

@rich-iannone
Copy link
Member

I'm assuming this is good to go, so I'll merge right now. Thanks!

@rich-iannone rich-iannone merged commit 0e50edd into rstudio:master Jun 19, 2024
11 of 12 checks passed
@nielsbock
Copy link
Contributor

Great work!

I've noticed issues when using pct as unit:

Setting column widths to percent off by a factor of 100:

#| echo: false

gt(dt, 
   rowname_col = 'x',
   row_group_as_column = FALSE) |>
  fmt_number(decimals = 3) |>
  tab_row_group(label = 'Only row group',
                rows = 1:2) |>
  cols_width(z ~ pct(10),
             y ~ pct(20),
             n ~ pct(10),
             m ~ pct(10),
             w ~ pct(10),) |>
  tab_spanner(label = 'Spanner with a long title that should be wrapped', columns = c('y', 'z')) |>
  tab_spanner(label = 'Spanner2', columns = c('n', 'w')) |>
  tab_spanner(label = 'Another long spanner that needs to wrap even more than the other', columns = c('z', 'm')) |>
  summary_rows(fns = list("mean")) 

Intermediate Latex code:

\begingroup
\fontsize{12.0pt}{14.4pt}\selectfont
\begin{longtable*}{l|>{\raggedleft\arraybackslash}p{\dimexpr 0.2\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}}
\toprule
 &  & \multicolumn{2}{>{\centering\arraybackslash}m{20.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Another long spanner that needs to wrap even more than the other} &  &  \\ 
\cmidrule(lr){3-4}
 & \multicolumn{2}{>{\centering\arraybackslash}m{30.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Spanner with a long title that should be wrapped} &  & \multicolumn{2}{>{\centering\arraybackslash}m{20.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Spanner2} \\ 
\cmidrule(lr){2-3} \cmidrule(lr){5-6}
 & y & z & m & n & w \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{\linewidth}}{Only row group} \\[2.5pt] 
\midrule\addlinespace[2.5pt]
a & 0.114 & 0.640 & 0.694 & 0.837 & 0.317 \\ 
b & 0.622 & 0.009 & 0.545 & 0.286 & 0.303 \\ 
\midrule 
mean & 0.3680014 & 0.3249032 & 0.6192831 & 0.5617595 & 0.3096529 \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{\linewidth}}{\rule{0pt}{0pt}} \\[-3.2ex] 
\midrule\addlinespace[2.5pt]
c & 0.609 & 0.233 & 0.283 & 0.267 & 0.159 \\ 
d & 0.623 & 0.666 & 0.923 & 0.187 & 0.040 \\ 
e & 0.861 & 0.514 & 0.292 & 0.232 & 0.219 \\ 
\bottomrule
\end{longtable*}
\endgroup

setting table.width with percentage or inches will also affect units:

table.width = pct(100):

#| echo: false

gt(dt, 
   rowname_col = 'x',
   row_group_as_column = FALSE) |>
  fmt_number(decimals = 3) |>
  tab_row_group(label = 'Only row group',
                rows = 1:2) |>
  cols_width(z ~ pct(10),
             y ~ pct(20),
             n ~ pct(10),
             m ~ pct(10),
             w ~ pct(10)) |>
  tab_options(table.width = pct(100)) |> 
  tab_spanner(label = 'Spanner with a long title that should be wrapped', columns = c('y', 'z')) |>
  tab_spanner(label = 'Spanner2', columns = c('n', 'w')) |>
  tab_spanner(label = 'Another long spanner that needs to wrap even more than the other', columns = c('z', 'm')) |>
  summary_rows(fns = list("mean")) |>  gtsave("spannerwidth.tex")

Intermediate LaTex code:

\begingroup
\setlength\LTleft{0\linewidth}
\setlength\LTright{0\linewidth}\fontsize{12.0pt}{14.4pt}\selectfont
\begin{longtable}{@{\extracolsep{\fill}}l|>{\raggedleft\arraybackslash}p{\dimexpr 0.2\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 0.1\linewidth-2\tabcolsep-1.5\arrayrulewidth}}
\toprule
 &  & \multicolumn{2}{>{\centering\arraybackslash}m{2000.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Another long spanner that needs to wrap even more than the other} &  &  \\ 
\cmidrule(lr){3-4}
 & \multicolumn{2}{>{\centering\arraybackslash}m{3000.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Spanner with a long title that should be wrapped} &  & \multicolumn{2}{>{\centering\arraybackslash}m{2000.00\linewidth -2\tabcolsep-1.5\arrayrulewidth}}{Spanner2} \\ 
\cmidrule(lr){2-3} \cmidrule(lr){5-6}
 & y & z & m & n & w \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{100%}}{Only row group} \\[2.5pt] 
\midrule\addlinespace[2.5pt]
a & 0.114 & 0.640 & 0.694 & 0.837 & 0.317 \\ 
b & 0.622 & 0.009 & 0.545 & 0.286 & 0.303 \\ 
\midrule 
mean & 0.3680014 & 0.3249032 & 0.6192831 & 0.5617595 & 0.3096529 \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{100%}}{\rule{0pt}{0pt}} \\[-3.2ex] 
\midrule\addlinespace[2.5pt]
c & 0.609 & 0.233 & 0.283 & 0.267 & 0.159 \\ 
d & 0.623 & 0.666 & 0.923 & 0.187 & 0.040 \\ 
e & 0.861 & 0.514 & 0.292 & 0.232 & 0.219 \\ 
\bottomrule
\end{longtable}
\endgroup

table.width = "6in":

#| echo: false

gt(dt, 
   rowname_col = 'x',
   row_group_as_column = FALSE) |>
  fmt_number(decimals = 3) |>
  tab_row_group(label = 'Only row group',
                rows = 1:2) |>
  cols_width(z ~ pct(10),
             y ~ pct(20),
             n ~ pct(10),
             m ~ pct(10),
             w ~ pct(10)) |>
  tab_options(table.width = "6in") |> 
  tab_spanner(label = 'Spanner with a long title that should be wrapped', columns = c('y', 'z')) |>
  tab_spanner(label = 'Spanner2', columns = c('n', 'w')) |>
  tab_spanner(label = 'Another long spanner that needs to wrap even more than the other', columns = c('z', 'm')) |>
  summary_rows(fns = list("mean")) |>  gtsave("spannerwidth.tex")

Intermediate Latex code:

\begingroup
\setlength\LTleft{\dimexpr(0.5\linewidth - 216pt)}
\setlength\LTright{\dimexpr(0.5\linewidth - 216pt)}\fontsize{12.0pt}{14.4pt}\selectfont
\begin{longtable}{@{\extracolsep{\fill}}l|>{\raggedleft\arraybackslash}p{\dimexpr 86.4pt-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 43.2pt-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 43.2pt-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 43.2pt-2\tabcolsep-1.5\arrayrulewidth}>{\raggedleft\arraybackslash}p{\dimexpr 43.2pt-2\tabcolsep-1.5\arrayrulewidth}}
\toprule
 &  & \multicolumn{2}{>{\centering\arraybackslash}m{8640.00pt -2\tabcolsep-1.5\arrayrulewidth}}{Another long spanner that needs to wrap even more than the other} &  &  \\ 
\cmidrule(lr){3-4}
 & \multicolumn{2}{>{\centering\arraybackslash}m{12960.00pt -2\tabcolsep-1.5\arrayrulewidth}}{Spanner with a long title that should be wrapped} &  & \multicolumn{2}{>{\centering\arraybackslash}m{8640.00pt -2\tabcolsep-1.5\arrayrulewidth}}{Spanner2} \\ 
\cmidrule(lr){2-3} \cmidrule(lr){5-6}
 & y & z & m & n & w \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{6in}}{Only row group} \\[2.5pt] 
\midrule\addlinespace[2.5pt]
a & 0.114 & 0.640 & 0.694 & 0.837 & 0.317 \\ 
b & 0.622 & 0.009 & 0.545 & 0.286 & 0.303 \\ 
\midrule 
mean & 0.3680014 & 0.3249032 & 0.6192831 & 0.5617595 & 0.3096529 \\ 
\midrule\addlinespace[2.5pt]
\multicolumn{6}{>{\raggedright\arraybackslash}m{6in}}{\rule{0pt}{0pt}} \\[-3.2ex] 
\midrule\addlinespace[2.5pt]
c & 0.609 & 0.233 & 0.283 & 0.267 & 0.159 \\ 
d & 0.623 & 0.666 & 0.923 & 0.187 & 0.040 \\ 
e & 0.861 & 0.514 & 0.292 & 0.232 & 0.219 \\ 
\bottomrule
\end{longtable}
\endgroup

@bzkrouse
Copy link

bzkrouse commented Jun 26, 2024

Really appreciate you tackling this, thank you!!!
I've tested using the current dev version of gt, and aside from the comment above, I also noticed a couple of related issues that I can file:

  • If I add a groupname_col to the gt, set row_group_as_column = TRUE, and use percent widths I receive this error: "Error in convert_to_px(): invalid units provided - %. Must be one of type pt, in, cm, emu, em
  • When I render the original example to pdf I see a lot of "-2"s prepended to text in the stub and column labels.

@kbrevoort
Copy link
Collaborator Author

Thank you, @nielsbock & @bzkrouse ! I appreciate you pointing these out so quickly.

@bzkrouse , do you have an example you can share of the second issue you've seen (the -2's)? I haven't been able to reproduce that one.

@nielsbock
Copy link
Contributor

I think the issue is that adding a group header when table width is set to, say, 100%, an unescaped "%" is added in the latex code, which causes an error. I don't have access to a PC right now, so I can't give an example. I tried removing the "%" in the code in my own repo, but even then, the new code for group headers does not work properly.
As far as I'm concerned, the old code for group headers worked fine. I reverted the code in my own repo and I got a nice table after fixing the percentage to units issue I pointed out in my previous comment.

@bzkrouse
Copy link

bzkrouse commented Jul 1, 2024

@bzkrouse , do you have an example you can share of the second issue you've seen (the -2's)? I haven't been able to reproduce that one.

@kbrevoort Thanks for the reply! To be more specific, when I render your example code to pdf within Rmarkdown it produces the -2's. If I render within Quarto, all seems fine.

@rich-iannone
Copy link
Member

@kbrevoort Just to add a bit more context, the -2's are also produced (in the column labels and in the stub) from a console render. It's captured in one of the snapshots (fmt_fraction.md) from this test:

fraction_tbl_diagonal %>% as_latex() %>% as.character() %>% expect_snapshot()

@kbrevoort kbrevoort mentioned this pull request Jul 4, 2024
3 tasks
@kbrevoort
Copy link
Collaborator Author

Thanks, everyone! I've just submitted a PR that should address all of these issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Column spanners do not comply with widths set by cols_widths() for latex tables
6 participants