News in htmlTable 2.0

A short intro to the new features in htmlTable 2.0. The image is a blend based on a CC image by Ken Xu.

The htmlTable 2.0 package was just released on CRAN! It is my most downloaded package with 160 000+ downloads/month and this update is something that I have been wanting to do for a long time. For those of you that never encountered htmlTable it is a package that takes a `matrix`/`data.frame` and outputs a nicely formatted HTML table. When I created the package there weren’t that many alternatives and knitr was this new thing that everyone was excited about, magrittr with its ubiquitous `%>%` pipe had not even entered the scene. The current update should make it easier to streamline table look, separate layout from content and use tidyverse functionality.

Style and theming

The biggest change in how to use the `htmlTable` is that you have a separate function for adding style to the table. The change is implemented in a non-breaking fashion, i.e. all the old options should work just as before but the new API is encouraged as it simplifies a lot. The new API changes so that instead of all the `css.*` arguments we now have a separate function that applies these, `addHtmlTableStyle` and passes them on as an attribute to the `htmlTable`:

library(htmlTable)
library(magrittr)
options(table_counter = TRUE)
 
rbind(
  `Group A` = c(20, 5, 380, 95),
  `1` = c(11, 55, 9, 45),
  `2` = c(11, 55, 9, 45)
) %>%
  addHtmlTableStyle(css.rgroup = "font-style: italic",
                    css.header = "font-weight: normal") %>% 
  htmlTable(header = rep(c("No", "%"), times = 2),
            n.cgroup = list(c(2), c(2, 2)),
            cgroup = list('Super', c("First", "Second")),
            rgroup = c("", "Group B"),
            n.rgroup = 1,
            caption = "A simple htmlTable example with the core components")

As we usually want the same layout for the entire table we can accomplish the same effect for all of our tables using `setHtmlTableTheme` and the style will be applied to all your tables.

library(glue)
setHtmlTableTheme(css.rgroup = "font-style: italic",
                  css.header = "font-weight: normal",
                  pos.caption = "bottom")
 
 
rbind(
  `Group A` = c(20, 5, 380, 95),
  `1` = c(11, 55, 9, 45),
  `2` = c(11, 55, 9, 45)
) %>%
  htmlTable(header = rep(c("No", "%"), times = 2),
            n.cgroup = list(c(2), c(2, 2)),
            cgroup = list('Super', c("First", "Second")),
            rgroup = c("", "Group B"),
            n.rgroup = 1,
            caption = glue("Same as Table {last} but with general styling and caption positioned at the bottom.",
                           last = tblNoLast()))

There is an option for selecting themes with predefined layouts. In addition to the `standard` which has the traditional `htmlTable` look, you also have `Google docs` and `blank`. The Google docs is still a work in progress and any help making it as compatible as possible with Google’s Drive document when copy-pasting is much appreciated.

Using tidyverse syntax in tidyHtmlTable

In 2017, Stephen Gragg added the `tidyHtmlTable` to the package. Since then advances to RStudio has impacted in how we use R and it became obvious that the function should instead of strings as arguments directly accept column names just as defined by tidyselect. The `tidyHtmlTable` solves one of `htmlTable`’s greates weaknesses, the need for calculating the `rgroup`, `tspanner`, `cgroup` arguments and using it with the `tidyselect` interface is now pure joy:

library(tidyverse)
tribble(
  ~ rowname, ~ `No (First)`, ~ `% (First)`, ~ `No (Second)`, ~ `% (Second)`,
  "Group A",             20,             5,             380,             95,
  "Group B1",            11,            55,               9,             45,
  "Group B2",            11,            55,               9,             45,
) %>%
  pivot_longer(cols = c(starts_with("No"), starts_with("%"))) %>% 
  mutate(group = str_replace(rowname, "Group ([AB]).*", "\\1"),
         rowname = str_replace(rowname, "Group ([AB12]+).*", "\\1"),
         group = if_else(group == rowname, "", group),
         header = str_replace(name, "([^ ]+).*", "\\1"),
         cgroup = str_replace(name, ".*\\(([^)]+)\\)$", "\\1")) %>% 
  tidyHtmlTable(rgroup = group,
                header = header,
                cgroup = cgroup,
                rnames = rowname,
                caption = "A version of the first tables but using the tidyHtmlTable")

A more advanced example on how `tidyHtmlTable` works with `tidyverse` we can have a look at the example in the `vignette(“tidyHtmlTable”)`:

mtcars %>%
  as_tibble(rownames = "rnames") %>% 
  pivot_longer(names_to = "per_metric", 
               cols = c(hp, mpg, qsec)) %>%
  group_by(cyl, gear, per_metric) %>% 
  summarise(Mean = round(mean(value), 1),
            SD = round(sd(value), 1),
            Min = round(min(value), 1),
            Max = round(max(value), 1),
            .groups = 'drop') %>%
  pivot_longer(names_to = "summary_stat", 
               cols = c(Mean, SD, Min, Max)) %>% 
  ungroup() %>% 
  mutate(gear = paste(gear, "Gears"),
         cyl = paste(cyl, "Cylinders")) %>% 
  arrange(per_metric, summary_stat) %>% 
  addHtmlTableStyle(align = "r") %>% 
  tidyHtmlTable(header = gear,
                cgroup = cyl,
                rnames = summary_stat,
                rgroup = per_metric,
                caption = "A full example of how to apply the tidyverse workflow to generate a table")

When using `tidyHtmlTable` you can decouple it from `htmlTable` and provide any table function by supplying the `table_fn` function.

Options

In htmlTable 2.0 there are plenty of options that have been added. Most of them should start with the prefix “htmlTable.”, e.g “htmlTable.css.tspanner.sep”. While the prefix is useful for reducing the risk of conflicting options between packages, options such as “table_counter” are unchanged in order to avoid unnecessary breaking changes.

NEWS for 2.0

  • Added theming and styling with `addHtmlTableStyle` and `setHtmlTableTheme` to reduce the cognitive burden of finding the right option within the docs. Note: this may impact your current tables and hence the major version (2.0.0).
  • Changed so that `css.cell` is properly applied to `rownames`, cell fillers and the actual cells of interest (may impact the final layout!)
  • Breaking change `tidyHtmlTable`: Moved to a fully tidyverse compatible system with `tidyHtmlTable`. This is a breaking change to the API as we switch from columns as strings to `tidyselect` syntax and as `gather`/`spread` have been replaced by `pivot_longer`/`pivot_wider` the default values have been updated in accordance with their defaults, e.g. `rnames = “name”` and `value = “value”`.
  • Breaking change `tidyHtmlTable`: Sorting of rows is skipped as we may have situations with repeating inputs and this can easily be performed pre-function by calling `dplyr::arrange`. This has furthermore the desirable feature that any custom sorting is retained.
  • Added mso-number-format to help (Issue #63) – thanks Rasmus Hertzum
  • `txtRound` can now add `txtInt` when formatting the integer section for easier readability
  • Added `htmlTable` css options – they should all start with `htmlTable.`
  • `pos.caption` now uses match.arg as expected
  • Fixed proper S3 function definition for htmlTable with all the arguments
  • Added `htmlTable.css.border` style option for allowing to choose border style. Also fixed bug with `cgroup` empty cells and vertical border.
  • Added `htmlTable.pretty_indentation` option for skipping the stripping of all the tabs that was required due to old Pandoc bug.
  • Added `attr(x, “html”) <- TRUE` by default and UTF-8 encoding on all outputted strings to mimic the `htmltools::HTML` function behavior.
  • For simple `tibble` output the `tidyHtmlTable` can now be used to choose a column for the `rnames` argument
  • The `print` statement now respects the `chunk_output_type` in Rmd files in RStudio

4 thoughts on “News in htmlTable 2.0

Leave a Reply to Max Gordon Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.