close
close

first Drop

Com TW NOw News 2024

Creating interactive tables with reactable
news

Creating interactive tables with reactable

(This article was first published on Albert Rappand kindly contributed to R-bloggers). (You can report issue about the content on this page here)


Want to share your content on R-bloggers? click here if you have a blog, or here if you don’t.

The R programming language has a rich ecosystem of packages that are fantastic for creating beautiful production-grade tables from within R. Today, I’m showing you that one package that makes it really easy (mostly) to create interactive tables. Namely, I’m going to show you {reactable}. Creating interactive tables with reactable

If you want to see a video version of this blog post, you can find it on YouTube:

Fake data

Let’s first create a dummy data set using one of {gt}‘s built-in data sets. {gt} has a lot of those so we might as well use it even if we don’t use {gt} to create the table in the end.

library(tidyverse) 
library(reactable) 
hawaiian_sales <- gt::pizzaplace |>  
  filter(name == 'hawaiian') |>  
  mutate( 
    month = month( 
      date, label = TRUE, abbr = FALSE, 
      locale="en_US.UTF-8" # English month names 
    ), 
    quarter = paste0('Q', quarter(date)) 
  ) |>  
  summarise( 
    sales = n(), 
    revenue = sum(price), 
    .by = c(month, quarter) 
  ) 
hawaiian_sales 
## # A tibble: 12 × 4 
##    month     quarter sales revenue 
##    <ord>     <chr>   <int>   <dbl> 
##  1 January   Q1        185   2443. 
##  2 February  Q1        198   2633  
##  3 March     Q1        217   2878. 
##  4 April     Q2        219   2868. 
##  5 May       Q2        198   2688  
##  6 June      Q2        189   2564. 
##  7 July      Q3        195   2620. 
##  8 August    Q3        201   2679. 
##  9 September Q3        196   2616. 
## 10 October   Q4        188   2515. 
## 11 November  Q4        227   2953. 
## 12 December  Q4        209   2817.

Base Layer

To create a table all we have to is to pass the data to the reactable function.

reactable(hawaiian_sales)

The nice thing is that this is interactive out of the box. By clicking onto the column names, you can sort the rows.

Use better column names

Unlike {gt}the {reactable} package doesn’t allow to change the table step by step by chaining pipes. Instead, you will have to use one of the many arguments of reactable() and helper functions to get things done. For example, to set nicer column names you can use the columns argument with a list of column definitions (using the colDef() helper)

reactable( 
  hawaiian_sales, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef(name="Month"), 
    sales = colDef(name="Sales"), 
    revenue = colDef(name="Revenue") 
  ) 
)

Title & Subtitle

For adding a nice title and subtitle to your plot, you can either use some custom HTML & CSS tricks or you just use the {reactablefmtr} package.

reactable( 
  hawaiian_sales, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef(name="Month"), 
    sales = colDef(name="Sales"), 
    revenue = colDef(name="Revenue") 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Format numbers

The numbers in the revenue column correspond to dollar amounts. We can format them by specifying a column format inside of colDef() with help from the colFormat() helper function.

reactable( 
  hawaiian_sales, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef(name="Month"), 
    sales = colDef(name="Sales"), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE) 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Add groups

Now, I want to structure my tables into quarters. The easiest way to do that is to use the quarter column in our data set for grouping. The cool thing about {reactable} is that it’s really easy and the output becomes nicely interactive out of the box. All you have to do is set the groupBy argument.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef(name="Month"), 
    sales = colDef(name="Sales"), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE) 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Add summaries

You can add group summaries by using the aggregate argument inside of colDef() and setting it to one of the built-in aggregate functions like "mean" or "sum". If you want to do something custom, you can do that, but then you will have to write a custom JavaScript function for that.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef(name="Month"), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum" 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum" 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Make table searchable

If we wanted to make our table more interactive, we could make it month column filterable. That way, we can look for particular columns. In that case, it probably makes sense to have the groups unfolded by default.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE, # Expand rows by default 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE  # Make column filterable 
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum" 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum" 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Add a total footer

