Aim of this document

I will show you some options to make side-by-side ggplots in R. While there are many options t=out there, I will cover only three:

A more advanced example using clustering is here!

A small note

I wish to demonstrate a rather annoying behaviour of ggplotly in the plotly package.

One of the most powerful function in plotly is the ggplotly function. For a ggplot2-addict like myself it takes a static ggplot and makes it interative.

However, this function has an undesirable legend when trying to produce a side-by-side plot. I found that the work around of this to be a bit harder than I would like. One way around this is to use the patchwork + ggiraph framework.

Just so you know that I know:

  • Yes, mtcars data is overused, but hey! It is only a quick coding demo!
library(tidyverse)
library(ggiraph)
library(patchwork)
library(plotly)

Making a better mtcars data

The code below will make variables with less than 6 unique values to be factor variables. This is because variables like cyl should be visualised as factors.

## We will make the mtcars data into a tibble and convert the rownames into a column of id
dat = mtcars %>% 
  tibble::rownames_to_column("id") %>% 
  as_tibble()

## Computing the number of unique values in each column
## We will make columns with less or equal to 6 unique values to be factors
num_unique = function(x){x %>% unique %>% length}
(vars_unique = dat %>% apply(2, num_unique))
##   id  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##   32   25    3   27   22   22   29   30    2    2    3    6
(fct_cols = names(vars_unique)[vars_unique <= 6])
## [1] "cyl"  "vs"   "am"   "gear" "carb"
## Mutating the columns based on the previous calculation
dat = dat %>% 
  as_tibble() %>% 
  mutate_at(.vars = fct_cols,
            .funs = as.factor)

dat
## # A tibble: 32 x 12
##    id            mpg cyl    disp    hp  drat    wt  qsec vs    am    gear  carb 
##    <chr>       <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <fct> <fct> <fct> <fct>
##  1 Mazda RX4    21   6      160    110  3.9   2.62  16.5 0     1     4     4    
##  2 Mazda RX4 …  21   6      160    110  3.9   2.88  17.0 0     1     4     4    
##  3 Datsun 710   22.8 4      108     93  3.85  2.32  18.6 1     1     4     1    
##  4 Hornet 4 D…  21.4 6      258    110  3.08  3.22  19.4 1     0     3     1    
##  5 Hornet Spo…  18.7 8      360    175  3.15  3.44  17.0 0     0     3     2    
##  6 Valiant      18.1 6      225    105  2.76  3.46  20.2 1     0     3     1    
##  7 Duster 360   14.3 8      360    245  3.21  3.57  15.8 0     0     3     4    
##  8 Merc 240D    24.4 4      147.    62  3.69  3.19  20   1     0     4     2    
##  9 Merc 230     22.8 4      141.    95  3.92  3.15  22.9 1     0     4     2    
## 10 Merc 280     19.2 6      168.   123  3.92  3.44  18.3 1     0     4     4    
## # … with 22 more rows

Making individual static plots

These are standard ggplots.

