rstudio / gt

Easily generate information-rich, publication-quality tables from R
https://gt.rstudio.com
Other
1.99k stars 201 forks source link

Align tables from `gt_split()` #1288

Open AlbertRapp opened 1 year ago

AlbertRapp commented 1 year ago

Proposal

It would be cool to put multiple tables from gt_split() next to each other or onto a grid. One way this could be done is to modify the internal print.gt_group function so that it accepts CSS for both the container that contains a single table and the container that contains all tables.

Here are a two drafts I came up with.

Draft 1: Wrap html_tbl_i and the final output into div().

library(gt)
my_print.gt_group <- 
  function (x, css_outer = NULL, css_inner = NULL, ..., view = interactive()) {
    html_tbls <- htmltools::tagList()
    seq_tbls <- seq_len(nrow(x$gt_tbls))
    for (i in seq_tbls) {
        html_tbl_i <- 
          htmltools::div(
            style = css_inner,
            gt:::as.tags.gt_tbl(grp_pull(x, which = i), ...)
          )
        html_tbls <- htmltools::tagList(
          html_tbls, 
          html_tbl_i, 
          if (i != max(seq_tbls)) htmltools::HTML("<br />"))
    }
    htmltools::div(
      style = css_outer,
      html_tbls
    )
}
metro |> 
  dplyr::slice(1:10) |> 
  dplyr::select(name, passengers) |> 
  gt() |> 
  cols_width(name ~ px(250), passengers ~ px(100)) |> 
  gt_split(row_every_n = 5) |> 
  my_print.gt_group(
    css_outer = 'display: flex;',
    css_inner = 'margin-left: auto; margin-right: auto;'
  )

image

For more than two tables one could ad flex-wrap: wrap to the outer div.

metro |> 
  dplyr::slice(1:10) |> 
  dplyr::select(name, passengers) |> 
  gt() |> 
  cols_width(name ~ px(250), passengers ~ px(100)) |> 
  gt_split(row_every_n = 3) |> 
  my_print.gt_group(
    css_outer = 'display: flex; flex-wrap: wrap;',
    css_inner = 'margin-left: auto; margin-right: auto;'
  )

image

Draft 2: Remove line breaks

The previous two approaches do not work if you use display: grid due to the line breaks. Hence, I had to make two changes:

  1. Remove line breaks
  2. Initialize taglist with first table and adjust loop accordingly
grid_print.gt_group <- 
  function (x, css_outer = NULL, css_inner = NULL, ..., view = interactive()) {
    html_tbls <- htmltools::tagList(
      htmltools::div(
        style = css_inner,
        gt:::as.tags.gt_tbl(grp_pull(x, which = 1), ...)
      )
    )
    seq_tbls <- seq_len(nrow(x$gt_tbls))
    for (i in seq_tbls[-1]) {
        html_tbl_i <- 
          htmltools::div(
            style = css_inner,
            gt:::as.tags.gt_tbl(grp_pull(x, which = i), ...)
          )
        html_tbls <- htmltools::tagList(
          html_tbls, 
          html_tbl_i
        )
    }
    htmltools::div(
      style = css_outer,
      html_tbls
    )
}

Then, using display: grid works.

metro |> 
  dplyr::slice(1:10) |> 
  dplyr::select(name, passengers) |> 
  gt() |> 
  cols_width(name ~ px(250), passengers ~ px(100)) |> 
  gt_split(row_every_n = 3) |> 
  grid_print.gt_group(
    css_outer = 'display: grid; grid-template-columns: repeat(2, 1fr);',
  )

image

Remark

In order for this to look good, the width of each column needs to be set with cols_width(). Otherwise, each sub-table will use the minimal necessary width for its data. Thus, if e.g. names are shorter than in the other tables, then that table will not align properly with the other tables. This could maybe be fixed in some iteration but I don't think that it's a big problem to apply cols_width().

rich-iannone commented 1 year ago

Albert, this is wonderful stuff! This is going to be worked on for the next release. And thank you for adding working code that people can use today!

AlbertRapp commented 1 year ago

Thanks, I'm glad that my code is helpful ☺️