2

Is there a way to create a table inside a Quarto document without applying the doc's theme to the HTML table.

Usually I use as_raw_html() to avoid the Quarto style. But in my current use-case, my table contains a custom ggplot which is removed when I use as_raw_html().

I've tried using custom output class but this didn't take any effect on the table output.

Reprex

library(tidyverse)
library(gt)

filtered_penguins <- palmerpenguins::penguins |>
    filter(!is.na(sex))

penguin_weights <- palmerpenguins::penguins |>
  filter(!is.na(sex)) |>
  group_by(species) |>
  summarise(
    Min = min(body_mass_g),
    Mean = mean(body_mass_g) |> round(digits = 2),
    Max = max(body_mass_g)
  ) |>
  mutate(species = as.character(species), Distribution = species) |>
  rename(Species = species)


plot_density_species <- function(species, variable) {
  full_range <- filtered_penguins |>
    pull({{variable}}) |>
    range()

  filtered_penguins |>
    filter(species == !!species) |>
    ggplot(aes(x = {{variable}}, y = species)) +
    geom_violin(fill = 'dodgerblue4') +
    theme_minimal() +
    scale_y_discrete(breaks = NULL) +
    scale_x_continuous(breaks = NULL) +
    labs(x = element_blank(), y = element_blank()) +
    coord_cartesian(xlim = full_range)
}

penguin_weights |>
  gt() |>
  tab_spanner(
    label = 'Penguin\'s Weight',
    columns = -Species
  ) |>
  text_transform(
    locations = cells_body(columns = 'Distribution'),
    # Create a function that takes the a column as input
    # and gives a list of ggplots as output
    fn = function(column) {
      map(column, ~plot_density_species(., body_mass_g)) |>
        ggplot_image(height = px(50), aspect_ratio = 3)
    }
  ) 

Desired Output

This is the output I get outside of a Quarto document. But inside a Quarto doc, the doc's theme is applied to the table. Using as_raw_html() removes the plots.

enter image description here

AlbertRapp
  • 408
  • 2
  • 9

2 Answers2

1

I don't think there's an easy way to 'protect' gt table styles from being overwritten by the doc's styles. However, styles passed to gt::opt_css() have higher specificity and so piping the following will yield the desired result.

|> opt_css(
    css = " 
      table.gt_row {
        border-top-width: 1px;
      }
      table.gt_table thead.gt_col_headings {
        font-weight: normal;
        border-top-color: #D3D3D3;
      }
      table.gt_table th.gt_col_heading {
        font-weight: normal;
      }
      table.gt_table {
        border-bottom-color: #D3D3D3;
        border-bottom-width: 2px;
        border-top-width: 2px;
        width: auto;
      }
      table.gt_table tbody.gt_table_body {
        border-top-color: #D3D3D3;
        border-top-width: 2px;
      }"
  )

Result:

enter image description here

Martin C. Arnold
  • 9,483
  • 1
  • 14
  • 22
  • 1
    Thank you. This is what I feared actually. Having to rely solely on CSS defeats the purpose of any styling with tab_style() and tab_options(). – AlbertRapp Nov 08 '22 at 13:59
1

As is documented on GitHub now, the current dev version of {gt} makes it possible to use as_raw_html().

However, there are still some small deviations in the Quarto output. Once step in the right direction is to reset all previous styles with a div that contains style="all:initial;" e.g.

::: {style="all:initial;"}

# Put code chunk that contains call to as_raw_html() here.

:::

But there seems to be some issues with line formatting as I've documented here. So until that is resolved you could add the CSS code manually or try using a function I've built that targets the resulting CSS code and replaces all gt classes with some other name so that Quarto can't target it. It probably won't work all the time but I like to think that it's better than writing CSS code manually.

make_tbl_quarto_robust <- function(tbl) {
  # Get tbl html code (without the inline stuff)
  tbl_html <- tbl |>
    as_raw_html(inline_css = FALSE) 
  
  # Find table id
  tbl_id <-  str_match(tbl_html, 'id="(.*)"\\s')[,2] 
  
  # Split html so that we only replace strings in the css part at first
  # That's important for performance
  split_html <- tbl_html |> 
    str_split_1('<table class="gt_table".{0,}>')
  css_part <- split_html[1] |> 
    str_split_1('<style>')
  
  # Create regex to add table id
  my_regex <- str_c('(', tbl_id, ' )?(.* \\{)')
  replaced_css <- css_part[2] |>
    # Make global html changes more specific
    str_replace_all('html \\{', str_c(tbl_id, ' .gt_table {')) |> 
    # Make changes to everything specific to the table id
    str_replace_all(my_regex, str_c('\\#', tbl_id, ' \\2')) |> 
    # Replace duplicate names 
    str_replace_all(
      str_c('\\#', tbl_id, ' \\#', tbl_id),
      str_c('\\#', tbl_id)
    )
  
  # Put split html back together
  str_c(
    css_part[1], '<style>', 
    replaced_css, '<table class="gt_table">', 
    split_html[2]
  ) |> 
    # Rename all gt_* classes to new_gt_*
    str_replace_all('(\\.|class="| )gt', '\\1new_gt') |> 
    # Reformat as html
    html()
}
AlbertRapp
  • 408
  • 2
  • 9