As a supplement to a recently published study by Marcel Helbig and Katja Salomo (available only in German) about socioeconomic inequalities for children in seven German cities, I’ve created an interactive web visualization with R Shiny and I wanted to share a few experiences that I made during development. This will be mainly about interactive visualization of geospatial data and custom UI elements. Below is a link to an example showing social welfare support rate amongst children and several environmental characteristics in Saarbrucken.
Leaflet for R
Leaflet for R is the most important package that you will need for such applications. Most features from the underlying leaflet JavaScript library can be accessed via R with this package. Furthermore, it comes with dedicated Shiny integration via the renderLeaflet()
function. I found it helpful to use map panes for stacking different layers. Each layer then displays several elements which have an unique group ID and that can be toggled with the controls on the left. Using a group ID allows quick removal of the elements via clearGroup()
.
Spatial data preparation with sf
Leaflet for R works with dataframes or matrices with longitude/latitude coordinates or spatial objects from the sp package. The successor sf package is usually easier to work with in terms of spatial data operations and you can convert sf dataframes to sp objects viaas(<sf dataframe>, 'Spatial')
.
Spatial data can be quite large and some of the original data I got was several hundred megabytes large, which is much too large for a web application. If you’d display this without further processing on a leaflet map, it would take many minutes to transfer and would also be very slow to render, especially on older computers, because of the complexity of the vector data. So it’s absolutely necessary to reduce the complexity and with this the size of the spatial data using methods that I explained in a previous blog post on simplifying geospatial data.
An alternative to reducing the complexity of the spatial vector data is to convert this data to raster data, since Leaflet for R can also display raster images. However, this may result in bad image quality depending on the image resolution and zoom level.
In any case you should aim to minimize the amount of data which must be loaded, transferred and rendered, while still providing accurate visualizations. I spent quite some time in experimenting with polygon simplification parameters and other geometric operations to achieve a small memory and render complexity footprint. With this, I could for example reduce the size of the full spatial data for Berlin (i.e. all features that can be toggled in the menu on the left side) from more than 450MB to just 9MB. It’s also important on the server side to run all spatial data preparation via a separate script (i.e. not in your app.R
) only once you update your dataset and then store the result on disk (e.g. as RDS file). Simply loading a fully prepared spatial dataset from an RDS file in your Shiny application is much faster than reading and parsing your dataset with read_sf()
. In my Shiny application I load this data from disk only once it is needed an then cache it for further use in an app-wide reactiveValues()
data structure. For more complex applications, using a (spatial) database is advised.
Customized UI elements
The menu on the left side which allows to toggle the displayed features should act as legend for the map at the same time. Toggle switches can be realized with Shiny’s grouped checkboxes input implemented in checkboxGroupInput()
. The problem with this is that you can’t easily add the legend to each checkbox, though. However, I noticed that you can hack the nested list that is generated by checkboxGroupInput()
and represents the HTML structure of the UI element. You can have a look at the list structure of any such Shiny UI element by simply generating one in R and printing it at first:
> library(shiny)
> opts <- c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")
> custom_inp <- checkboxGroupInput("variable",
"Variables to show:",
opts)
> custom_inp
<div id="variable" class="form-group shiny-input-checkboxgroup shiny-input-container">
<label class="control-label" for="variable">Variables to show:</label>
<div class="shiny-options-group">
<div class="checkbox">
<label>
<input type="checkbox" name="variable" value="cyl"/>
<span>Cylinders</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="variable" value="am"/>
<span>Transmission</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="variable" value="gear"/>
<span>Gears</span>
</label>
</div>
</div>
</div>
By printing the UI element, its HTML structure is shown but the element is actually a list. You can browse through the list structure for example with RStudio’s View(custom_inp)
command. You will notice that you can traverse through the list/HTML tree by using the $children
element. The following, for example, gives a subset of the above output, the checkbox input container div for option #2, “Transmission”:
> custom_inp$children[[2]]$children[[1]][[2]]
<div class="checkbox">
<label>
<input type="checkbox" name="variable" value="am"/>
<span>Transmission</span>
</label>
</div>
With this, we can also alter the UI element’s HTML structure. Let’s replace the checkbox label and set a red color via the CSS style attribute:
custom_inp$
children[[2]]$
children[[1]][[2]]$
children[[1]]$
children[[2]] <- span('Transmission in red!', style = 'color:red')
The following minimal Shiny application …
ui <- fluidPage(
custom_inp,
tableOutput("data")
)
server <- function(input, output, session) {
output$data <- renderTable({
mtcars[, c("mpg", input$variable), drop = FALSE]
}, rownames = TRUE)
}
shinyApp(ui, server)
… will now give us this output:
Using this approach, you can use Shiny’s large inventory of beautiful UI elements and customize them quickly. I used this to add small HTML/CSS-only “icons” before the checkbox labels to generate the map legend.
Conclusion
All in all, making such an application was much less trouble than I thought in the beginning, thanks to Shiny and the Leaflet package which do the heavy lifting in terms of UI, interactivity and spatial data visualization. The whole web application (without the spatial data preparation scripts) is written in less than 500 lines of R code. The source code for the application can be sent upon request.
Hallo,
spannendes Projekt! Ich hätte tatsächlich Interesse am Source Code – könntet ihr mir den zuschicken?
Vielen Dank!