4.4 htmlWidgets

4.4.1 Overview of htmlWidgets

JavaScript has a number of libraries that can be used to create interactive graphics for documents viewed in a web browser. There are now several R packages that allow you to create plots built on these JavaScript libraries from within R.

While these packages draw on JavaScript libraries, they do not require you to export your data from R or to write JavaScript code. However, because the output runs on JavaScript, the resulting plots will not be interactive if included in static output like pdf or Word documents that you create using R Markdown. The output will be interactive if viewed in the “Viewer” panel of RStudio or if included in a HTML document created with R Markdown, and they can be incorporated into Shiny web applications.

There is a website with much more on these htmlWidgets at http://www.htmlwidgets.org. Some of the packages availabe to help you create interactive graphics from R using JavaScript graphics libraries are:

  • leaflet: Mapping
  • dygraphs: Time series
  • plotly: A variety of plots, including maps
  • rbokeh: A variety of plots, including maps
  • networkD3: Network data
  • d3heatmap: Heatmaps
  • DT: Data tables
  • DiagrammeR: Diagrams and flowcharts

The leaflet and plotly packages are two of the most useful and developed packages in this collection of htmlWidgets. In this section, we will overview what you can make with these two packages.

If you are interested in learning all the details about the JavaScript on which these htmlWidgets are built, you may find the short book Getting Started with D3 by Mike Dewar interesting.

4.4.2 plotly package

The plotly package in R wraps the JavaScript plotly.js package, an open source library for creating interactive graphs. The plotly.js JavaScript library is built on d3.js (Data-Driven Documents), which is a key driver in interactive web-based data graphics today. The package allows the plotting of a variety of interactive plots; one of the most interesting feature is it’s ability to plot 3-D point and surface plots that can be rotated to view them from different angles. You can find out more about the plotly.js JavaScript library at plotly’s website.

Like some of the other packages we’ve looked at (e.g., ggmap), the plotly R package allows you to draw on functionality external to R, but while work entirely within R. Some of the functions in this package extend the ggplot2 code you’ve learned.

There are two main ways of creating plots using the R plotly package:

  1. Use one of the package’s functions to create a customized interactive graphic:
    • plot_ly: Workhorse of plotly, renders most non-map types of graphs
    • plot_geo, plot_mapbax: Specific functions for creating plotly maps
  2. Create a ggplot object and then convert it to a plotly object using the ggplotly function.

When using the first method, most graphics other than maps will be created using the plot_ly function. For example, if you want to plot an interactive scatterplot of time versus shots for the World Cup 2010 data (which we have created as a static plot earlier in this section), you can do so with the following code:

library(faraway) 
data(worldcup)
library(plotly)

plot_ly(worldcup, type = "scatter",
        x = ~ Time, y = ~ Shots, color = ~ Position)

If you view this plot in a format where it is interactive, you can see that labels pop up for each point as you pass the cursor over it. Further, there are some buttons at the top of the graph that allow interactive actions like zooming, panning, and selection of a subset of the graph.

This code specifies the dataframe with the data to plot, what type of plot to create, and mappings of variables to aesthetics. In this case, we want to show Time on the x-axis and Shots on the y-axis, so we specify those for the x and y parameters. Further, we want to show player position with color, so we map the Position column to color.

This code uses a ~ syntax for mapping aesthetics that is a bit different from the ggplot syntax we’ve presented earlier. This syntax is necessary in the plotly call to let R know that these variables can be found as columns in the dataframe passed to the function. If you would like to use a constant value for an aesthetic, you must specify that the argument should be used “as-is”, using the I() function. For example, to plot all points in blue, you could run:

plot_ly(worldcup, type = "scatter",
        x = ~ Time, y = ~ Shots, color = I("blue"))

While you usually won’t use ~ syntax like this when using ggplot2 in interactive coding, you will use it to avoid non-standard evaluation when using ggplot2 code in functions you write for a package. See the section on non-standard evaluation earlier in the book for more on this concept.

By default, the pop-ups will show the mapped aesthetics when you move the cursor over each point. However, you can change this default to show something different when the viewer scrolls over each point. For example, the plot we created above for the World Cup data maps player time to the x aesthetic, shots to the y aesthetic, and color to the player’s position. Therefore, by default these three values will be shown for a point if you move the cursor over the point. However, you might prefer to show each player’s name, which is contained in the rownames of the worldcup data. You can do this by using dplyr tools to move the rownames to a column named Name and then mapping that column to the text aesthetic and specifying that aesthetic to the hoverinfo parameter:

worldcup %>%
  mutate(Name = rownames(worldcup)) %>%
  plot_ly(x = ~ Time, y = ~ Shots, color = ~ Position) %>%
  add_markers(text = ~ Name, hoverinfo = "text")