There are two ways to add a total footer to a column. One, you can use an R function that takes the values ​​and column name as an argument and calculates the result from that. Or two, you can use a JavaScript function to the same thing but dynamically. Let’s first start with the R approach and then see its drawback.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE,  
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE   
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum", 
      footer = function(values, name) { 
        sum(values) 
      } 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum", 
      footer = function(values, name) { 
        sum(values) |> scales::dollar() 
      } 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

But if you filter for January now, you will see that the overall total doesn’t update. That’s not something that R can do for you. That’s where JavaScript comes in. Thankfully, the reactable cookbook gives you the JS code you need.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE  
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total 
      }"), 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) 
      }") 
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Change row styling

Finally, to add a little bit more style and visual structure let us make the group rows blue. Let’s first try to change the theme() argument with the reactableTheme() helper function.

With that we can inject a bit of CSS to our table. The {htmltools} package makes it easy to combine multiple style instructions.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE  
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total 
      }"), 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) 
      }") 
    ) 
  ), 
  theme = reactableTheme( 
    rowGroupStyle = htmltools::css( 
      background =  '#E7EDF3',  
      borderLeft="2px solid #104E8B"  
    ) 
  ) 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Unfortunately, this didn’t do what we wanted. This seems to target all cells in our table because they are all a row of some group. But to only highlight the header row of each group we will have to proceed differently.

For that, we need to write a JavaScript function that takes the rowInfo object as an argument and returns a JSON object with camelCased style properties. Thankfully, the reactable cookbook shows you exactly what you need.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE  
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total 
      }"), 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) 
      }") 
    ) 
  ), 
  rowStyle = JS( 
    "function(rowInfo) { 
      if (rowInfo.level == 0) { // corresponds to row group 
        return {  
          background: '#E7EDF3',  
          borderLeft: '2px solid #104E8B', 
          fontWeight: 600 
        } 
      }  
    }" 
  ), 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

Change footer style

But defining plain CSS without having to wrap it in JS code can still be useful. For example, you could make the footer a bit nicer.

reactable( 
  hawaiian_sales, 
  groupBy = 'quarter', 
  defaultExpanded = TRUE, 
  columns = list( 
    quarter = colDef(name="Quarter"), 
    month = colDef( 
      name="Month", 
      filterable = TRUE  
    ), 
    sales = colDef( 
      name="Sales", 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total 
      }"), 
      footerStyle = htmltools::css( 
        font_weight = 600, 
        border_top = '2px solid black' 
      ) 
    ), 
    revenue = colDef( 
      name="Revenue", 
      format = colFormat(currency = 'USD', separators = TRUE), 
      aggregate="sum", 
      footer =  JS("function(column, state) { 
        let total = 0 
        state.sortedData.forEach(function(row) { 
          total += row(column.id) 
        }) 
        return total.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) 
      }"), 
      footerStyle = htmltools::css( 
        font_weight = 600, 
        border_top = '2px solid black' 
      ) 
    ) 
  ), 
  rowStyle = JS( 
    "function(rowInfo) { 
      if (rowInfo.level == 0) { // corresponds to row group 
        return {  
          background: '#E7EDF3',  
          borderLeft: '2px solid #104E8B', 
          fontWeight: 600 
        } 
      }  
    }" 
  ), 
) |>  
  reactablefmtr::add_title( 
    title="Hawaiian Pizza Sales in 2015" 
  ) |>  
  reactablefmtr::add_subtitle( 
    subtitle="Based on the fake pizzaplace data from `{gt}`", 
    font_weight="normal" 
  )

Hawaiian Pizza Sales in 2015

Based on the fake pizza place data from `{gt}`

To leave a comment for the author, please follow the link and comment on their blog: Albert Rapp.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you’re looking to post or find an R/data-science job.


Want to share your content on R-bloggers? click here if you have a blog, or here if you don’t.

Continue reading: Creating interactive tables with reactable