Interactive table with inline labels, images and charts



Interactive table made in R with reactable that has labels, images and charts.
This blogpost guides you through the construction of this table made by szymanskir.

Table section Data to Viz

About


This page showcases the work of szymanskir that introduces packages to make ggplot2 plots more beautiful. You can find the original code and where the data are from on this repo.

Load packages


Let’s start by loading the packages needed to build the table:

library(magrittr)
library(reactable)
library(htmlwidgets)
library(htmltools)
library(echarts4r)


Load and prepare the dataset


Today’s data can be easily accessed on the github of the gallery by using the following code:

# REMOVE WHEN MERGING
# url = 'https://raw.githubusercontent.com/holtzy/R-graph-gallery/master/DATA/tidyverse-packages.RDS'
# data_table <- readRDS(url)
data_table <- readRDS("DATA/tidyverse-packages.RDS")


Create the table


The table mainly relies on the reactable package and some cool html/css/javascript features.

First, we need to define a few functions.

create_history_download_chart <- function(downloads_data) {
  echarts4r::e_chart(downloads_data, x = start, height = "80px", width = "256px") %>% 
    echarts4r::e_line(downloads, symbol = "none") %>% 
    echarts4r::e_legend(show = FALSE) %>% 
    echarts4r::e_axis(axis = "y", show = FALSE) %>% 
    echarts4r::e_tooltip(
      trigger = "axis",
      
      # Creates custom tooltip content
      formatter = htmlwidgets::JS("
        function(params) {
          const downloadsDate = params[0].value[0];
          const downloadsDateLabel = new Date(downloadsDate).
            toLocaleString('en-Us', { month: 'short', year: 'numeric' });
          
          const downloadsCount = params[0].value[1];
          const downloadsCountLabel = echarts.format.addCommas(params[0].value[1]);
          return `
            ${downloadsDateLabel}
            <br/>
            ${downloadsCountLabel}
          `;
        }
      ")
    ) %>% 
    # Prevents the plot toolitp from being hidden in case
    # it goes over the table boundaries
    htmlwidgets::onRender(
      htmlwidgets::JS("
        function(el, x) {
          el.parentElement.style.overflow = 'visible';
        }
      ")
    )
}
number_value_icon_cell <- function(value, icon) {
  htmltools::div(
    style = "
      display: flex;
      justify-content: flex-end
    ",
    htmltools::img(
      src = knitr::image_uri(icon),
      height = "24px",
      width = "24px",
      style = "
        vertical-align: bottom;
        margin-right: 4px;
      "
    ),
    value
  )
}

create_table():

create_table <- function(all_data) {
  table <- reactable::reactable(
    all_data,
    defaultColDef = reactable::colDef(
      headerStyle = "text-align: center"
    ),
    columns = list(
      hex_sticker = reactable::colDef(
        name = "",
        cell = function(value, row_index) {
          row_data <- all_data[row_index, ]
          
          # Display Hex along with title and
          # repository description
          htmltools::div(
            style = "
              display: flex; 
              align-items: center; 
            ",
            htmltools::div(
              htmltools::img(
                src = value,
                alt = glue::glue("Hex sticker of {row_data$package_name}"),
                height = "80px"
              )
            ),
            htmltools::div(
              style = "
                margin-left: 24px
              ",
              htmltools::div(
                style = "
                  font-weight: 700;
                ",
                htmltools::a(
                  style = "
                    color: #38577f;
                  ",
                  class = "package_repo_url",
                  href = row_data$repo_url,
                  target = "_blank",
                  row_data$package_name
                ),
              ),
              htmltools::div(
                style = "
                  font-size: 14px;
                  font-style: italic;
                  color: hsl(201, 23%, 34%);
                  line-height: 20px
                ",
                row_data$description
              )
            )
          )
        },
        width = 384
      ),
      
      # Hide unused columns
      repo_url = reactable::colDef(show = FALSE),
      package_name = reactable::colDef(show = FALSE),
      description = reactable::colDef(show = FALSE),
      
      # Render repo stats in form of <ICON> <NUMBER>
      stargazers = reactable::colDef(
        name = "Stargazers",
        cell = function(value) {
          number_value_icon_cell(value, "img/graph/star.svg")
        },
        width = 112
      ),
      watchers = reactable::colDef(
        name = "Watchers",
        cell = function(value) {
          number_value_icon_cell(value, "img/graph/eye.svg")
        },
        width = 112
      ),
      tags = reactable::colDef(
        name = "Tags",
        cell = function(value) {
          number_value_icon_cell(value, "img/graph/tag.svg")
        },
        width = 80
      ),
      download_stats = reactable::colDef(
        name = "Monthly Downloads",
        cell = function(value) {
          create_history_download_chart(value)
        },
        width = 256,
        style = "overflow: visible;"
      ),
      
      # Display contributor avatars as image bubbles
      top_5_contributors = reactable::colDef(
        name = "Top Five Contributors",
        cell = function(value) {
          htmltools::div(
            purrr::map(
              value,
              ~htmltools::span(
                htmltools::img(
                  src = .x$avatar_url,
                  alt = glue::glue("GitHub avatar of {.x$login}"),
                  class = "contributor",
                  height = "44px", 
                  style = "
                    border-radius: 100%;
                    box-shadow: rgba(27, 31, 36, 0.15) 0px 0px 0px 1px
                  "
                ) %>% htmltools::a(href = .x$html_url, target="_blank") %>% 
                  htmltools::tags$abbr(title = .x$login)
              )
            )
          )
        },
        width = 256
      )
    ),
    
    # Center Vertically the content of each column
    # and use custom font
    theme = reactable::reactableTheme(
      cellStyle = list(
        display = "flex",
        flexDirection = "column",
        justifyContent = "center"
      ),
      style = list(
        fontFamily = "Lato, sans-serif"
      )
    ),
    fullWidth = FALSE,
    sortable = FALSE
  ) %>% 
    # Add titleto the table
    htmlwidgets::prependContent(
      htmltools::div(
        style = "
          width: 1200px;
        ",
        htmltools::div("Tidyverse in Numbers",
                       style = "padding: 0px 4px; font-size: 28px; font-weight: 700; font-family: Lato, sans-serif;")
      )
    )
  
  
  # Include custom styling as it's impossible to
  # use the :hover in inline CSS
  htmltools::browsable(htmltools::tagList(
    htmltools::tags$head(
      htmltools::tags$style("
        .contributor {
          margin: 0px 2px;
          transition: all 0.2s ease-in-out;
        }
        
        .contributor:hover {
          transform: translateY(-6px);
          transition: all 0.2s ease-in-out;
        }
        
        .package_repo_url {
          text-decoration: none;
        }
        
        .package_repo_url:hover {
          text-decoration: underline;
        }
      ")
    ),
    return(table)
  ))
}

Now we just have to create the table thanks to our functions:

create_table(data_table)
Tidyverse in Numbers

Conclusion

This post explains how to reproduce an interactive table built with reactable by szymanskir. The table has very nice features such as images when CSS features, interactive charts and statistics.

If you want to learn more about reactable, check out the dedicated section. Check also the table section


Related chart types


Barplot
Spider / Radar
Wordcloud
Parallel
Lollipop
Circular Barplot



❀️ 10 best R tricks ❀️

πŸ‘‹ After crafting hundreds of R charts over 12 years, I've distilled my top 10 tips and tricks. Receive them via email! One insight per day for the next 10 days! πŸ”₯