You can use the paste function to create a more customized text label. Use HTML tags for any formatting. For example, to show both the player’s name and team in a more attractive format, you could run:

worldcup %>%
  mutate(Name = rownames(worldcup)) %>%
  plot_ly(x = ~ Time, y = ~ Shots, color = ~ Position) %>%
  add_markers(text = ~ paste("<b>Name:</b> ", Name, "<br />", 
                             "<b>Team:</b> ", Team),
              hoverinfo = "text")

If you aren’t familiar with HTML syntax, you may find it helpful to use a HTML cheatsheet like this one.

Just like with ggplot2, the mappings you need depend on the type of plot you are creating. For example, scatterplots (type = "scatter") need x and y defined, while a surface plot (type = "surface") can be created with a single vector of elevation, using a mapping to the z aesthetic.

The plotly package is designed so you can pipe data into plot_ly and add elements by piping into add_* functions (this idea is similar to adding elements to a ggplot object with +). For example, you could create the same scatterplot we just created by piping the World Cup data into plotly, and then piping that output to add_markers, without needing to specify that the type of plot should be a scatterplot as we did in the last code chunk:

worldcup %>%
  plot_ly(x = ~ Time, y = ~ Shots, color = ~ Position) %>%
  add_markers()

The add_* functions for plotly include:

  • add_markers
  • add_lines
  • add_paths
  • add_polygons
  • add_segments
  • add_histogram

If you pipe to the rangeslider function, it allows the viewer to zoom in on part of the x range. This functionality can be particularly nice for time series. For example, you can read in data on the maximum winds for Hurricane Floyd at different points along its track. You can pipe the result of reading in the csv directly into the plot_ly call. To show a time series of wind speeds, map the time stamp to the x aesthetic and the wind to the y aesthetic. You can then add a line and range slider:

read_csv("data/floyd_track.csv") %>%
  plot_ly(x = ~ datetime, y = ~ max_wind) %>% 
  add_lines() %>%
  rangeslider()

Notice that, in the output, you can change the range of data plotted in the top graph by interactively adjusting the window shown in the lower plot.

You can make a 3-D scatterplot with plot_ly by mapping a variable to the z variable. For example, to plot a scatter plot of time, shots, and passes in the World Cup 2010 data, you can run (note that size is set with a constant value to make the points larger):

worldcup %>%
  plot_ly(x = ~ Time, y = ~ Shots, z = ~ Passes,
          color = ~ Position, size = I(3)) %>%
  add_markers()

Again, if you move the cursor over the points in the scatterplot, you can see the value of the point. Further, the tool bar above the plot includes buttons that allow you to rotate the plot and look at it from different angles.

Similarly, you can create 3-D surface plots with the plot_ly function. In this case, if you have a matrix of data regularly spaced on x- and y-dimensions, with the cell values in the matrix giving values of a third variable, you can create a surface map with plot_ly by mapping the matrix values to the z aesthetic. The helpfile for plot_ly includes an example using the volcano data that comes with R. This data is in a matrix format, and each value gives the elevation for a particular pair of x- and y-coordinates for a volcano in New Zealand.

class(volcano)
[1] "matrix"
volcano[1:4, 1:4]
     [,1] [,2] [,3] [,4]
[1,]  100  100  101  101
[2,]  101  101  102  102
[3,]  102  102  103  103
[4,]  103  103  104  104

You can use the following code to create a 3-D surface plot of this data.

plot_ly(z = ~ volcano, type = "surface")

The other way to create a plotly graph is to first create a ggplot object and then transform it into an interactive graphic using the ggplotly function.

Earlier in this subsection, we used plot_ly to create an interactive scatterplot with the World Cup. We could have created the same plot by first creating a ggplot object with the scatterplot and then passing it to the ggplotly function:

worldcup_scatter <- worldcup %>%
  ggplot(aes(x = Time, y = Shots, color = Position)) + 
  geom_point() 
ggplotly(worldcup_scatter)
We recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`

If you get an error when you try this code, make sure you have the latest versions of ggplot2 and plotly installed. It may be necessary for you to install the development version of plotly directly from GitHub, which you can do using devtools::install_github("ropensci/plotly").

If you would like to find out more about what you can do with the plotly package, the creator of the package has written a bookdown book on the package that you can read here.

4.4.3 Leaflet

Leaflet is a JavaScript library that you can use to create very attractive interactive maps. You will recognize the output, as maps created using Leaflet are now very common on websites. You can find out more about the JavaScript version here: http://leafletjs.com. The leaflet package allows you to create these maps from within R. As with other htmlWidgets, you can explore these maps in the “Viewer” pane of RStudio and also add them to HTML R Markdown output and Shiny web applications.

For the examples in these section, we’ll use the data on fatal accidents and census tracts in Denver, Colorado. This data is contained in the denver_tracts and driver_data datasets created in an earlier subsection of the book. If you need to, you can reload those using the following code (replace the filepath in the load call with the filepath to where you have saved this example data on your own computer):

library(tigris)
denver_tracts <- tracts(state = "CO", county = 31, cb = TRUE)
load("data/fars_colorado.RData")
denver_fars <- driver_data %>% 
  filter(county == 31 & longitud < -104.5)

To start creating a leaflet map in R, you need to initialize a leaflet object (this is similar to how you initialize a ggplot object when creating plots with ggplot2). You do this with the leaflet function. If you just run leaflet() without adding any elements, however, you just get a blank leaflet area:

library(leaflet)
leaflet()

In leaflet, the map background is composed of map tiles, which you can pull from a number of different sources. To get a background for your leaflet map, you’ll need to add tiles to the object created by leaflet. If you don’t add any elements other than tiles, the leaflet map will zoom out to show the world:

leaflet() %>%
  addTiles()

Once you have a leaflet object and map tiles, you’ll add other elements to show your data. This is similar to adding geoms to a ggplot object.

A common element you’ll want to add are points showing locations. You can add points using markers (these will give the map “pins” you may be familiar with from Google maps) or circle markers. You add these elements by piping the current leaflet object into an addMarkers or addCircleMarkers function. These functions can input either a dataframe of data or a spatial object (SpatialPoints, SpatialLines, etc.).

For example, to add markers for all the fatal accidents from the Denver dataset, you can call (note: this data is by driver, so there will be a point for every car involved in each accident):

leaflet() %>%
  addTiles() %>%
  addMarkers(data = denver_fars, lng = ~ longitud, lat = ~ latitude)

In the call to addMarkers, the lng and lat parameters tell R which columns contain data on longitude and latitude for each point. These parameters are not needed if you are using a spatial object (e.g., SpatialPointsDataFrame). Further, R will try to guess which columns show longitude and latitude in a regular dataframe if you do not specify these parameters.

To use circles for your markers instead of pins, use addCircleMarkers. You can adjust the circle size with the radius parameter.

leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data = denver_fars, radius = 2,
                   lng = ~ longitud, lat = ~ latitude)

If you have a lot of overlapping data, you may prefer to use the clusterOptions argument when adding markers. When using this option, markers are shown as clusters that group together when you zoom out but split up when you zoom in, so they can be useful when you have very dense points you would like to map, as in this example.

leaflet() %>%
  addTiles() %>%
  addMarkers(data = denver_fars, 
                   lng = ~ longitud, lat = ~ latitude,
                   clusterOptions = markerClusterOptions())

The background map comes from the map tiles you add to the leaflet object. For the background, the default is to use map tiles from OpenStreetMap. However, you can use different tiles by changing the source of the tiles. To do this, use the addProviderTiles function in place of the addTiles function and specify the provider of the tiles you would like to use. To figure out what you would like to use, you can see previews of provider choices here: http://leaflet-extras.github.io/leaflet-providers/preview/index.html.

For example, to use Stamen watercolor map tiles, you can call:

leaflet() %>%
  addProviderTiles("Stamen.Watercolor") %>%
  addCircleMarkers(data = denver_fars, radius = 2,
                   lng = ~ longitud, lat = ~ latitude)

Similarly, to use Thunderforest TransportDark tiles, you can all:

leaflet() %>%
  addProviderTiles("Thunderforest.TransportDark") %>%
  addCircleMarkers(data = denver_fars, radius = 2, color = I("red"),
                   lng = ~ longitud, lat = ~ latitude)

You can also add pop-ups that show information about a point when a user clicks on the point. To do this, use the popup option in the function in the function where you add the element to the leaflet object. The popup parameter requires a character vector, so if you want to show something currently in a different class vector, wrap it in paste. For example, to add popups giving the age of the driver for the map of accidents, you can run:

leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data = denver_fars, radius = 2, 
                   lng = ~ longitud, lat = ~ latitude,
                   popup = ~ paste(age))

You can build nicely formatted popups by adding HTML tags into the character string for the pop-up. For example, to make it clearer to viewers that the pop-up is showing age, you could use paste and some HTML formatting to create the character string for the popup parameter.

leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data = denver_fars, radius = 2, 
                   lng = ~ longitud, lat = ~ latitude,
                   popup = ~ paste("<b>Driver age:</b>", age))

If you are going to make more complex pop-ups, you might want to create a column with the pop-up strings before passing the data into the leaflet call. For example, you could create pop-ups that show driver age, the date and time of the accident, and blood alcohol content if that data is available:

denver_fars <- denver_fars %>%
  mutate(popup_info = paste("<b>Driver age:</b>", age, "<br />",
                            "<b>Date:</b>", format(date, "%Y-%m-%d"), "<br />",
                            "<b>Time:</b>", format(date, "%H:%M"), "<br />"),
         popup_info = ifelse(!is.na(alc_res),
                             paste(popup_info,
                                   "<b>Blood alcohol</b>", alc_res, "<br />"),
                             popup_info)) 

denver_fars %>%
  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(radius = 2, lng = ~ longitud, lat = ~ latitude,
                   popup = ~ popup_info)

In the popups, you can use HTML to format things like color, typeface, and size. You can also add links.

To use color to show a value, you need to do a few things. First, you need to the the colorFactor function (or another in its family) to create a function for mapping from values to colors. Then, you need to use this within the call to add the markers. For example, the drunk_dr column in the denver_fars data gives the number of drunk drivers involved in an accident. You can use the following code to show that value using color in the leaflet map:

library(viridis)
pal <- colorFactor(viridis(5), denver_fars$drunk_dr)
leaflet() %>%
  addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
  addCircleMarkers(data = denver_fars, radius = 2,
                   lng = ~ longitud, lat = ~ latitude,
                   popup = ~ popup_info,
                   color = ~ pal(drunk_dr)) 

The colorFactor function (and related functions) actually creates a new function, which is why its syntax in this call is a bit different than the syntax used to set other parameters. Note that in this code we are using the viridis function from the viridis package within the pal call to use a viridis color palette for the points.

Once you have mapped a variable to color, you can add a legend to explain the mapping. You can do that with the addLegend function, which must include values for the color palette and values for each point from this color palette.

library(viridis)
pal <- colorFactor(viridis(5), denver_fars$drunk_dr)
leaflet() %>%
  addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
  addCircleMarkers(data = denver_fars, radius = 2,
                   lng = ~ longitud, lat = ~ latitude,
                   popup = ~ popup_info,
                   color = ~ pal(drunk_dr)) %>%
  addLegend(pal = pal, values = denver_fars$drunk_dr)

You can add polygons to leaflet objects with the addPolygons function. For example, you can use the following code to add the census tract boundaries for Denver to a leaflet object:

leaflet() %>%
  addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
  addPolygons(data = denver_tracts)

You can add popups for polygons, as well:

leaflet() %>%
  addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
  addPolygons(data = denver_tracts, 
              popup = paste0("Tract ID:  ", denver_tracts@data$NAME))

Note that, because the denver_tracts object is a spatial object, we’ve used @data to pull a value from the spatial objects attribute dataframe to use in the pop-ups, but we do not need to specify lat or lng in the addPolygons call.

You can overlay multiple elements on a leaflet map. For example, you add elements to show both accidents and tracts by adding accidents using markers and adding census tracts using polygons:

leaflet() %>%
  addProviderTiles("Thunderforest.Transport") %>%
  addPolygons(data = denver_tracts,
              popup = paste0("Tract ID:  ", denver_tracts@data$NAME),
              color = "#000000", fillColor = "969696", 
              weight = 2) %>%
  addCircleMarkers(data = denver_fars, lat = ~ latitude, 
                   lng = ~ longitud, radius = 2,
                   popup = ~ popup_info, opacity = 0.9,
                   color = ~ pal(drunk_dr)) %>%
  addLegend(pal = pal, values = denver_fars$drunk_dr, opacity = 0.9)

You can allow the user to pick which layers to show on the graph by adding addLayersControls. When using this function, add group specifications to each of your map layers, and then specify which to include as overlays in the overlayGroups parameter of addLayersControl. For example, this code adds layer control to the map of Denver accidents:

leaflet() %>%
  addProviderTiles("Thunderforest.Transport") %>%
  addPolygons(data = denver_tracts,
              popup = paste0("Tract ID:  ", denver_tracts@data$NAME),
              color = "#000000", fillColor = "969696", 
              weight = 2, group = "tracts") %>%
  addCircleMarkers(data = denver_fars, lat = ~ latitude, 
                   lng = ~ longitud, radius = 2,
                   popup = ~ popup_info, opacity = 0.9,
                   color = ~ pal(drunk_dr),
                   group = "accidents") %>%
  addLegend(pal = pal, values = denver_fars$drunk_dr, opacity = 0.9) %>%
  addLayersControl(baseGroups = c("base map"), 
                   overlayGroups = c("tracts", "accidents"))