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/RtmpuL78C6/flags' and will be copied here:
#> '/home/runner/work/trelliscope/trelliscope/docs/articles/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.