The following is a repost of my submission in the 2022 Posit Table Contest, which was recognized as one of the runners up in the contest. The original tutorial, stand-alone table, and code repository are available if interested. Huge thanks to the Posit team for hosting this contest.
In this tutorial, I’ll walkthrough the process of developing a table in R using the reactable and reactablefmtr packages and other supplemental packages and functions. The goal is to create a table that describes the Amtrak system, with detail about the routes and the stations. The table displays a route on each row with the included stations along that route available as expandable details. To help visualize the stations and routes, the table includes embedded graphics generated by ggplot2.
1.1 Load packages and prepared data
The data used for this table comes from multiple sources including the US Department of Transportation, Wikipedia, and TrainWeb.org. The data preparation script and the generated data files are available in the Github repository.
To generate maps of the United States and Canada, the tigris package provides US Census Bureau boundaries of states and the canadianmaps package includes data on provinces.
We can see that the routes data set includes all 43 active Amtrak routes and 9 variables, such as the beginning and ending cities, passenger volume in fiscal year 2021, length, duration, and which kind of train cars the route includes.
The route data set also includes route geometry that we can use for geospatial analysis. In the code below, we create the routes and routes_map dataframes which will be used for the table. routes_map is generated using the route_map_fcn function which generates a ggplot2 map graphic using the route, station, and state/province geometries. We also generated a separate map graphic for the table header (rendered below).
2.2 Create functions to generate icons representing available train cars
Each of the routes uses one or more different types of train cars (dining, coach, etc.). To display this information, we use the function below to replace the word descriptions with icons representing whether each type of train car is present for the route.
Show the code
icons <-function(icon, color, size =30, empty =FALSE) { fill_color <- grDevices::adjustcolor(color, alpha.f =1.0) empty_color <- grDevices::adjustcolor(color, alpha.f =0.3) htmltools::tagAppendAttributes( shiny::icon(icon),style =paste0("font-size:", size, "px", "; color:", if (empty) empty_color else fill_color),"aria-hidden"="true" )}train_icons <-function(vals) {if(is.na(vals)) { coach <-span(icons("train", "gray10", empty = T), title ="Coach Not Available", style ="margin: 5px;") diner <-span(icons("utensils", "gray10", empty = T), title ="Diner/Cafe Not Available", style ="margin: 5px;") sleeper <-span(icons("bed", "gray10", empty = T), title ="Sleeper Not Available", style ="margin: 5px;") business <-span(icons("briefcase", "gray10", empty = T), title ="Business Not Available", style ="margin: 5px;") first_class <-span(icons("money-check-dollar", "gray10", empty = T), title ="First Class Not Available", style ="margin: 5px;") auto <-span(icons("car-side", "gray10", empty = T), title ="Auto Transport Not Available", style ="margin: 5px;") } else {if (str_detect(vals, "Coach")) { coach <-span(icons("chair", "gray10", empty = F), title ="Coach Available", style ="margin: 5px;") } else { coach <-span(icons("chair", "gray10", empty = T), title ="Coach Not Available", style ="margin: 5px;") }if (str_detect(vals, "Dinner|Dinette|Cafe|Bistro")) { diner <-span(icons("utensils", "gray10", empty = F), title ="Diner/Cafe Available", style ="margin: 5px;") } else { diner <-span(icons("utensils", "gray10", empty = T), title ="Diner/Cafe Not Available", style ="margin: 5px;") }if (str_detect(vals, "Sleeper")) { sleeper <-span(icons("bed", "gray10", empty = F), title ="Sleeper Available", style ="margin: 5px;") } else { sleeper <-span(icons("bed", "gray10", empty = T), title ="Sleeper Not Available", style ="margin: 5px;") }if (str_detect(vals, "Business")) { business <-span(icons("briefcase", "gray10", empty = F), title ="Business Available", style ="margin: 5px;") } else { business <-span(icons("briefcase", "gray10", empty = T), title ="Business Not Available", style ="margin: 5px;") }if (str_detect(vals, "First Class")) { first_class <-span(icons("money-check-dollar", "gray10", empty = F), title ="First Class Available", style ="margin: 5px;") } else { first_class <-span(icons("money-check-dollar", "gray10", empty = T), title ="First Class Not Available", style ="margin: 5px;") }if (str_detect(vals, "Auto")) { auto <-span(icons("car-side", "gray10", empty = F), title ="Auto Transport Available", style ="margin: 5px;") } else { auto <-span(icons("car-side", "gray10", empty = T), title ="Auto Transport Not Available", style ="margin: 5px;") } }div(coach, diner, sleeper, business, first_class, auto)}
2.3 Generate sample routes reactable table
We can now use the route data frames created above to generate a sample reactable table, using the first 5 rows of the routes data. In this table, we create a bar chart of passenger data using reactablefmtr, display the train car icons, include a map graphic, and format the other columns.
The stations data set includes all 876 active Amtrak route/station combinations and variables such as the station location, what other routes the station serves, when the station opened, and what type of station it is. The data also includes a variable indicating what type of station junction it is along the route (beginning, middle, split, end, etc.).
The ridership data includes ridership by station from fiscal year 2005 to 2021.
3.1 Join station and ridership data and create subway-style route diagrams
Since the stations in a route may be along different branches, it’s helpful to display the data using a route diagram. Some Wikipedia articles (see example) include helpful subway-style route diagrams to display different types of junctions. The route_diagram_fcn below generate specific diagram for each type of junction in ggplot2 in the stations_diag dataframe, which will be used with the stations dataframe in the table.
Similar to the routes table above, we can use the stations data frames created above to generate a sample reactable table, using the stations on the Northeast Regional route. In this table, we embed the route diagram for the station, create a sparkline chart of passenger data using reactablefmtr, display the connecting routes using a details breakout, and format the other columns.
4 Generate nested reactable table including routes and stations
Finally, we can bring together the routes and stations data into one reactable table. After creating the amtrak_table object, we add headers and footers using the prependContent and appendContent functions in the htmlwidgets package. The header includes the map graphic of the whole Amtrak system as well as the title and subtitle. The footer includes footnotes and source information.
amtrak_table_final <- amtrak_table %>%# add title, subtitle, and map htmlwidgets::prependContent( tags$div( tags$link(href ="https://fonts.googleapis.com/css?family=Recursive:400,600,700&display=swap", rel ="stylesheet"), tags$div( tags$div("All Aboard!", style =css('font-size'='60pt', 'font-weight'='bold', 'font-family'='Recursive', 'text-align'='left','margin-bottom'=0,'padding-left'='10px','vertical-align'='middle') ), tags$div("Exploring the Amtrak Passenger Rail System", style =css('font-family'='Recursive','margin-bottom'=0,'margin-top'=0,'font-size'='28pt','text-align'='left',color ='#8C8C8C','padding-left'='10px') ),style =css(width ='70%') ), tags$div(plotTag( state_route_map,alt ="Map of all Amtrak routes",height =200 ),style =css(width ='30%')),style =css(width ='1250px',display ='inline-flex'))) %>%# add footnotes and source notes htmlwidgets::appendContent( tags$div( tags$link(href ="https://fonts.googleapis.com/css?family=Recursive:400,600,700&display=swap", rel ="stylesheet"), tags$sup("*"), "Amtrak suspended Adirondack service in July 2021, and no resumption date has been set as of October 2022.", tags$br(), tags$sup("**"), "Berkshire Flyer seasonal service began in 2022, and Valley Flyer service began in 2019.",style =css(display ='inline-block','text-align'='left','font-family'='Recursive',color ='black', 'font-size'='9pt','border-bottom-style'='solid','border-top-style'='solid',width ='1250px','padding-bottom'='8px','padding-top'='8px','padding-left'='10px','border-color'='#DADADA')), tags$div( tags$link(href ="https://fonts.googleapis.com/css?family=Roboto:400,600,700&display=swap", rel ="stylesheet"), tags$div("Data Sources: Wikipedia, US Dept of Transportation, US Census Bureau, TrainWeb.org, and OpenStreetMaps | ",style =css(display ='inline-block', 'vertical-align'='middle')), tags$div( shiny::icon("twitter"), style =css(display ='inline-block', 'vertical-align'='middle')), tags$div( tags$a("@joshfangmeier", href ="https://twitter.com/joshfangmeier", target ="_blank"),style =css(display ='inline-block', 'vertical-align'='middle')), tags$div( shiny::icon("github"), style =css(display ='inline-block', 'vertical-align'='middle')), tags$div( tags$a("jfangmeier", href ="https://github.com/jfangmeier", target ="_blank"), style =css(display ='inline-block', 'vertical-align'='middle')),style =css('text-align'='left','font-family'='Roboto', color ='#8C8C8C', 'font-size'='10pt', width ='1250px', 'padding-top'='8px', 'padding-left'='10px',display ='inline-block', 'vertical-align'='middle') ) )
5 Display the table
All Aboard!
Exploring the Amtrak Passenger Rail System
*
Amtrak suspended Adirondack service in July 2021, and no resumption date has been set as of October 2022.
**
Berkshire Flyer seasonal service began in 2022, and Valley Flyer service began in 2019.
Data Sources: Wikipedia, US Dept of Transportation, US Census Bureau, TrainWeb.org, and OpenStreetMaps |