How to include tables in your {ggiraph} tooltips.
In this tutorial, I’ll show you how to add tables to interactive {ggiraph} tooltips like the one I created below using the {kableExtra} and {gt}/{gtExtras} packages.
knitr::include_graphics("https://raw.githubusercontent.com/kcuilla/USgasprices/main/imgs/gas_map_demo.gif")
Source: US Gas Prices Shiny App
As an added bonus, I’ll show you a trick on how to apply conditional formatters from {gtExtras} to the tooltips by parsing the raw HTML content of the table.
{ggiraph} is an amazing package that makes any {ggplot2} graphic interactive.
The example below, which comes from the package site, shows how easy it is to make a {ggplot2} interactive:
library(ggplot2)
library(ggiraph)
library(dplyr)
# load mtcars dataset
data <- mtcars %>% dplyr::select(qsec, wt, disp, mpg, hp, cyl)
data$car <- row.names(data)
# default ggiraph tooltip
gg_point <- ggplot2::ggplot(data = data) +
ggiraph::geom_point_interactive(aes(
x = wt,
y = qsec,
color = disp,
data_id = car,
# display car in the tooltip
tooltip = car
)) +
ggplot2::theme_minimal()
# pass through girafe to activate interactivity
ggiraph::girafe(ggobj = gg_point)
If you hover your mouse over the data points on the chart, you will see the car name within the tooltip. But what if we wanted to add more info to the tooltip such as the car’s mpg, hp, and number of cyl? How would we do that?
Well if you’ve made it this far, you probably already know the answer: tables! How do we do that exactly? I’ll explain step-by-step below.
The first thing we need to do is to design our table. In this example, we’ll use the {kableExtra} package to build the table.
Later, I will also show you how to use the {gt} and {gtExtras} packages.
Here’s a preview of a simple table built with {kableExtra} with the columns that we need:
car | mpg | hp | cyl |
---|---|---|---|
Mazda RX4 | 21.0 | 110 | 6 |
Mazda RX4 Wag | 21.0 | 110 | 6 |
Datsun 710 | 22.8 | 93 | 4 |
Hornet 4 Drive | 21.4 | 110 | 6 |
Hornet Sportabout | 18.7 | 175 | 8 |
Valiant | 18.1 | 105 | 6 |
Duster 360 | 14.3 | 245 | 8 |
Merc 240D | 24.4 | 62 | 4 |
Merc 230 | 22.8 | 95 | 4 |
Merc 280 | 19.2 | 123 | 6 |
Merc 280C | 17.8 | 123 | 6 |
Merc 450SE | 16.4 | 180 | 8 |
Merc 450SL | 17.3 | 180 | 8 |
Merc 450SLC | 15.2 | 180 | 8 |
Cadillac Fleetwood | 10.4 | 205 | 8 |
Lincoln Continental | 10.4 | 215 | 8 |
Chrysler Imperial | 14.7 | 230 | 8 |
Fiat 128 | 32.4 | 66 | 4 |
Honda Civic | 30.4 | 52 | 4 |
Toyota Corolla | 33.9 | 65 | 4 |
Toyota Corona | 21.5 | 97 | 4 |
Dodge Challenger | 15.5 | 150 | 8 |
AMC Javelin | 15.2 | 150 | 8 |
Camaro Z28 | 13.3 | 245 | 8 |
Pontiac Firebird | 19.2 | 175 | 8 |
Fiat X1-9 | 27.3 | 66 | 4 |
Porsche 914-2 | 26.0 | 91 | 4 |
Lotus Europa | 30.4 | 113 | 4 |
Ford Pantera L | 15.8 | 264 | 8 |
Ferrari Dino | 19.7 | 175 | 6 |
Maserati Bora | 15.0 | 335 | 8 |
Volvo 142E | 21.4 | 109 | 4 |
If we replace ‘car’ with our table in the tooltip option of ggiraph::geom_point_interactive()
, the full table will appear when hovering over each point on the plot.
Our table is showing within the tooltip, but this isn’t quite what we want. Instead, we want to show the values that are relevant for each specific car.
gg_point <- ggplot2::ggplot(data = data) +
ggiraph::geom_point_interactive(aes(
x = wt,
y = qsec,
color = disp,
data_id = car,
tooltip = table
)) +
ggplot2::theme_minimal()
girafe(ggobj = gg_point)
To fix this, we need to create a column within our dataset that contains a table for each row. We can write a function that will loop through each car and add its corresponding data from the mpg, hp, and cyl columns using the {purrr} package.
We’ll start by creating a simple function that filters our dataset based on the car, selects the columns we need for our table, and builds the table with {kableExtra}. This is the same code we used to build our tables in the previous section, the only difference is that we’re adding a parameter to filter on the car before building our table.
Now that we have our function, we can use purrr::map()
to iterate over each car in the dataset and store the tables in a column called ‘table’.
When we look at the updated dataset, we can see that the table column contains the raw HTML that is used to create the tables in the {kableExtra} package.
car qsec wt disp
Mazda RX4 Mazda RX4 16.46 2.620 160
Mazda RX4 Wag Mazda RX4 Wag 17.02 2.875 160
Datsun 710 Datsun 710 18.61 2.320 108
Hornet 4 Drive Hornet 4 Drive 19.44 3.215 258
Hornet Sportabout Hornet Sportabout 17.02 3.440 360
Valiant Valiant 20.22 3.460 225
table
Mazda RX4 <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Mazda RX4 </td>\n <td style="text-align:right;"> 21 </td>\n <td style="text-align:right;"> 110 </td>\n <td style="text-align:right;"> 6 </td>\n </tr>\n</tbody>\n</table>
Mazda RX4 Wag <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Mazda RX4 Wag </td>\n <td style="text-align:right;"> 21 </td>\n <td style="text-align:right;"> 110 </td>\n <td style="text-align:right;"> 6 </td>\n </tr>\n</tbody>\n</table>
Datsun 710 <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Datsun 710 </td>\n <td style="text-align:right;"> 22.8 </td>\n <td style="text-align:right;"> 93 </td>\n <td style="text-align:right;"> 4 </td>\n </tr>\n</tbody>\n</table>
Hornet 4 Drive <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Hornet 4 Drive </td>\n <td style="text-align:right;"> 21.4 </td>\n <td style="text-align:right;"> 110 </td>\n <td style="text-align:right;"> 6 </td>\n </tr>\n</tbody>\n</table>
Hornet Sportabout <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Hornet Sportabout </td>\n <td style="text-align:right;"> 18.7 </td>\n <td style="text-align:right;"> 175 </td>\n <td style="text-align:right;"> 8 </td>\n </tr>\n</tbody>\n</table>
Valiant <table>\n <thead>\n <tr>\n <th style="text-align:left;"> car </th>\n <th style="text-align:right;"> mpg </th>\n <th style="text-align:right;"> hp </th>\n <th style="text-align:right;"> cyl </th>\n </tr>\n </thead>\n<tbody>\n <tr>\n <td style="text-align:left;"> Valiant </td>\n <td style="text-align:right;"> 18.1 </td>\n <td style="text-align:right;"> 105 </td>\n <td style="text-align:right;"> 6 </td>\n </tr>\n</tbody>\n</table>
Now, when we feed the table column into the tooltip, we should get a single table for each car on the plot!
gg_point <- ggplot2::ggplot(data = df) +
ggiraph::geom_point_interactive(aes(
x = wt,
y = qsec,
color = disp,
data_id = car,
tooltip = table
)) +
ggplot2::theme_minimal()
ggiraph::girafe(ggobj = gg_point)
We can further customize the appearance of the tooltip tables by using styles from the {kableExtra} package.
In order to do that, we just need to modify the function we used to create the tables for each car and apply the styles as shown below:
make_table <- function(name) {
data %>%
# filter by car name
dplyr::filter(car == name) %>%
dplyr::select(car, mpg, hp, cyl) %>%
kableExtra::kbl(row.names = FALSE) %>%
# change the font family and increase font size
kableExtra::kable_styling(font_size = 24, html_font = "Courier New") %>%
# increase the width of the columns, make the text blue and bold, apply white background
kableExtra::column_spec(1:4, width = "3em", bold = T, color = "blue", background = "white")
}
df <- data %>%
dplyr::mutate(table = purrr::map(car, make_table)) %>%
dplyr::select(car, qsec, wt, disp, table)
And then call the table within our chart using the same method as before:
gg_point <- ggplot2::ggplot(data = df) +
ggiraph::geom_point_interactive(aes(
x = wt,
y = qsec,
color = disp,
data_id = car,
tooltip = table
)) +
ggplot2::theme_minimal()
ggiraph::girafe(ggobj = gg_point)
In addition to the {kableExtra} package, we can also use the {gt} and {gtExtras} packages to build tables for our tooltip.
For this example, we are going to build a {gt} table that displays the most populous city in each U.S. city (based on the 2010 U.S. Census). The dataset comes from the {usmap} package, which we will also use to build a U.S. map in the next section.
Here is what the full {gt} table looks like with a theme applied from the {gtExtras} package:
library(gt)
library(gtExtras)
library(usmap)
# load city population dataset from {usmap}
cities_t <- usmap::usmap_transform(citypop) %>%
# remove DC from dataset
dplyr::filter(!state %in% c('District of Columbia')) %>%
# sort by state
dplyr::arrange(state)
gt_table <- cities_t %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
# create a {gt} table
gt::gt() %>%
# add comma delimeters to the city_pop column
gt::fmt_number(columns = city_pop, decimals = 0) %>%
# adjust column widths
gt::cols_width(everything() ~ px(120)) %>%
# apply the espn theme from {gtExtras}
gtExtras::gt_theme_espn() %>%
# add a title and subtitle to the table
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010")
gt_table
Most Populous City in Each State | ||
Source: US Census 2010 | ||
state | city | city_pop |
---|---|---|
Alabama | Birmingham | 212,237 |
Alaska | Anchorage | 291,826 |
Arizona | Phoenix | 1,445,632 |
Arkansas | Little Rock | 193,524 |
California | Los Angeles | 3,792,621 |
Colorado | Denver | 600,158 |
Connecticut | Bridgeport | 144,229 |
Delaware | Wilmington | 70,851 |
Florida | Jacksonville | 880,619 |
Georgia | Atlanta | 420,003 |
Hawaii | Honolulu | 337,256 |
Idaho | Boise | 205,671 |
Illinois | Chicago | 2,695,598 |
Indiana | Indianapolis | 820,445 |
Iowa | Des Moines | 215,472 |
Kansas | Wichita | 382,368 |
Kentucky | Louisville | 597,337 |
Louisiana | New Orleans | 343,829 |
Maine | Portland | 66,194 |
Maryland | Baltimore | 620,961 |
Massachusetts | Boston | 617,594 |
Michigan | Detroit | 713,777 |
Minnesota | Minneapolis | 382,578 |
Mississippi | Jackson | 173,514 |
Missouri | Kansas City | 459,787 |
Montana | Billings | 104,170 |
Nebraska | Omaha | 466,893 |
Nevada | Las Vegas | 583,756 |
New Hampshire | Manchester | 109,565 |
New Jersey | Newark | 277,140 |
New Mexico | Albuquerque | 545,852 |
New York | New York City | 8,175,133 |
North Carolina | Charlotte | 731,424 |
North Dakota | Fargo | 105,549 |
Ohio | Columbus | 879,170 |
Oklahoma | Oklahoma City | 579,999 |
Oregon | Portland | 583,776 |
Pennsylvania | Philadelphia | 1,526,006 |
Rhode Island | Providence | 178,042 |
South Carolina | Charleston | 129,272 |
South Dakota | Sioux Falls | 153,888 |
Tennessee | Nashville | 660,388 |
Texas | Houston | 2,099,451 |
Utah | Salt Lake City | 186,440 |
Vermont | Burlington | 42,417 |
Virginia | Virginia Beach | 437,994 |
Washington | Seattle | 608,660 |
West Virginia | Charleston | 51,400 |
Wisconsin | Milwaukee | 594,833 |
Wyoming | Cheyenne | 59,466 |
An important thing to note here is that if we were to apply a {gt} table, such as the one above, directly to {ggiraph}, it would not appear in our tooltip. If you remember earlier when we were using the {kableExtra} package, the tooltip column we created for our tables contained the raw HTML of the table. That is because, by default, {kableExtra} gives you the HTML content that was used to create the table. The {gt} package, however, does not do this by default. Thankfully, though, there is a way of extracting the HTML content of the table using the gt::as_raw_html()
function. We can do this by simply piping the table we created directly into the gt::as_raw_html()
function as shown below:
# get HTML content from {gt} table
gt_table_html <- gt_table %>%
gt::as_raw_html()
Now that we have the HTML content of our {gt} table, we can follow the same steps as we did above with our {kableExtra} tables to create a table for each row, or state, in the dataset:
make_table <- function(name) {
cities_t %>%
# filter by state name
dplyr::filter(state == name) %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
gt::gt() %>%
gt::fmt_number(columns = city_pop, decimals = 0) %>%
gt::cols_width(everything() ~ px(120)) %>%
gtExtras::gt_theme_espn() %>%
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010") %>%
# get HTML content of table
gt::as_raw_html()
}
cities_t <- cities_t %>%
dplyr::mutate(tooltip = purrr::map(state, make_table))
gg_map <- usmap::plot_usmap(fill = "white", alpha = 0.25) +
ggiraph::geom_point_interactive(
data = cities_t,
ggplot2::aes(
x = x,
y = y,
size = city_pop,
tooltip = tooltip,
data_id = state
),
color = "purple",
alpha = 0.8
) +
scale_size_continuous(range = c(1, 16),
label = scales::comma) +
labs(title = "Most Populous City in Each State",
subtitle = "Source: US Census 2010",
size = "City Population") +
theme(legend.position = "right")
ggiraph::girafe(ggobj = gg_map)
Let’s say that we wanted to add a column to our table that shows a horizontal bar chart for each city’s population. We can do so by adding gtExtras::gt_color_rows()
to our table as shown below:
cities_t <- usmap_transform(citypop) %>%
dplyr::filter(!state %in% c('District of Columbia','Alaska','Hawaii')) %>%
dplyr::arrange(state)
gt_table <- cities_t %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
gt::gt() %>%
gt::fmt_number(columns = city_pop, decimals = 0) %>%
# add horizontal bar chart to values based on relative population size
gtExtras::gt_plt_bar(city_pop, keep_column = TRUE) %>%
gtExtras::gt_theme_espn() %>%
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010")
gt_table
Most Populous City in Each State | |||
Source: US Census 2010 | |||
state | city | city_pop | city_pop |
---|---|---|---|
Alabama | Birmingham | 212,237 | |
Arizona | Phoenix | 1,445,632 | |
Arkansas | Little Rock | 193,524 | |
California | Los Angeles | 3,792,621 | |
Colorado | Denver | 600,158 | |
Connecticut | Bridgeport | 144,229 | |
Delaware | Wilmington | 70,851 | |
Florida | Jacksonville | 880,619 | |
Georgia | Atlanta | 420,003 | |
Idaho | Boise | 205,671 | |
Illinois | Chicago | 2,695,598 | |
Indiana | Indianapolis | 820,445 | |
Iowa | Des Moines | 215,472 | |
Kansas | Wichita | 382,368 | |
Kentucky | Louisville | 597,337 | |
Louisiana | New Orleans | 343,829 | |
Maine | Portland | 66,194 | |
Maryland | Baltimore | 620,961 | |
Massachusetts | Boston | 617,594 | |
Michigan | Detroit | 713,777 | |
Minnesota | Minneapolis | 382,578 | |
Mississippi | Jackson | 173,514 | |
Missouri | Kansas City | 459,787 | |
Montana | Billings | 104,170 | |
Nebraska | Omaha | 466,893 | |
Nevada | Las Vegas | 583,756 | |
New Hampshire | Manchester | 109,565 | |
New Jersey | Newark | 277,140 | |
New Mexico | Albuquerque | 545,852 | |
New York | New York City | 8,175,133 | |
North Carolina | Charlotte | 731,424 | |
North Dakota | Fargo | 105,549 | |
Ohio | Columbus | 879,170 | |
Oklahoma | Oklahoma City | 579,999 | |
Oregon | Portland | 583,776 | |
Pennsylvania | Philadelphia | 1,526,006 | |
Rhode Island | Providence | 178,042 | |
South Carolina | Charleston | 129,272 | |
South Dakota | Sioux Falls | 153,888 | |
Tennessee | Nashville | 660,388 | |
Texas | Houston | 2,099,451 | |
Utah | Salt Lake City | 186,440 | |
Vermont | Burlington | 42,417 | |
Virginia | Virginia Beach | 437,994 | |
Washington | Seattle | 608,660 | |
West Virginia | Charleston | 51,400 | |
Wisconsin | Milwaukee | 594,833 | |
Wyoming | Cheyenne | 59,466 |
As you can see, the size of each bar is relative to the overall distribution of population sizes within the column. This would be something fun to add to our tooltip, but look what happens when we do using the same method as before:
make_table <- function(name) {
cities_t %>%
# filter by state name
dplyr::filter(state == name) %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
gt::gt() %>%
gt::fmt_number(columns = city_pop, decimals = 0) %>%
# add horizontal bar chart to values based on relative population size
gtExtras::gt_plt_bar(city_pop, keep_column = TRUE) %>%
gtExtras::gt_theme_espn() %>%
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010") %>%
# get HTML content of table
gt::as_raw_html()
}
cities_t <- cities_t %>%
dplyr::mutate(tooltip = purrr::map(state, make_table))
gg_map <- usmap::plot_usmap(fill = "white", alpha = 0.25) +
ggiraph::geom_point_interactive(
data = cities_t,
ggplot2::aes(
x = x,
y = y,
size = city_pop,
tooltip = tooltip,
data_id = state
),
color = "purple",
alpha = 0.8
) +
scale_size_continuous(range = c(1, 16),
label = scales::comma) +
labs(title = "Most Populous City in Each State",
subtitle = "Source: US Census 2010",
size = "City Population") +
theme(legend.position = "right")
ggiraph::girafe(ggobj = gg_map)
Did you notice in the map above that all of the purple bar charts were exactly the same length regardless of which state you hovered over? That’s because gtExtras::gt_plt_bar()
determines the length of each horizontal bar based on how that value compares to other values within the column. But, since we filter each state BEFORE building our {gt} table, gtExtras::gt_plt_bar()
only sees one value within the column and assigns it the same length regardless if the value is 1 or 10,000 because it has no other value to compare it with.
You may be wondering why we didn’t apply our dplyr::filter()
after building our {gt} table instead of before, and the reason is simply because we can’t. Once we pass data through a {gt} table, it gets converted to a gt_tbl
object and is no longer compatible with dplyr
functions. However, through some HTML-parsing trickery outlined in the next section, we can still filter our {gt} table thanks to the extracted HTML content via gt::as_raw_html()
.
Before diving in to the HTML output from {gt} tables, it may help to understand the basic structure of HTML tables.
Below is a simple example of a table created with HTML. Every HTML table starts with <table>
and ends with </table>
. Within the table, the names of the columns are defined in table header, or <th>
cells which appear as <th>Column Name</th>
. Each row in the table starts with <tr>
and the data values are stored within <td>Value</td>
.
"<table>
<tr>
<th>Column 1</th>
</tr>
<tbody>
<tr>
<td>Row 1</td>
</tr>
<tr>
<td>Row 2</td>
</tr>
</tbody>
</table>"
Column 1 |
---|
Row 1 |
Row 2 |
There are many additional options within HTML tables, such as a table title (<caption>
), a table footer (<tfoot>
), and styling elements that contain CSS code.
However, it’s not necessary to know all of that, because all we’re looking for are the names of the states within the table. And given the info above, we know the states will be contained within a row (<tr>
) followed by a data cell (<td>
) containing the state name, such as: <tr><td>California
.
I mentioned that we will be filtering the part of the table that contains the data for each state so that we can capture the correct size of the horizontal bar charts based on the state’s population. However, before we do that, we need to extract the head of table first. Once we have the HTML content for the head of the table, we can append the HTML content for each one of the states to it so that we can have a complete HTML table for each state.
To get the HTML content for the head of the table, we can convert the output to a character vector and use strsplit()
to split the vector at the point when reach <tr><td
which marks the start of the rows that contain our state data. When we run this, it splits our table before each row and stores it within a list. Since we have 48 continental states within our dataset plus the header of the table (remember, even the table headers in an HTML table start with <tr>
), our list will contain 49 elements in total:
# the code used to create our dataset and HTML table:
cities_t <- usmap_transform(citypop) %>%
dplyr::filter(!state %in% c('District of Columbia','Alaska','Hawaii')) %>%
dplyr::arrange(state)
gt_table_html <- cities_t %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
gt::gt() %>%
gt::fmt_number(columns = city_pop, decimals = 0) %>%
gtExtras::gt_plt_bar(city_pop, keep_column = TRUE) %>%
gtExtras::gt_theme_espn() %>%
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010") %>%
gt::as_raw_html()
length(strsplit(as.character(gt_table_html), "<tr><td")[[1]])
[1] 49
So, based on what we described above, the head of the table will be contained within the first element of our list, while the data for the states will be contained in the other elements.
Let’s store the head of the table as table_head
so that we can append the HTML for the states to it later:
table_head <- strsplit(as.character(gt_table_html), "<tr><td")[[1]][1]
table_head
[1] "<table style=\"font-family: Lato, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif; display: table; border-collapse: collapse; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 3px; border-top-color: #FFFFFF; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3;\">\n <thead style=\"\">\n <tr>\n <td colspan=\"4\" style=\"background-color: #FFFFFF; text-align: left; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; color: #333333; font-size: 24px; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-width: 0; font-weight: normal;\" style>Most Populous City in Each State</td>\n </tr>\n <tr>\n <td colspan=\"4\" style=\"background-color: #FFFFFF; text-align: left; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; color: #333333; font-size: 85%; font-weight: initial; padding-top: 0; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; font-weight: normal;\" style>Source: US Census 2010</td>\n </tr>\n </thead>\n <thead style=\"border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3;\">\n <tr>\n <th style=\"color: #333333; background-color: #FFFFFF; font-size: 80%; font-weight: bolder; text-transform: uppercase; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;\" rowspan=\"1\" colspan=\"1\" scope=\"col\">state</th>\n <th style=\"color: #333333; background-color: #FFFFFF; font-size: 80%; font-weight: bolder; text-transform: uppercase; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;\" rowspan=\"1\" colspan=\"1\" scope=\"col\">city</th>\n <th style=\"color: #333333; background-color: #FFFFFF; font-size: 80%; font-weight: bolder; text-transform: uppercase; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;\" rowspan=\"1\" colspan=\"1\" scope=\"col\">city_pop</th>\n <th style=\"color: #333333; background-color: #FFFFFF; font-size: 80%; font-weight: bolder; text-transform: uppercase; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;\" rowspan=\"1\" colspan=\"1\" scope=\"col\">city_pop</th>\n </tr>\n </thead>\n <tbody style=\"border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3;\">\n "
/* CSS code to prevent HTML output from truncating in output */
pre code {white-space: pre-wrap;
}
The data for the states are stored within elements 2 through 49. Before creating the table, we sorted the states in alphabetical order, so the first state that appears in our HTML should be Alabama. There’s a lot of style content within the HTML output shown below, but if you look close enough, you should be able to see the state name (Alabama), city (Birmingham), and population (212,237).
strsplit(as.character(gt_table_html), "<tr><td")[[1]][2]
[1] " style=\"padding-top: 7px; padding-bottom: 7px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #F6F7F7; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;\">Alabama</td>\n<td style=\"padding-top: 7px; padding-bottom: 7px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #F6F7F7; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;\">Birmingham</td>\n<td style=\"padding-top: 7px; padding-bottom: 7px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #F6F7F7; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;\">212,237</td>\n<td style=\"padding-top: 7px; padding-bottom: 7px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #F6F7F7; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;\"><?xml version='1.0' encoding='UTF-8' ?><svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='svglite' width='198.43pt' height='14.17pt' viewBox='0 0 198.43 14.17'><defs> <style type='text/css'><![CDATA[ .svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle { fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-miterlimit: 10.00; } .svglite text { white-space: pre; } ]]></style></defs><rect width='100%' height='100%' style='stroke: none; fill: none;'/><defs> <clipPath id='cpMC4wMHwxOTguNDN8MC4wMHwxNC4xNw=='> <rect x='0.00' y='0.00' width='198.43' height='14.17' /> </clipPath></defs><g clip-path='url(#cpMC4wMHwxOTguNDN8MC4wMHwxNC4xNw==)'><rect x='8.78' y='1.77' width='4.47' height='10.63' style='stroke-width: 1.07; stroke: none; stroke-linecap: square; stroke-linejoin: miter; fill: #A020F0;' /><line x1='8.78' y1='14.17' x2='8.78' y2='0.0000000000000018' style='stroke-width: 2.13; stroke-linecap: butt;' /></g></svg></td></tr>\n "
In order to pull the HTML content for each of the remaining states in our dataset, we will need to create a for loop that will go through each element in our list, extract the HTML content, and append it to the table_head
we created in the previous section and store it in a vector called html_tables
.
A couple quick things to note are when we use strsplit()
to split the HTML on <tr><td
, strsplit()
actually will remove the <tr><td
during the split. So, in order to add it back in, we can just paste it before the split. The other thing is we will need to add </tbody></table>
to the end of the table body to tell the HTML to close the body and table so that the table can be created.
table_body <- c()
for (i in 2:49) {
table_body[i - 1] <-
paste0("<tr><td",
strsplit(as.character(gt_table_html), "<tr><td")[[1]][i],
"</tbody></table>")
html_tables <- paste0(table_head, table_body)
}
To use the HTML tables we created for each state, we will need to create a column containing the code for the HTML within our dataset so that we can call it within the tooltip of ggiraph::geom_point_interactive()
just as we did in prior sections.
Now, when we hover over each state, you can see that our bar charts are displaying properly!
cities_t <- cities_t %>%
dplyr::mutate(tooltip = data.frame(html_tables))
gg_map <- usmap::plot_usmap(fill = "white", alpha = 0.25) +
ggiraph::geom_point_interactive(
data = cities_t,
ggplot2::aes(
x = x,
y = y,
size = city_pop,
tooltip = tooltip$html_tables,
data_id = state
),
color = "purple",
alpha = 0.8
) +
scale_size_continuous(range = c(1, 16),
label = scales::comma) +
labs(title = "Most Populous City in Each State",
subtitle = "Source: US Census 2010",
size = "City Population") +
theme(legend.position = "right")
ggiraph::girafe(ggobj = gg_map)
Now that we went over step-by-step on how to add conditional formatters from {gtExtras} to our tooltips, I’ll quickly share another example of how we can create an interactive choropleth map with {ggiraph} and match the color of the state on the map, which pertains to the state’s city with the largest population, to the color of the population within our {gt} table.
Here is the same table we created in the previous section but with gtExtras::gt_color_rows()
applied to the city_pop column:
cities_t <- usmap_transform(citypop) %>%
dplyr::filter(!state %in% c('District of Columbia','Alaska','Hawaii')) %>%
dplyr::arrange(state)
gt_table <- cities_t %>%
dplyr::arrange(state) %>%
dplyr::select(state, city = most_populous_city, city_pop) %>%
gt::gt() %>%
gt::fmt_number(columns = city_pop, decimals = 0) %>%
gt::cols_width(everything() ~ px(140)) %>%
gtExtras::gt_color_rows(city_pop, palette = "ggsci::blue_material") %>%
gtExtras::gt_theme_espn() %>%
gt::tab_header(title = "Most Populous City in Each State", subtitle = "Source: US Census 2010")
gt_table
Most Populous City in Each State | ||
Source: US Census 2010 | ||
state | city | city_pop |
---|---|---|
Alabama | Birmingham | 212,237 |
Arizona | Phoenix | 1,445,632 |
Arkansas | Little Rock | 193,524 |
California | Los Angeles | 3,792,621 |
Colorado | Denver | 600,158 |
Connecticut | Bridgeport | 144,229 |
Delaware | Wilmington | 70,851 |
Florida | Jacksonville | 880,619 |
Georgia | Atlanta | 420,003 |
Idaho | Boise | 205,671 |
Illinois | Chicago | 2,695,598 |
Indiana | Indianapolis | 820,445 |
Iowa | Des Moines | 215,472 |
Kansas | Wichita | 382,368 |
Kentucky | Louisville | 597,337 |
Louisiana | New Orleans | 343,829 |
Maine | Portland | 66,194 |
Maryland | Baltimore | 620,961 |
Massachusetts | Boston | 617,594 |
Michigan | Detroit | 713,777 |
Minnesota | Minneapolis | 382,578 |
Mississippi | Jackson | 173,514 |
Missouri | Kansas City | 459,787 |
Montana | Billings | 104,170 |
Nebraska | Omaha | 466,893 |
Nevada | Las Vegas | 583,756 |
New Hampshire | Manchester | 109,565 |
New Jersey | Newark | 277,140 |
New Mexico | Albuquerque | 545,852 |
New York | New York City | 8,175,133 |
North Carolina | Charlotte | 731,424 |
North Dakota | Fargo | 105,549 |
Ohio | Columbus | 879,170 |
Oklahoma | Oklahoma City | 579,999 |
Oregon | Portland | 583,776 |
Pennsylvania | Philadelphia | 1,526,006 |
Rhode Island | Providence | 178,042 |
South Carolina | Charleston | 129,272 |
South Dakota | Sioux Falls | 153,888 |
Tennessee | Nashville | 660,388 |
Texas | Houston | 2,099,451 |
Utah | Salt Lake City | 186,440 |
Vermont | Burlington | 42,417 |
Virginia | Virginia Beach | 437,994 |
Washington | Seattle | 608,660 |
West Virginia | Charleston | 51,400 |
Wisconsin | Milwaukee | 594,833 |
Wyoming | Cheyenne | 59,466 |
And here is a choropleth map created with {ggplot2} and {ggriaph} without the interactive tooltip activated:
states_map <- ggplot2::map_data("state")
cities_t$state <- tolower(cities_t$state)
gg_map <- ggplot(cities_t, aes(map_id = state)) +
ggiraph::geom_map_interactive(
aes(
fill = city_pop,
data_id = state
),
color = "white",
map = states_map
) +
expand_limits(x = states_map$long, y = states_map$lat) +
ggsci::scale_fill_material("blue",
label = scales::comma) +
labs(title = "Most Populous City in Each State",
subtitle = "Source: US Census 2010",
fill = "City Population") +
theme_void()
gg_map
By following the same steps in the previous section, we can extract the HTML content from our {gt} table and build our tooltip that contains the same shade of blue for each state that is seen on the map.
# get HTML content from the {gt} table
gt_table_html <- gt_table %>%
gt::as_raw_html()
# extract HTML content in the head of the table
table_head <- strsplit(as.character(gt_table_html), "<tr><td")[[1]][1]
# extract HTML content from the body of the table for each state
table_body <- c()
for (i in 2:49) {
table_body[i - 1] <-
paste0("<tr><td",
strsplit(as.character(gt_table_html), "<tr><td")[[1]][i],
"</tbody></table>")
html_tables <- paste0(table_head, table_body)
}
# add the HTML tables to our dataset
cities_t <- cities_t %>%
dplyr::mutate(tooltip = data.frame(html_tables))
gg_map <- ggplot(cities_t, aes(map_id = state)) +
ggiraph::geom_map_interactive(
aes(
fill = city_pop,
data_id = state,
tooltip = tooltip$html_tables
),
color = "white",
map = states_map
) +
expand_limits(x = states_map$long, y = states_map$lat) +
ggsci::scale_fill_material("blue",
label = scales::comma) +
labs(title = "Most Populous City in Each State",
subtitle = "Source: US Census 2010",
fill = "City Population") +
theme_void()
ggiraph::girafe(ggobj = gg_map, width_svg = 5, height_svg = 3)
If you don’t want the tables to follow the cursor as you hover, you can place them in a stable position by setting use_cursor_pos
to FALSE and adjusting the position of where you want the table to be displayed by utilizing the offx
and offy
options within opts_tooltip()
of {ggiraph}:
ggiraph::girafe(
ggobj = gg_map,
options = list(opts_tooltip(
offx = 50,
offy = 425,
use_cursor_pos = FALSE
)),
width_svg = 5,
height_svg = 3
)
In this tutorial, we’ve gone through how to build {kableExtra}, {gt}/{gtExtras} tables and place them within {ggiraph} tooltips. Because we need the raw HTML of the table output in order for {ggiraph} to use the table as a tooltip, that limits the types of table-building packages we can use. For example, tables built with {reactable}/{reactablefmtr} are not compatible with {ggiraph} because their output is in JSON format. Thankfully, the {kableExtra} and {gt}/{gtExtras} packages are highly flexible and should give you all the customization options you need for your tooltips.
For attribution, please cite this work as
Cuilla (2022, Sept. 30). UNCHARTED DATA: Interactive Tooltip Tables. Retrieved from https://uncharteddata.netlify.app/posts/2022-09-30-interactive-tooltip-tables/
BibTeX citation
@misc{cuilla2022interactive, author = {Cuilla, Kyle}, title = {UNCHARTED DATA: Interactive Tooltip Tables}, url = {https://uncharteddata.netlify.app/posts/2022-09-30-interactive-tooltip-tables/}, year = {2022} }