g1 = dat %>% 
  ggplot(aes(x = mpg, y = disp, 
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point_interactive(size = 4) +
  labs(x = "Miles per gallon", 
       y = "Displacement")

g1

g2 = dat %>% 
  ggplot(aes(x = mpg, y = drat, 
             colour = cyl,
             shape = vs, 
             data_id = id,
             tooltip = id)) +
  geom_point_interactive(size = 4) +
  labs(x = "Miles per gallon", 
       y = "Rear axle ratio")
g2

Side-by-side static plots using patchwork

After some wait, patchwork is finally on CRAN! It’s great advantage is putting together p1 and p2 using the familiar ggplot2 syntax of +.

g1 + g2

Note: even though we designed these two plots as interactive plots, they will not become interactive until we put a small wrapper around it, which we will see later.

Side-by-side interactive plots using ggiraph

The main function we will use is geom_point_interactive from the ggiraph package. It behaves just like geom_point, but with two additional aesthetics. The data_id aesthetic is critical to link observations between plots and the tooltip aesthetic is optional but nice to have when mouse over a point. After making these plots, the girafe function using the same syntax in patchwork will allow us to make a pretty interactive plot!

Since I am writing in a RMarkdown file, I will also specify the height and width of my output using optional parameters.

girafe(code = print(g1 + g2), width_svg = 12, height_svg = 4)

Side-by-side interactive plots using plotly

In the code below, g3 and g4 are identical to g1 and g2 respectively, except that geom_point was used. The ggplotly function will convert the two plots into plotly interactive plots and the subplot will combine the two plots into one singular plot.

g3 = dat %>%
  ggplot(aes(x = mpg, y = disp,
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point(size = 4) +
  labs(x = "Miles per gallon",
       y = "Displacement")


g4 = dat %>%
  ggplot(aes(x = mpg, y = drat,
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point(size = 4) +
  labs(x = "Miles per gallon",
       y = "Rear axle ratio")

subplot(ggplotly(g3),
        ggplotly(g4))

Look at the colour and shape legend being repeated. This is the main reason that I don’t prefer ggplotly for this type of task. But don’t get me wrong! I still use ggplotly in my everyday work, but just not this task.

Session info

sessionInfo()
## R version 3.6.1 (2019-07-05)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS Mojave 10.14.6
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_AU.UTF-8/en_AU.UTF-8/en_AU.UTF-8/C/en_AU.UTF-8/en_AU.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] gdtools_0.2.1   plotly_4.9.1    patchwork_1.0.0 ggiraph_0.7.0  
##  [5] forcats_0.4.0   stringr_1.4.0   dplyr_0.8.3     purrr_0.3.3    
##  [9] readr_1.3.1     tidyr_1.0.0     tibble_2.1.3    ggplot2_3.2.1  
## [13] tidyverse_1.3.0
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.3        lubridate_1.7.4   lattice_0.20-38   assertthat_0.2.1 
##  [5] zeallot_0.1.0     digest_0.6.23     utf8_1.1.4        mime_0.7         
##  [9] R6_2.4.1          cellranger_1.1.0  backports_1.1.5   reprex_0.3.0     
## [13] evaluate_0.14     httr_1.4.1        pillar_1.4.2      rlang_0.4.2      
## [17] lazyeval_0.2.2    readxl_1.3.1      uuid_0.1-2        rstudioapi_0.10  
## [21] data.table_1.12.6 rmarkdown_1.18    labeling_0.3      htmlwidgets_1.5.1
## [25] munsell_0.5.0     shiny_1.4.0       broom_0.5.2       httpuv_1.5.2     
## [29] compiler_3.6.1    modelr_0.1.5      xfun_0.11         pkgconfig_2.0.3  
## [33] systemfonts_0.1.1 base64enc_0.1-3   htmltools_0.4.0   tidyselect_0.2.5 
## [37] fansi_0.4.0       viridisLite_0.3.0 later_1.0.0       crayon_1.3.4     
## [41] dbplyr_1.4.2      withr_2.1.2       grid_3.6.1        xtable_1.8-4     
## [45] nlme_3.1-142      jsonlite_1.6      gtable_0.3.0      lifecycle_0.1.0  
## [49] DBI_1.0.0         magrittr_1.5      scales_1.1.0      cli_1.1.0        
## [53] stringi_1.4.3     farver_2.0.1      promises_1.1.0    fs_1.3.1         
## [57] xml2_1.2.2        generics_0.0.2    vctrs_0.2.0       Cairo_1.5-10     
## [61] tools_3.6.1       glue_1.3.1        crosstalk_1.0.0   hms_0.5.2        
## [65] fastmap_1.0.1     yaml_2.2.0        colorspace_1.4-1  rvest_0.3.5      
## [69] knitr_1.26        haven_2.2.0
LS0tCnRpdGxlOiAic2lkZS1ieS1zaWRlIChnZylwbG90cyBpbiBSIgphdXRob3I6ICJLZXZpbiBXYW5nIgpkYXRlOiAiMDcgRGVjIDIwMTkiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdGhlbWU6IHBhcGVyCi0tLQoKIyBBaW0gb2YgdGhpcyBkb2N1bWVudAoKSSB3aWxsIHNob3cgeW91IHNvbWUgb3B0aW9ucyB0byBtYWtlIHNpZGUtYnktc2lkZSBnZ3Bsb3RzIGluIFIuIFdoaWxlIHRoZXJlIGFyZSBtYW55IG9wdGlvbnMgdD1vdXQgdGhlcmUsIEkgd2lsbCBjb3ZlciBvbmx5IHRocmVlOiAKCisgYHBhdGNod29ya2AsIHdoaWNoIGFsbG93cyBjb21iaW5hdGlvbiBvZiB0d28gb3IgbW9yZSBnZ3Bsb3RzIHVzaW5nIGEgbmF0dXJhbCBzeW50YXggb2YgYCtgIChzaW1pbGFyIG91dHB1dCBhcyBgZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2VgKQorIGBnZ2lyYXBoYCwgd2hpY2ggbGV2ZXJhZ2VzIGBwYXRjaHdvcmtgJ3Mgc3ludGF4IHRvIG1ha2UgKippbnRlcmFjdGl2ZSoqIGdncGxvdHMhCisgYHBsb3RseWAsIHdoaWNoIGNvbnZlcnRzIGdncGxvdHMgaW50byBpbnRlcmFjdGl2ZSBwbG90bHktcGxvdHMgYmVmb3JlIGNvbWJpbmluZyBldmVyeXRoaW5nIGludG8gc3VicGxvdHMuIAoKQSBtb3JlIGFkdmFuY2VkIGV4YW1wbGUgdXNpbmcgY2x1c3RlcmluZyBpcyBbaGVyZV0oZGltX3JlZHUuaHRtbCkhCgoKIyMgQSBzbWFsbCBub3RlCgpJIHdpc2ggdG8gZGVtb25zdHJhdGUgYSByYXRoZXIgYW5ub3lpbmcgYmVoYXZpb3VyIG9mIGBnZ3Bsb3RseWAgaW4gdGhlIGBwbG90bHlgIHBhY2thZ2UuIAoKT25lIG9mIHRoZSBtb3N0IHBvd2VyZnVsIGZ1bmN0aW9uIGluIGBwbG90bHlgIGlzIHRoZSBgZ2dwbG90bHlgIGZ1bmN0aW9uLiBGb3IgYSBgZ2dwbG90MmAtYWRkaWN0IGxpa2UgbXlzZWxmIGl0IHRha2VzIGEgc3RhdGljIGdncGxvdCBhbmQgbWFrZXMgaXQgaW50ZXJhdGl2ZS4gCgpIb3dldmVyLCB0aGlzIGZ1bmN0aW9uIGhhcyBhbiB1bmRlc2lyYWJsZSBsZWdlbmQgd2hlbiB0cnlpbmcgdG8gcHJvZHVjZSBhIHNpZGUtYnktc2lkZSBwbG90LiBJIGZvdW5kIHRoYXQgdGhlIHdvcmsgYXJvdW5kIG9mIHRoaXMgdG8gYmUgYSBiaXQgaGFyZGVyIHRoYW4gSSB3b3VsZCBsaWtlLiBPbmUgd2F5IGFyb3VuZCB0aGlzIGlzIHRvIHVzZSB0aGUgYHBhdGNod29ya2AgKyBgZ2dpcmFwaGAgZnJhbWV3b3JrLgoKCkp1c3Qgc28geW91IGtub3cgdGhhdCBJIGtub3c6CgorIFllcywgYG10Y2Fyc2AgZGF0YSBpcyBvdmVydXNlZCwgYnV0IGhleSEgSXQgaXMgb25seSBhIHF1aWNrIGNvZGluZyBkZW1vIQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdnaXJhcGgpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KHBsb3RseSkKYGBgCgojIE1ha2luZyBhIGJldHRlciBgbXRjYXJzYCBkYXRhCgpUaGUgY29kZSBiZWxvdyB3aWxsIG1ha2UgdmFyaWFibGVzIHdpdGggbGVzcyB0aGFuIDYgdW5pcXVlIHZhbHVlcyB0byBiZSBmYWN0b3IgdmFyaWFibGVzLiBUaGlzIGlzIGJlY2F1c2UgdmFyaWFibGVzIGxpa2UgYGN5bGAgc2hvdWxkIGJlIHZpc3VhbGlzZWQgYXMgZmFjdG9ycy4gCgpgYGB7cn0KIyMgV2Ugd2lsbCBtYWtlIHRoZSBtdGNhcnMgZGF0YSBpbnRvIGEgdGliYmxlIGFuZCBjb252ZXJ0IHRoZSByb3duYW1lcyBpbnRvIGEgY29sdW1uIG9mIGlkCmRhdCA9IG10Y2FycyAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImlkIikgJT4lIAogIGFzX3RpYmJsZSgpCgojIyBDb21wdXRpbmcgdGhlIG51bWJlciBvZiB1bmlxdWUgdmFsdWVzIGluIGVhY2ggY29sdW1uCiMjIFdlIHdpbGwgbWFrZSBjb2x1bW5zIHdpdGggbGVzcyBvciBlcXVhbCB0byA2IHVuaXF1ZSB2YWx1ZXMgdG8gYmUgZmFjdG9ycwpudW1fdW5pcXVlID0gZnVuY3Rpb24oeCl7eCAlPiUgdW5pcXVlICU+JSBsZW5ndGh9Cih2YXJzX3VuaXF1ZSA9IGRhdCAlPiUgYXBwbHkoMiwgbnVtX3VuaXF1ZSkpCihmY3RfY29scyA9IG5hbWVzKHZhcnNfdW5pcXVlKVt2YXJzX3VuaXF1ZSA8PSA2XSkKCiMjIE11dGF0aW5nIHRoZSBjb2x1bW5zIGJhc2VkIG9uIHRoZSBwcmV2aW91cyBjYWxjdWxhdGlvbgpkYXQgPSBkYXQgJT4lIAogIGFzX3RpYmJsZSgpICU+JSAKICBtdXRhdGVfYXQoLnZhcnMgPSBmY3RfY29scywKICAgICAgICAgICAgLmZ1bnMgPSBhcy5mYWN0b3IpCgpkYXQKYGBgCgoKIyBNYWtpbmcgaW5kaXZpZHVhbCBzdGF0aWMgcGxvdHMKClRoZXNlIGFyZSBzdGFuZGFyZCBnZ3Bsb3RzLgoKYGBge3IsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQ9NH0KZzEgPSBkYXQgJT4lIAogIGdncGxvdChhZXMoeCA9IG1wZywgeSA9IGRpc3AsIAogICAgICAgICAgICAgY29sb3VyID0gY3lsLAogICAgICAgICAgICAgc2hhcGUgPSB2cywKICAgICAgICAgICAgIGRhdGFfaWQgPSBpZCwKICAgICAgICAgICAgIHRvb2x0aXAgPSBpZCkpICsKICBnZW9tX3BvaW50X2ludGVyYWN0aXZlKHNpemUgPSA0KSArCiAgbGFicyh4ID0gIk1pbGVzIHBlciBnYWxsb24iLCAKICAgICAgIHkgPSAiRGlzcGxhY2VtZW50IikKCmcxCmBgYAoKYGBge3J9CmcyID0gZGF0ICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBtcGcsIHkgPSBkcmF0LCAKICAgICAgICAgICAgIGNvbG91ciA9IGN5bCwKICAgICAgICAgICAgIHNoYXBlID0gdnMsIAogICAgICAgICAgICAgZGF0YV9pZCA9IGlkLAogICAgICAgICAgICAgdG9vbHRpcCA9IGlkKSkgKwogIGdlb21fcG9pbnRfaW50ZXJhY3RpdmUoc2l6ZSA9IDQpICsKICBsYWJzKHggPSAiTWlsZXMgcGVyIGdhbGxvbiIsIAogICAgICAgeSA9ICJSZWFyIGF4bGUgcmF0aW8iKQpnMgpgYGAKCgoKIyBTaWRlLWJ5LXNpZGUgc3RhdGljIHBsb3RzIHVzaW5nIGBwYXRjaHdvcmtgCgpBZnRlciBzb21lIHdhaXQsIGBwYXRjaHdvcmtgIGlzIGZpbmFsbHkgb24gQ1JBTiEgSXQncyBncmVhdCBhZHZhbnRhZ2UgaXMgcHV0dGluZyB0b2dldGhlciBgcDFgIGFuZCBgcDJgIHVzaW5nIHRoZSBmYW1pbGlhciBgZ2dwbG90MmAgc3ludGF4IG9mIGArYC4gCgoKCjxibG9ja3F1b3RlIGNsYXNzPSJ0d2l0dGVyLXR3ZWV0Ij48cCBsYW5nPSJlbiIgZGlyPSJsdHIiPlNvIHNvIGV4Y2l0ZWQg8J+OifCfjonwn46JIHBhdGNod29yayBpcyBub3cgb24gQ1JBTiA8YSBocmVmPSJodHRwczovL3QuY28vM2tpZUNReG9KYiI+aHR0cHM6Ly90LmNvLzNraWVDUXhvSmI8L2E+PC9wPiZtZGFzaDsgVGhvbWFzIExpbiBQZWRlcnNlbiAoQHRob21hc3A4NSkgPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS90aG9tYXNwODUvc3RhdHVzLzEyMDExMjU5MzYzMTEyNzU1MjI/cmVmX3NyYz10d3NyYyU1RXRmdyI+RGVjZW1iZXIgMSwgMjAxOTwvYT48L2Jsb2NrcXVvdGU+IDxzY3JpcHQgYXN5bmMgc3JjPSJodHRwczovL3BsYXRmb3JtLnR3aXR0ZXIuY29tL3dpZGdldHMuanMiIGNoYXJzZXQ9InV0Zi04Ij48L3NjcmlwdD4KCmBgYHtyLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodD00fQpnMSArIGcyCmBgYAoKCk5vdGU6IGV2ZW4gdGhvdWdoIHdlIGRlc2lnbmVkIHRoZXNlIHR3byBwbG90cyBhcyBpbnRlcmFjdGl2ZSBwbG90cywgdGhleSB3aWxsIG5vdCBiZWNvbWUgaW50ZXJhY3RpdmUgdW50aWwgd2UgcHV0IGEgc21hbGwgd3JhcHBlciBhcm91bmQgaXQsIHdoaWNoIHdlIHdpbGwgc2VlIGxhdGVyLiAKCgojIFNpZGUtYnktc2lkZSBpbnRlcmFjdGl2ZSBwbG90cyB1c2luZyBgZ2dpcmFwaGAKClRoZSBtYWluIGZ1bmN0aW9uIHdlIHdpbGwgdXNlIGlzIGBnZW9tX3BvaW50X2ludGVyYWN0aXZlYCBmcm9tIHRoZSBgZ2dpcmFwaGAgcGFja2FnZS4gSXQgYmVoYXZlcyBqdXN0IGxpa2UgYGdlb21fcG9pbnRgLCBidXQgd2l0aCB0d28gYWRkaXRpb25hbCBhZXN0aGV0aWNzLiBUaGUgYGRhdGFfaWRgIGFlc3RoZXRpYyBpcyBjcml0aWNhbCB0byBsaW5rIG9ic2VydmF0aW9ucyBiZXR3ZWVuIHBsb3RzIGFuZCB0aGUgYHRvb2x0aXBgIGFlc3RoZXRpYyBpcyBvcHRpb25hbCBidXQgbmljZSB0byBoYXZlIHdoZW4gbW91c2Ugb3ZlciBhIHBvaW50LiBBZnRlciBtYWtpbmcgdGhlc2UgcGxvdHMsIHRoZSBgZ2lyYWZlYCBmdW5jdGlvbiB1c2luZyB0aGUgc2FtZSBzeW50YXggaW4gYHBhdGNod29ya2Agd2lsbCBhbGxvdyB1cyB0byBtYWtlIGEgcHJldHR5IGludGVyYWN0aXZlIHBsb3QhCgoKU2luY2UgSSBhbSB3cml0aW5nIGluIGEgUk1hcmtkb3duIGZpbGUsIEkgd2lsbCBhbHNvIHNwZWNpZnkgdGhlIGhlaWdodCBhbmQgd2lkdGggb2YgbXkgb3V0cHV0IHVzaW5nIG9wdGlvbmFsIHBhcmFtZXRlcnMuCgpgYGB7cn0KZ2lyYWZlKGNvZGUgPSBwcmludChnMSArIGcyKSwgd2lkdGhfc3ZnID0gMTIsIGhlaWdodF9zdmcgPSA0KQpgYGAKCgojIFNpZGUtYnktc2lkZSBpbnRlcmFjdGl2ZSBwbG90cyB1c2luZyBgcGxvdGx5YAoKSW4gdGhlIGNvZGUgYmVsb3csIGBnM2AgYW5kIGBnNGAgYXJlIGlkZW50aWNhbCB0byBgZzFgIGFuZCBgZzJgIHJlc3BlY3RpdmVseSwgZXhjZXB0IHRoYXQgYGdlb21fcG9pbnRgIHdhcyB1c2VkLiBUaGUgYGdncGxvdGx5YCBmdW5jdGlvbiB3aWxsIGNvbnZlcnQgdGhlIHR3byBwbG90cyBpbnRvIHBsb3RseSBpbnRlcmFjdGl2ZSBwbG90cyBhbmQgdGhlIGBzdWJwbG90YCB3aWxsIGNvbWJpbmUgdGhlIHR3byBwbG90cyBpbnRvIG9uZSBzaW5ndWxhciBwbG90LgoKCmBgYHtyfQpnMyA9IGRhdCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtcGcsIHkgPSBkaXNwLAogICAgICAgICAgICAgY29sb3VyID0gY3lsLAogICAgICAgICAgICAgc2hhcGUgPSB2cywKICAgICAgICAgICAgIGRhdGFfaWQgPSBpZCwKICAgICAgICAgICAgIHRvb2x0aXAgPSBpZCkpICsKICBnZW9tX3BvaW50KHNpemUgPSA0KSArCiAgbGFicyh4ID0gIk1pbGVzIHBlciBnYWxsb24iLAogICAgICAgeSA9ICJEaXNwbGFjZW1lbnQiKQoKCmc0ID0gZGF0ICU+JQogIGdncGxvdChhZXMoeCA9IG1wZywgeSA9IGRyYXQsCiAgICAgICAgICAgICBjb2xvdXIgPSBjeWwsCiAgICAgICAgICAgICBzaGFwZSA9IHZzLAogICAgICAgICAgICAgZGF0YV9pZCA9IGlkLAogICAgICAgICAgICAgdG9vbHRpcCA9IGlkKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDQpICsKICBsYWJzKHggPSAiTWlsZXMgcGVyIGdhbGxvbiIsCiAgICAgICB5ID0gIlJlYXIgYXhsZSByYXRpbyIpCgpzdWJwbG90KGdncGxvdGx5KGczKSwKICAgICAgICBnZ3Bsb3RseShnNCkpCmBgYAoKCkxvb2sgYXQgdGhlIGNvbG91ciBhbmQgc2hhcGUgbGVnZW5kIGJlaW5nIHJlcGVhdGVkLiBUaGlzIGlzIHRoZSBtYWluIHJlYXNvbiB0aGF0IEkgZG9uJ3QgcHJlZmVyIGBnZ3Bsb3RseWAgZm9yIHRoaXMgdHlwZSBvZiB0YXNrLiBCdXQgZG9uJ3QgZ2V0IG1lIHdyb25nISBJIHN0aWxsIHVzZSBgZ2dwbG90bHlgIGluIG15IGV2ZXJ5ZGF5IHdvcmssIGJ1dCBqdXN0IG5vdCB0aGlzIHRhc2suCgoKIyBTZXNzaW9uIGluZm8KYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgo=