Skip to content
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

Render tables as gtable #1563

Merged
merged 15 commits into from
Feb 5, 2024
Merged

Render tables as gtable #1563

merged 15 commits into from
Feb 5, 2024

Conversation

teunbrand
Copy link
Contributor

Summary

This PR aims to fix #180 and fix #961, and conflicts with #1015.
Briefly, it adds a render_as_gtable() function that builds the table as a gtable.

Related GitHub Issues and PRs

Checklist

Description

I was asked by @thomasp85 whether I could take a stab at #180, so that patchwork and other (gg)plot composition packages could integrate nice tables that {gt} offers. This PR adds a method for rendering tables in the grid graphics system. It consists of two important parts.

  1. Getting a layout of the table.
  2. Rendering the cells.

Table layout

For {gtable}, we need to know the layout of the table in advance. For every cell in the table, we need to know the left/right/top/bottom cell in the gtable it occupies, similar to the rowspan and colspan attributes determine in html how wide or tall a cell is. I'm not proficient enough in html to parse the table layout from the html that the render_as_html() function produces. Instead, the layout is computed separately from, but analogous to, the html code. All the important layout code is captured in the create_{part}_component_g() functions ('g' for grid or gtable), that mirror the create_{part}_component_h() ('h' for html).

Rendering cells

For rendering cells, I've opted for following the css classes and styles that are used in the html output. These are parsed and translated to graphical parameters that {grid} understands. Mostly, this requires a bunch of parsers and a way to consolidate different classes/styles, but it works well enough for most cases. Of course, not all html/css features can be translated to grid, like overflow-x, collapsing borders, automatic text wrapping etc. These are thus not implemented. In addition, if the cell text itself contains html, this is not natively understood by grid. To make this slightly less of a pain, render_as_gtable() has a text_grob argument that can take e.g. gridtext::richtext_grob() to render the html in the cells better than vanilla grid would.

Current state

I think I'm finished with all the internal cogs and gears, there are just a few dangling ends that still need to be taken care of:

  • The vertical spacing seems a bit off compared to the html. @thomasp85, should we take extra care around font ascents and descents in any way?
  • How should we expose this to users? Currently, render_as_gtable(), like render_as_html() is an internal function.

Here is a small demo what it looks like:

devtools::load_all("~/packages/gt")
#> ℹ Loading gt

table <- pizzaplace |>
  dplyr::filter(type %in% c("classic", "veggie")) |>
  dplyr::group_by(type, size) |>
  dplyr::summarize(
    sold = dplyr::n(),
    income = sum(price),
    .groups = "drop"
  ) |>
  gt(rowname_col = "size", groupname_col = "type") |>
  tab_header(title = "Pizzas Sold in 2015") |>
  fmt_integer(columns = sold) |>
  fmt_currency(columns = income) |>
  summary_rows(
    fns = list(label = "All Sizes", fn = "sum"),
    side = c("top"),
    fmt = list(
      ~ fmt_integer(., columns = sold),
      ~ fmt_currency(., columns = income)
    )
  ) |>
  tab_options(
    summary_row.background.color = "gray95",
    row_group.as_column = TRUE
  ) |>
  tab_stub_indent(
    rows = everything(),
    indent = 2
  ) |>
  grand_summary_rows(
    columns = c("sold", "income"),
    fns = list(Sum ~ sum(.)),
    fmt = ~ fmt_number(.)
  ) |>
  tab_caption("Here be caption text") |>
  tab_spanner(
    label = "Spanner",
    columns = c("sold", "income")
  ) |>
  tab_stubhead("Stubhead label") |>
  tab_source_note("Source: the pizzaria") |>
  tab_footnote("Pineapples not included")

gtable <- render_as_gtable(table)
plot(gtable)

Created on 2024-02-01 with reprex v2.1.0

Copy link
Collaborator

@olivroy olivroy left a comment

Choose a reason for hiding this comment

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

This looks exciting! I will test this, here are some minor suggestions for tests.

tests/testthat/test-utils_render_grid.R Outdated Show resolved Hide resolved
tests/testthat/test-utils_render_grid.R Outdated Show resolved Hide resolved
tests/testthat/test-utils_render_grid.R Outdated Show resolved Hide resolved
tests/testthat/test-utils_render_grid.R Outdated Show resolved Hide resolved
@rich-iannone
Copy link
Member

@teunbrand , this is excellent! Thank you so much for taking this on.

@rich-iannone
Copy link
Member

Because we're still testing older R versions, we're stuck with using %>% in our tests.

@teunbrand
Copy link
Contributor Author

Oops, sorry my bad 😅

@rich-iannone
Copy link
Member

@teunbrand do you feel this is ready for a final review (i.e., no more work on your end for the time being)?

@rich-iannone rich-iannone self-requested a review February 2, 2024 18:06
@teunbrand
Copy link
Contributor Author

I think this is almost complete, the only open question is about how this should face the user.
All is working as I think it should, save for a few rare cases where also the html layout is off (for example I ran into #1561 at some point). So yeah, I'm satisfied with the bulk of it, so I think this is ready to be reviewed.

@rich-iannone
Copy link
Member

Somewhat strangely, tests on R 3.6.3 aren't passing because of something new. Looks like a problem both on the gt side and possibly with gtable (and maybe stemming from the gt column widths problem?). I will investigate this in a short while.

@teunbrand
Copy link
Contributor Author

Part of the issue might be the old v1 units {grid} had before R 4.0.0, back when unit(10, "pt") + unit(10, "pt") was stored as a delayed operation sum(unit(10, "pt"), unit(10, "pt")) instead of unit(20, "pt"). I've skipped these tests on old R versions for now, but it shouldn't affect the layout of the table, it is just much harder to do unit() tests. If {gt} follows tidyverse R version support rules, this should cease being an issue in April.

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 rich-iannone merged commit fd91088 into rstudio:master Feb 5, 2024
12 checks passed
@rich-iannone rich-iannone mentioned this pull request May 18, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convert gt to grob for cowplot + patchwork Feature request: combine gt with cowplot or patchwork
3 participants