Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Creating custom HTML tables with icons can be a great way to display data. In some
cases, like when we have a few, heterogeneous data points, it is better than
creating charts or using feature-rich table widgets that come with a lot of dependencies
(e.g., {reactable}
, {DT}
, and similar).
In a recent project, I worked on a {shiny}
application that displays a custom, static,
HTML
table with some icons. For this project we needed some icons available
through the {icons}
R
package.
Below is a quick tutorial about how to use {htmltools}
and {icons}
to
create tables with icons, and how to use the icons for Rmd HTML reports and {shiny}
applications.
Dependencies
install.packages("htmltools") # possibly unnecessary remotes::install_github("mitchelloharawild/icons")
Data
For the type of table we are creating here, we want a few data points of different types. For example, if we had to display personal and social media information in a tabular format, we could have something like the list below. We have one person, “Jaime” and we record information about their age, hobby, and twitter account:
jaime <- list( Name = "Jaime", Position = "Researcher", Twitter = "Jaime123", Hobby = "Football" ) jaime ## $Name ## [1] "Jaime" ## ## $Position ## [1] "Researcher" ## ## $Twitter ## [1] "Jaime123" ## ## $Hobby ## [1] "Football"
For now, we’ll work only with this one person list, but you can imagine having many such items in a data frame and indexing this data frame to display data.
Icons
For icons, we’ll use the {icons}
package. We’ll work with awesome
icons,
but with the {icons}
package, we have several other options too:
library(icons) ## ── Installed icons ─────────────────────────────────────────────── icon 0.2.0 ── ## ✖ ionicons ✖ google_material ## ✖ academicons ✖ feather_icons ## ✖ simple_icons ✖ octicons ## ✖ bioicons ✔ awesome 6.3.0
Downloading icon sets is simple, we use icons::download_*
, and the resulting
object is an icon_set
class that we can pass an icon name to obtain the SVG
of the icon:
icons::download_awesome() icons::awesome("twitter")< svg viewBox="0 0 512 512" style="height:1em;position:relative;display:inline-block;top:.1em;" xmlns="http://www.w3.org/2000/svg"> < path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z">
HTML table with icons
First, we add icons to our person list. We convert each item to a list with
two slots, text
and icon
.
jaime <- list( Name = list(text = "Jaime", icon = "user"), Position = list(text = "Researcher", icon = "flask"), Twitter = list(text = "Jaime123", icon = "twitter"), Hobby = list(text = "Football", icon = "futbol") )
Next, we’ll use this list of item lists to generate the HTML for our table:
- We define some CSS styles for the
th
andtd
tags - We use
lapply
to cycle over the elements of our person listjaime
to generate rows (tr
+td
) tags for each item - We wrap the row_tags in a table tag (
tags$table
)
style <- "text-align: left; padding: 10px 25px;" row_tags <- lapply(jaime, function(x) { htmltools::tags$tr( htmltools::tags$td( style = style, icons::icon_style( icons::awesome(name = x[["icon"]]), scale = 1.5, fill = "#5E81AC" ) ), htmltools::tags$td(style = style, x[["text"]]) ) }) container_style <- " border: 0.5px solid #5E81AC; width: 50%; padding: 20px; display: flex; justify-content: center;" table_with_icons <- htmltools::div(style = container_style, htmltools::tags$table( htmltools::tags$tr( htmltools::tags$th("Icon", style = style), htmltools::tags$th("Text", style = style) ), row_tags )) table_with_icons
Icon | Text |
---|---|
< svg viewBox="0 0 448 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M313.6 304c-28.7 0-42.5 16-89.6 16-47.1 0-60.8-16-89.6-16C60.2 304 0 364.2 0 438.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-25.6c0-74.2-60.2-134.4-134.4-134.4zM400 464H48v-25.6c0-47.6 38.8-86.4 86.4-86.4 14.6 0 38.3 16 89.6 16 51.7 0 74.9-16 89.6-16 47.6 0 86.4 38.8 86.4 86.4V464zM224 288c79.5 0 144-64.5 144-144S303.5 0 224 0 80 64.5 80 144s64.5 144 144 144zm0-240c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z"> | Jaime |
< svg viewBox="0 0 448 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M437.2 403.5L320 215V64h8c13.3 0 24-10.7 24-24V24c0-13.3-10.7-24-24-24H120c-13.3 0-24 10.7-24 24v16c0 13.3 10.7 24 24 24h8v151L10.8 403.5C-18.5 450.6 15.3 512 70.9 512h306.2c55.7 0 89.4-61.5 60.1-108.5zM137.9 320l48.2-77.6c3.7-5.2 5.8-11.6 5.8-18.4V64h64v160c0 6.9 2.2 13.2 5.8 18.4l48.2 77.6h-172z"> | Researcher |
< svg viewBox="0 0 512 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"> | Jaime123 |
< svg viewBox="0 0 496 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M483.8 179.4C449.8 74.6 352.6 8 248.1 8c-25.4 0-51.2 3.9-76.7 12.2C41.2 62.5-30.1 202.4 12.2 332.6 46.2 437.4 143.4 504 247.9 504c25.4 0 51.2-3.9 76.7-12.2 130.2-42.3 201.5-182.2 159.2-312.4zm-74.5 193.7l-52.2 6.4-43.7-60.9 24.4-75.2 71.1-22.1 38.9 36.4c-.2 30.7-7.4 61.1-21.7 89.2-4.7 9.3-10.7 17.8-16.8 26.2zm0-235.4l-10.4 53.1-70.7 22-64.2-46.5V92.5l47.4-26.2c39.2 13 73.4 38 97.9 71.4zM184.9 66.4L232 92.5v73.8l-64.2 46.5-70.6-22-10.1-52.5c24.3-33.4 57.9-58.6 97.8-71.9zM139 379.5L85.9 373c-14.4-20.1-37.3-59.6-37.8-115.3l39-36.4 71.1 22.2 24.3 74.3-43.5 61.7zm48.2 67l-22.4-48.1 43.6-61.7H287l44.3 61.7-22.4 48.1c-6.2 1.8-57.6 20.4-121.7 0z"> | Football |
Application in a parametrized report or a Shiny application
To use our table with icons in a Rmd report or shiny application, we need to wrap it into a function:
make_table_w_icons <- function(person_list) { style <- "text-align: left; padding: 10px 25px;" row_tags <- lapply(person_list, function(x) { htmltools::tags$tr( htmltools::tags$td( style = style, icons::icon_style( icons::awesome(name = x[["icon"]]), scale = 1.5, fill = "#5E81AC" ) ), htmltools::tags$td(style = style, x[["text"]]) ) }) container_style <- " border: 0.5px solid #5E81AC; width: 50%; padding: 20px; display: flex; justify-content: center;" table_with_icons <- htmltools::div(style = container_style, htmltools::tags$table( htmltools::tags$tr( htmltools::tags$th("Icon", style = style), htmltools::tags$th("Text", style = style) ), row_tags )) return(table_with_icons) } make_table_w_icons(jaime)
Icon | Text |
---|---|
< svg viewBox="0 0 448 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M313.6 304c-28.7 0-42.5 16-89.6 16-47.1 0-60.8-16-89.6-16C60.2 304 0 364.2 0 438.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-25.6c0-74.2-60.2-134.4-134.4-134.4zM400 464H48v-25.6c0-47.6 38.8-86.4 86.4-86.4 14.6 0 38.3 16 89.6 16 51.7 0 74.9-16 89.6-16 47.6 0 86.4 38.8 86.4 86.4V464zM224 288c79.5 0 144-64.5 144-144S303.5 0 224 0 80 64.5 80 144s64.5 144 144 144zm0-240c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z"> | Jaime |
< svg viewBox="0 0 448 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M437.2 403.5L320 215V64h8c13.3 0 24-10.7 24-24V24c0-13.3-10.7-24-24-24H120c-13.3 0-24 10.7-24 24v16c0 13.3 10.7 24 24 24h8v151L10.8 403.5C-18.5 450.6 15.3 512 70.9 512h306.2c55.7 0 89.4-61.5 60.1-108.5zM137.9 320l48.2-77.6c3.7-5.2 5.8-11.6 5.8-18.4V64h64v160c0 6.9 2.2 13.2 5.8 18.4l48.2 77.6h-172z"> | Researcher |
< svg viewBox="0 0 512 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"> | Jaime123 |
< svg viewBox="0 0 496 512" style="position:relative;display:inline-block;top:.1em;fill:#5E81AC;height:1.5em;" xmlns="http://www.w3.org/2000/svg"> < path d="M483.8 179.4C449.8 74.6 352.6 8 248.1 8c-25.4 0-51.2 3.9-76.7 12.2C41.2 62.5-30.1 202.4 12.2 332.6 46.2 437.4 143.4 504 247.9 504c25.4 0 51.2-3.9 76.7-12.2 130.2-42.3 201.5-182.2 159.2-312.4zm-74.5 193.7l-52.2 6.4-43.7-60.9 24.4-75.2 71.1-22.1 38.9 36.4c-.2 30.7-7.4 61.1-21.7 89.2-4.7 9.3-10.7 17.8-16.8 26.2zm0-235.4l-10.4 53.1-70.7 22-64.2-46.5V92.5l47.4-26.2c39.2 13 73.4 38 97.9 71.4zM184.9 66.4L232 92.5v73.8l-64.2 46.5-70.6-22-10.1-52.5c24.3-33.4 57.9-58.6 97.8-71.9zM139 379.5L85.9 373c-14.4-20.1-37.3-59.6-37.8-115.3l39-36.4 71.1 22.2 24.3 74.3-43.5 61.7zm48.2 67l-22.4-48.1 43.6-61.7H287l44.3 61.7-22.4 48.1c-6.2 1.8-57.6 20.4-121.7 0z"> | Football |
We can now create a simple {shiny}
application that displays our person data with icons.
Shiny module
A simple {shiny}
module that uses server-side rendering to make the HTML
table.
The server defines a reactive value person_rct
that we use to create the table.
The set_person
function returned by the module server is used by the calling module
to supply the person data (see the next section).
tableWithIconsUI <- function(id) { ns <- shiny::NS(id) shiny::tagList( shiny::uiOutput(ns("tab")) ) } tableWithIconsServer <- function(id) { shiny::moduleServer( id, function(input, output, session) { person_rct <- shiny::reactiveVal() output$tab <- shiny::renderUI({ make_table_w_icons(person_list = person_rct()) }) return(list( set_person = function(x) { person_rct(x) } )) } ) }
Shiny app
For our application, we define another person (Jessica) and let the user choose
a person with a selectInput
. Then the server observes this input, indexes the
person_list
data object, and passes the person data list to the tableWithIcons
module.
library(shiny) jaime <- list( Name = list(text = "Jaime", icon = "user"), Position = list(text = "Researcher", icon = "flask"), Twitter = list(text = "Jaime123", icon = "twitter"), Hobby = list(text = "Football", icon = "futbol") ) jessica <- list( Name = list(text = "Jessica", icon = "user"), Position = list(text = "Researcher", icon = "flask"), Twitter = list(text = "IamJessica", icon = "twitter"), Hobby = list(text = "Fishing", icon = "fish") ) persons_data <- list( Jaime = jaime, Jessica = jessica ) ui <- fluidPage( selectInput( inputId = "person", label = "Person", choices = c("Jaime", "Jessica") ), tableWithIconsUI(id = "tab1") ) server <- function(input, output, session) { tab1 <- tableWithIconsServer(id = "tab1") shiny::observeEvent(input$person, { person_data <- person_list[[input$person]] tab1$set_person(person_data) }) } shinyApp(ui, server)
Creating an icon set
If you followed along and run the code, you’ll probably be able to run the application
without errors. However, if we were to deploy such an application, we would get an error
because by default, our deployment would only install the {icons}
package, but not
also download the required icon set. We could include a download_awesome
in our server
or global
file, but that would mean downloading the icons on every
deployment or session start, neither of which is desirable.
The solution is to create an icon set and store that as an asset to our application. Then we would deploy this asset with our application, and instead of downloading the full set of icons, we would only load the SVGs for the icons we use in our application.
needed_icons <- c(lapply(persons_data$Jaime, "[[", "icon"), lapply(persons_data$Jessica, "[[", "icon") ) needed_icons <- unique(needed_icons) # requires that folder `icons` exists! icons::icon_save(icons = needed_icons, path = "./icons")
If we had a {golem}
application the icons
folder might be placed in inst
.
In a rhino
application setup, we would put this icon set in static
.
Either way, we would need to load the icon set on application start with:
app_icons <- icons::icon_set("path/to/icons")
Summary
In this post we went through a simple workflow for creating HTML tables with icons
to display small-scale, heterogenous data that are not suitable for charting and
don’t require interactive table widgets. We also saw how to use this type of visualization
in a {shiny}
application and how to include only a subset of required icons as
resources for our web application.
Gist
The full code for the working application is available as a gist below:
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.