install.packages("htmltools") # possibly unnecessary
::install_github("mitchelloharawild/icons") remotes
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
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
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 fontawesome
icons, but with the {icons}
package, we have several other options too:
library(icons)
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:
::download_fontawesome() icons
::fontawesome("twitter") icons
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
)
<- "text-align: left; padding: 10px 25px;"
style <- lapply(jaime,
row_tags function(x) {
::tags$tr(
htmltools::tags$td(
htmltoolsstyle = style,
::icon_style(
icons::fontawesome(name = x[["icon"]]),
iconsscale = 1.5,
fill = "#5E81AC"
)
),::tags$td(style = style, x[["text"]])
htmltools
)
})
<- "
container_style border: 0.5px solid #5E81AC;
width: 50%;
padding: 20px;
display: flex;
justify-content: center;"
<- htmltools::div(style = container_style,
table_with_icons ::tags$table(
htmltools::tags$tr(
htmltools::tags$th("Icon", style = style),
htmltools::tags$th("Text", style = style)
htmltools
),
row_tags
)) table_with_icons
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:
<- function(person_list) {
make_table_w_icons <- "text-align: left; padding: 10px 25px;"
style <- lapply(person_list,
row_tags function(x) {
::tags$tr(
htmltools::tags$td(
htmltoolsstyle = style,
::icon_style(
icons::fontawesome(name = x[["icon"]]),
iconsscale = 1.5,
fill = "#5E81AC"
)
),::tags$td(style = style, x[["text"]])
htmltools
)
})
<- "
container_style border: 0.5px solid #5E81AC;
width: 50%;
padding: 20px;
display: flex;
justify-content: center;"
<- htmltools::div(style = container_style,
table_with_icons ::tags$table(
htmltools::tags$tr(
htmltools::tags$th("Icon", style = style),
htmltools::tags$th("Text", style = style)
htmltools
),
row_tags
))return(table_with_icons)
}
make_table_w_icons(jaime)
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).
<- function(id) {
tableWithIconsUI <- shiny::NS(id)
ns ::tagList(
shiny::uiOutput(ns("tab"))
shiny
)
}
<- function(id) {
tableWithIconsServer ::moduleServer(
shiny
id,function(input, output, session) {
<- shiny::reactiveVal()
person_rct
$tab <- shiny::renderUI({
outputmake_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")
)
<- list(
jessica Name = list(text = "Jessica", icon = "user"),
Position = list(text = "Researcher", icon = "flask"),
Twitter = list(text = "IamJessica", icon = "twitter"),
Hobby = list(text = "Fishing", icon = "fish")
)
<- list(
persons_data Jaime = jaime,
Jessica = jessica
)
<- fluidPage(
ui selectInput(
inputId = "person",
label = "Person",
choices = c("Jaime", "Jessica")
),tableWithIconsUI(id = "tab1")
)
<- function(input, output, session) {
server <- tableWithIconsServer(id = "tab1")
tab1
::observeEvent(input$person, {
shiny<- person_list[[input$person]]
person_data $set_person(person_data)
tab1
})
}
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_fontawesome
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.
<- c(lapply(persons_data$Jaime, "[[", "icon"),
needed_icons lapply(persons_data$Jessica, "[[", "icon")
)<- unique(needed_icons)
needed_icons # requires that folder `icons` exists!
::icon_save(icons = needed_icons, path = "./icons") 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:
<- icons::icon_set("path/to/icons") app_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: