Creating Panel Columns

library(trelliscope)
library(ggplot2)
library(dygraphs)
library(dplyr, warn.conflicts = FALSE)

This article provides a more in-depth look at different ways to create panel columns for use with Trelliscope. It is assumed that you have read the Introduction article and are familiar with the basics of Trelliscope.

Using facet_panels() with ggplot2

We will stick with the gapminder example for this article (using the gap version of the gapminder data that comes with this package). In the Introduction article, we showed how to visualize life expectancy vs. time for each country with ggplot2 using facet_panels() and as_panels_df().

gp <- (
  ggplot(gap, aes(year, life_exp)) +
    geom_point() +
    facet_panels(vars(country, continent, iso_alpha2))
  ) |>
  as_panels_df(panel_col = "lexp_time")

gp
#> # A tibble: 142 × 4
#>    country     continent iso_alpha2 lexp_time 
#>    <chr>       <fct>     <chr>      <ggpanels>
#>  1 Afghanistan Asia      AF         <ggplot>  
#>  2 Albania     Europe    AL         <ggplot>  
#>  3 Algeria     Africa    DZ         <ggplot>  
#>  4 Angola      Africa    AO         <ggplot>  
#>  5 Argentina   Americas  AR         <ggplot>  
#>  6 Australia   Oceania   AU         <ggplot>  
#>  7 Austria     Europe    AT         <ggplot>  
#>  8 Bahrain     Asia      BH         <ggplot>  
#>  9 Bangladesh  Asia      BD         <ggplot>  
#> 10 Belgium     Europe    BE         <ggplot>  
#> # ℹ 132 more rows

The lexp_time column is a list of what appears to be ggplot2 objects, one for each panel. What is actually happening is a bit more complex. lexp_time is a special vector that contains minimal information for each row needed to create the ggplot facet for that particular row, along with a plotting function that contains instructions for how to create the panel and the underlying data. When a single plot is rendered (e.g. calling gap$lexp_time[[1]]), the plotting function is called with the data for that particular panel. And when you view this data frame as a Trelliscope data frame, it will write out all the panels using these instructions. This is a special case of a more general “lazy” panel specification approach that we will discuss in the next section.

Note that as_panels_df() has an arument as_plotly argument, which if TRUE, will convert the ggplot panels to Plotly. Additional arguments plotly_args and plotly_cfg are provided to further customize the Plotly output.

Custom R-generated panels with panel_lazy()

A typical use case for visualizing data with Trelliscope, as seen in the example above, is that we have a larger dataset which we aggregate in some way according to some grouping variables, and we then want to visualize the larger dataset corresponding to each group. One way to do this is as we did above, using faceting to specify the subsetting and “summary” visualizations. Another way to do it is to summarize the data first and then specify a plotting function that provides a plot for each row of the summary dataset which can be based on data from the full dataset.

For example, consider the following simple summary dataset of mean life expectancy and mean GDP per capita for each country in the gapminder dataset.

gsumm <- gap %>%
  summarise(
    mean_lexp = mean(life_exp),
    mean_gdp = mean(gdp_percap),
    .by = c("continent", "country", "iso_alpha2")
  )
gsumm
#> # A tibble: 142 × 5
#>    continent country     iso_alpha2 mean_lexp mean_gdp
#>    <fct>     <chr>       <chr>          <dbl>    <dbl>
#>  1 Asia      Afghanistan AF              37.5     803.
#>  2 Europe    Albania     AL              68.4    3255.
#>  3 Africa    Algeria     DZ              59.0    4426.
#>  4 Africa    Angola      AO              37.9    3607.
#>  5 Americas  Argentina   AR              69.1    8956.
#>  6 Oceania   Australia   AU              74.7   19981.
#>  7 Europe    Austria     AT              73.1   20412.
#>  8 Asia      Bahrain     BH              65.6   18078.
#>  9 Asia      Bangladesh  BD              49.8     818.
#> 10 Europe    Belgium     BE              73.6   19901.
#> # ℹ 132 more rows

Now suppose that we would like to use the dygraphs package to create an interactive time series plot of life expectancy over time for each country. To do this, we create a plot function that takes an input, country, and returns a dygraph plot of life expectancy vs. time for the gap dataset filtered to that country.

library(dygraphs)

plot_fn <- function(country) {
  x <- filter(gap, country == {{ country }})
  dygraph(select(x, year, life_exp)) %>%
    dyOptions(strokeWidth = 3, drawPoints = TRUE, pointSize = 4, gridLineColor = "#dedede") %>%
    dyHighlight(highlightCircleSize = 6) %>%
    dyAxis("x", label = "Year", valueRange = c(1952, 2007)) %>%
    dyAxis("y", label = "Life Expectancy (yrs)", valueRange = c(23.59, 82.61)) %>%
    dyRangeSelector()
}

plot_fn("Afghanistan")

Now all we need to do to add a new plot column to gsumm is to use panel_lazy() which takes our plot function and the dataset it applies to as arguments. Note that if used in a dplyr mutate(), the dataset is inferred and does not need to be provided.

gsumm <- gsumm %>%
  mutate(lexp_time = panel_lazy(plot_fn))

gsumm
#> # A tibble: 142 × 6
#>    continent country     iso_alpha2 mean_lexp mean_gdp lexp_time    
#>    <fct>     <chr>       <chr>          <dbl>    <dbl> <lazy_panels>
#>  1 Asia      Afghanistan AF              37.5     803. <htmlwidget> 
#>  2 Europe    Albania     AL              68.4    3255. <htmlwidget> 
#>  3 Africa    Algeria     DZ              59.0    4426. <htmlwidget> 
#>  4 Africa    Angola      AO              37.9    3607. <htmlwidget> 
#>  5 Americas  Argentina   AR              69.1    8956. <htmlwidget> 
#>  6 Oceania   Australia   AU              74.7   19981. <htmlwidget> 
#>  7 Europe    Austria     AT              73.1   20412. <htmlwidget> 
#>  8 Asia      Bahrain     BH              65.6   18078. <htmlwidget> 
#>  9 Asia      Bangladesh  BD              49.8     818. <htmlwidget> 
#> 10 Europe    Belgium     BE              73.6   19901. <htmlwidget> 
#> # ℹ 132 more rows

If we create this plot column outside of dplyr, we need to provide the dataset as an argument:

gsumm$lexp_time <- panel_lazy(plot_fn, data = gsumm)

Note that in this case these panels are htmlwidgets. Your plot function can return any type of htmlwidget or plot objects that can be printed to image files, such as ggplot2 objects.

We can now view a plot for any country by indexing into the lexp_time column.

gsumm$lexp_time[[1]]
#> <panel_lazy_vec[1]>
#> [1] <htmlwidget>

An important thing to note is that your plot function arguments must match variable names found in the dataset. In this case, the country argument of plot_fn() matches the country column in gsumm. You can access any columns from the dataset that you would like. For example, if you want to use the iso_alpha2 column to help label the plot, you can this:

plot_fn <- function(country, iso_alpha2) {
  x <- filter(gap, country == {{ country }})
  dygraph(select(x, year, life_exp),
    main = paste0(country, " (", iso_alpha2, ")")) %>%
    dyOptions(strokeWidth = 3, drawPoints = TRUE, pointSize = 4, gridLineColor = "#dedede") %>%
    dyHighlight(highlightCircleSize = 6) %>%
    dyAxis("x", label = "Year", valueRange = c(1952, 2007)) %>%
    dyAxis("y", label = "Life Expectancy (yrs)", valueRange = c(23.59, 82.61)) %>%
    dyRangeSelector()
}

When applied to any row of gsumm, the iso_alpha2 column will be passed to plot_fn() along with the country column.

Another thing to note is that if you are using dplyr to filter inside your plot function, please be aware of issues that can arise from data masking. For example, you will notice in our example that we need to use the embrace operator {{ in our filter() to keep the code execution from getting confused about whether we are referring to a column name in the data or not. If you are not using dplyr, something like x <- gap[gap$country == country, ] would work fine.

Panels of images existing on the web with panel_url()

If image or html assets exist on the web, you can create panel columns pointing to these using panel_url(). For example, suppose we want to add a column of flags for each country. This forked repository contains image files for each country flag, where each file is named using the country’s 2-letter code. We can use the iso_alpha2 column in the gapminder dataset to construct the URL for each flag.

flag_base_url <- "https://raw.githubusercontent.com/hafen/countryflags/master/png/512/"

gsumm <- mutate(gsumm,
  flag_url = panel_url(paste0(flag_base_url, iso_alpha2, ".png"))
)
gsumm
#> # A tibble: 142 × 7
#>    continent country     iso_alpha2 mean_lexp mean_gdp lexp_time     flag_url   
#>    <fct>     <chr>       <chr>          <dbl>    <dbl> <lazy_panels> <url_panel>
#>  1 Asia      Afghanistan AF              37.5     803. <htmlwidget>  <img>      
#>  2 Europe    Albania     AL              68.4    3255. <htmlwidget>  <img>      
#>  3 Africa    Algeria     DZ              59.0    4426. <htmlwidget>  <img>      
#>  4 Africa    Angola      AO              37.9    3607. <htmlwidget>  <img>      
#>  5 Americas  Argentina   AR              69.1    8956. <htmlwidget>  <img>      
#>  6 Oceania   Australia   AU              74.7   19981. <htmlwidget>  <img>      
#>  7 Europe    Austria     AT              73.1   20412. <htmlwidget>  <img>      
#>  8 Asia      Bahrain     BH              65.6   18078. <htmlwidget>  <img>      
#>  9 Asia      Bangladesh  BD              49.8     818. <htmlwidget>  <img>      
#> 10 Europe    Belgium     BE              73.6   19901. <htmlwidget>  <img>      
#> # ℹ 132 more rows

Panels of images on the local filesystem with panel_local()

Suppose we instead want to use local image files. We can use panel_local() to create a panel column that points to local files.

We will download all of the flag images to a temporary directory to illustrate.

flag_path <- tempdir()
download.file("https://github.com/trelliscope/trelliscope/files/12265140/flags.zip",
  destfile = file.path(flag_path, "flags.zip"))
unzip(file.path(flag_path, "flags.zip"), exdir = file.path(flag_path, "flags"))
list.files(file.path(flag_path, "flags"))
#>   [1] "AF.png" "AL.png" "AO.png" "AR.png" "AT.png" "AU.png" "BA.png" "BD.png"
#>   [9] "BE.png" "BF.png" "BG.png" "BH.png" "BI.png" "BJ.png" "BR.png" "BW.png"
#>  [17] "CA.png" "CF.png" "CH.png" "CL.png" "CM.png" "CN.png" "CO.png" "CR.png"
#>  [25] "CU.png" "DE.png" "DJ.png" "DK.png" "DO.png" "DZ.png" "EC.png" "EG.png"
#>  [33] "ER.png" "ES.png" "ET.png" "FI.png" "FR.png" "GA.png" "GB.png" "GH.png"
#>  [41] "GM.png" "GN.png" "GQ.png" "GR.png" "GT.png" "GW.png" "HN.png" "HR.png"
#>  [49] "HT.png" "HU.png" "ID.png" "IE.png" "IL.png" "IN.png" "IQ.png" "IS.png"
#>  [57] "IT.png" "JM.png" "JO.png" "JP.png" "KE.png" "KH.png" "KM.png" "KW.png"
#>  [65] "LB.png" "LK.png" "LR.png" "LS.png" "LY.png" "MA.png" "ME.png" "MG.png"
#>  [73] "ML.png" "MM.png" "MN.png" "MR.png" "MU.png" "MW.png" "MX.png" "MY.png"
#>  [81] "MZ.png" "NA.png" "NE.png" "NG.png" "NI.png" "NL.png" "NO.png" "NP.png"
#>  [89] "NZ.png" "OM.png" "PA.png" "PE.png" "PH.png" "PK.png" "PL.png" "PR.png"
#>  [97] "PT.png" "PY.png" "RO.png" "RS.png" "RW.png" "SA.png" "SD.png" "SE.png"
#> [105] "SG.png" "SI.png" "SL.png" "SN.png" "SO.png" "ST.png" "SV.png" "SZ.png"
#> [113] "TD.png" "TG.png" "TH.png" "TN.png" "TR.png" "TT.png" "UG.png" "UY.png"
#> [121] "ZA.png" "ZM.png" "ZW.png"

We can now create a panel column that points to these local files.

gsumm <- mutate(gsumm,
  flag = panel_local(file.path(flag_path, "flags", paste0(iso_alpha2, ".png"))),
)
gsumm
#> # A tibble: 142 × 8
#>    continent country   iso_alpha2 mean_lexp mean_gdp lexp_time    flag_url flag 
#>    <fct>     <chr>     <chr>          <dbl>    <dbl> <lazy_panel> <url_pa> <loc>
#>  1 Asia      Afghanis… AF              37.5     803. <htmlwidget> <img>    <img>
#>  2 Europe    Albania   AL              68.4    3255. <htmlwidget> <img>    <img>
#>  3 Africa    Algeria   DZ              59.0    4426. <htmlwidget> <img>    <img>
#>  4 Africa    Angola    AO              37.9    3607. <htmlwidget> <img>    <img>
#>  5 Americas  Argentina AR              69.1    8956. <htmlwidget> <img>    <img>
#>  6 Oceania   Australia AU              74.7   19981. <htmlwidget> <img>    <img>
#>  7 Europe    Austria   AT              73.1   20412. <htmlwidget> <img>    <img>
#>  8 Asia      Bahrain   BH              65.6   18078. <htmlwidget> <img>    <img>
#>  9 Asia      Banglade… BD              49.8     818. <htmlwidget> <img>    <img>
#> 10 Europe    Belgium   BE              73.6   19901. <htmlwidget> <img>    <img>
#> # ℹ 132 more rows

Note that once we make gsumm into a Trelliscope data frame and view it, local panel files will be copied into the directory of the Trelliscope display so that it is self-contained. If you want to avoid a making copy, you can create the appropriate directory structure and put the files there yourself.

Setting panel options

d <- as_trelliscope_df(gsumm, name = "gapminder")  |>
  set_default_labels(c("country", "continent")) |>
  set_default_layout(ncol = 3) |>
  set_default_sort(c("continent", "mean_lexp"), dir = c("asc", "desc"))
#> ℹ Using the variables "continent" and "country" to uniquely identify each row
#>   of the data.
#> Warning: ! Files for local panel "flag" are not in the correct location. They are
#>   currently here: '/tmp/RtmpUcAH0L/flags' and will be copied here:
#>   '/tmp/Rtmp7kC5Eq/Rbuild13091e51f5ca/trelliscope/vignettes/panels_files/figure-html/gap/displays/gapminder/panels/flag'
#>   when the display is written.

# set panel options (if not specified, defaults are used)
d <- d |>
  set_panel_options(
    lexp_time = panel_options(width = 600, height = 400)
  ) |>
  set_primary_panel("lexp_time")

view_trelliscope(d)
#> ℹ Inferred that variable 'mean_gdp' should be shown on log scale.