This R Notebook is the complement to my blog post How to Create an Interactive WebGL Network Graph Using R.

This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)

1 Setup

Setup the R packages.

# must install ggnetwork using from source to avoid ggplot2 2.2.0 issue
# install.packages("ggnetwork", type="source")
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(nycflights13)
library(igraph)

Attaching package: ‘igraph’

The following objects are masked from ‘package:dplyr’:

    %>%, as_data_frame, groups, union

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
library(intergraph)
library(sna)
Loading required package: statnet.common
Loading required package: network
network: Classes for Relational Data
Version 1.13.0 created on 2015-08-31.
copyright (c) 2005, Carter T. Butts, University of California-Irvine
                    Mark S. Handcock, University of California -- Los Angeles
                    David R. Hunter, Penn State University
                    Martina Morris, University of Washington
                    Skye Bender-deMoll, University of Washington
 For citation information, type citation("network").
 Type help("network-package") to get started.


Attaching package: ‘network’

The following objects are masked from ‘package:igraph’:

    %c%, %s%, add.edges, add.vertices, delete.edges, delete.vertices,
    get.edge.attribute, get.edges, get.vertex.attribute,
    is.bipartite, is.directed, list.edge.attributes,
    list.vertex.attributes, set.edge.attribute, set.vertex.attribute

sna: Tools for Social Network Analysis
Version 2.4 created on 2016-07-23.
copyright (c) 2005, Carter T. Butts, University of California-Irvine
 For citation information, type citation("sna").
 Type help(package="sna") to get started.


Attaching package: ‘sna’

The following objects are masked from ‘package:igraph’:

    betweenness, bonpow, closeness, components, degree, dyad.census,
    evcent, hierarchy, is.connected, neighborhood, triad.census
library(ggplot2)
library(ggnetwork)
library(plotly)

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following objects are masked from ‘package:igraph’:

    %>%, groups

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(htmlwidgets)
sessionInfo()
R version 3.3.2 (2016-10-31)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: macOS Sierra 10.12.2

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] htmlwidgets_0.8      plotly_4.5.6         ggnetwork_0.5.1     
 [4] ggplot2_2.2.0        sna_2.4              network_1.13.0      
 [7] statnet.common_3.3.0 intergraph_2.0-2     igraph_1.0.1        
[10] nycflights13_0.2.0   dplyr_0.5.0         

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.8       knitr_1.15.1      magrittr_1.5      munsell_0.4.3    
 [5] viridisLite_0.1.3 colorspace_1.3-2  R6_2.2.0          httr_1.2.1       
 [9] plyr_1.8.4        stringr_1.1.0     tools_3.3.2       grid_3.3.2       
[13] gtable_0.2.0      DBI_0.5-1         htmltools_0.3.5   lazyeval_0.2.0   
[17] assertthat_0.1    rprojroot_1.1     digest_0.6.10     tibble_1.2       
[21] tidyr_0.6.0       purrr_0.2.2       base64enc_0.1-3   ggrepel_0.6.5    
[25] evaluate_0.10     rmarkdown_1.3     stringi_1.1.2     scales_0.4.1     
[29] backports_1.0.4   jsonlite_1.1     

The nycflights13 package contains a flights dataset.

flights %>% head()

There are 336,776 flights in the dataset.

2 Build the Network

Getting the edge weights is a dplyr aggregation.

df_edges <- flights %>% group_by(origin, dest) %>% summarize(weight = n())
df_edges %>% arrange(desc(weight)) %>% head()

There are 224 total edges.

Add a colors column to edge for each origin which will eventually be used for final ggplot.

# blue, red, green
colors = c("#3498db", "#e74c3c", "#2ecc71")
# seting alphabetical order; allows for predictable ordering later
origins = c("EWR", "JFK", "LGA")
df_colors = tbl_df(data.frame(origin=origins, color=origins))
df_edges <- df_edges %>% left_join(df_colors)
Joining, by = "origin"
joining factor and character vector, coercing into character vector
df_edges %>% arrange(desc(weight)) %>% head()
net <- graph.data.frame(df_edges, directed = T)
net
IGRAPH DNW- 107 224 -- 
+ attr: name (v/c), weight (e/n), color (e/c)
+ edges (vertex names):
 [1] EWR->ALB EWR->ANC EWR->ATL EWR->AUS EWR->AVL EWR->BDL EWR->BNA EWR->BOS
 [9] EWR->BQN EWR->BTV EWR->BUF EWR->BWI EWR->BZN EWR->CAE EWR->CHS EWR->CLE
[17] EWR->CLT EWR->CMH EWR->CVG EWR->DAY EWR->DCA EWR->DEN EWR->DFW EWR->DSM
[25] EWR->DTW EWR->EGE EWR->FLL EWR->GRR EWR->GSO EWR->GSP EWR->HDN EWR->HNL
[33] EWR->HOU EWR->IAD EWR->IAH EWR->IND EWR->JAC EWR->JAX EWR->LAS EWR->LAX
[41] EWR->LGA EWR->MCI EWR->MCO EWR->MDW EWR->MEM EWR->MHT EWR->MIA EWR->MKE
[49] EWR->MSN EWR->MSP EWR->MSY EWR->MTJ EWR->MYR EWR->OKC EWR->OMA EWR->ORD
[57] EWR->ORF EWR->PBI EWR->PDX EWR->PHL EWR->PHX EWR->PIT EWR->PVD EWR->PWM
+ ... omitted several edges
V(net)$degree <- centralization.degree(net)$res
V(net)$weighted_degree <- graph.strength(net)
V(net)$color_v <- c(origins, rep("Others", gorder(net) - length(colors)))

Write specialized hovertext for each vertex. Note that airport attributes must be mapped to same order as vertices.

df_airports <- data.frame(vname=V(net)$name) %>% left_join(airports, by=c("vname" = "faa"))
joining character vector and factor, coercing into character vector
V(net)$text <- paste(V(net)$name,
                       df_airports$name,
                       paste(format(V(net)$weighted_degree, big.mark=",", trim=T), "Flights"),
                        sep = "<br>")
V(net)$text %>% head()
[1] "EWR<br>Newark Liberty Intl<br>120,835 Flights"           
[2] "JFK<br>John F Kennedy Intl<br>111,279 Flights"           
[3] "LGA<br>La Guardia<br>104,663 Flights"                    
[4] "ALB<br>Albany Intl<br>439 Flights"                       
[5] "ANC<br>Ted Stevens Anchorage Intl<br>8 Flights"          
[6] "ATL<br>Hartsfield Jackson Atlanta Intl<br>17,215 Flights"

Add latitudes/longitudes to both vertices and edges for spatial map;

V(net)$lat <- df_airports$lat
V(net)$lon <- df_airports$lon
# gives to/from locations; map to corresponding ending lat/long
end_loc <- data.frame(ename=get.edgelist(net)[,2]) %>% left_join(airports, by=c("ename" = "faa"))
joining character vector and factor, coercing into character vector
E(net)$endlat <- end_loc$lat
E(net)$endlon <- end_loc$lon

3 Plotting the Network Graph

Use ggnetwork to transform the network to a ggplot friendly format.

# ggnetwork sets default nodes randomly; set seed for reproducibility
set.seed(123)
df_net <- ggnetwork(net, layout = "fruchtermanreingold", weights="weight", niter=50000, arrow.gap=0)
df_net %>% head()
plot <- ggplot(df_net, aes(x = x, y = y, xend = xend, yend = yend)) +
    geom_edges(aes(color = color), size=0.4, alpha=0.25) +
    geom_nodes(aes(color = color_v, size = degree, text=text)) +
    ggtitle("Network Graph of U.S. Flights Outbound from NYC in 2013") +
    scale_color_manual(labels=c("EWR", "JFK", "LGA", "Others"), 
                         values=c(colors, "#1a1a1a"), name="Airports") +
    guides(size=FALSE) +
    theme_blank() +
    theme(plot.title = element_text(family="Source Sans Pro"),
            legend.title = element_text(family="Source Sans Pro"),
            legend.text = element_text(family="Source Sans Pro"))
Ignoring unknown aesthetics: text
plot

plot %>% ggplotly(tooltip="text") %>% toWebGL()

Save interactive plot locally to disk using htmlwidgets for uploading to my website. (this only saves the data/layout; you will need to provide the relevant plot.ly javascript on your own website)

plot %>% ggplotly(tooltip="text", height=400) %>%
    toWebGL() %>% 
    saveWidget("ggplot-graph-1.html", selfcontained=F, libdir="plotly")

I make a few manual changes to the output for the output used on the website:

  • Only extract the script and the HTML container. The static files are loaded separately.
  • Set the browser padding CSS to 0.

3.1 Plot by Physical Location

plot <- ggplot(df_net, aes(x = lon, y = lat, xend = endlon, yend = endlat)) +
    geom_edges(aes(color = color), size=0.4, alpha=0.25) +
    geom_nodes(aes(color = color_v, size = degree, text=text)) +
    ggtitle("Locations of U.S. Flights Outbound from NYC in 2013") +
    scale_color_manual(labels=c("EWR", "JFK", "LGA", "Others"), 
                         values=c(colors, "#1a1a1a"), name="Airports") +
    guides(size=FALSE) +
    theme_blank() +
    theme(plot.title = element_text(family="Source Sans Pro"),
            legend.title = element_text(family="Source Sans Pro"),
            legend.text = element_text(family="Source Sans Pro"))
Ignoring unknown aesthetics: text
plot

plot %>% ggplotly(tooltip="text") %>% toWebGL()
plot %>% ggplotly(tooltip="text", height=400) %>%
    toWebGL() %>%
    saveWidget("ggplot-graph-2.html", selfcontained=F, libdir="plotly")

4 Simplified Version

Minimum amount of code needed to demonstate proof-of-concept for article.

df_edges <- flights %>% group_by(origin, dest) %>% summarize(weight = n())
net <- graph.data.frame(df_edges, directed = T)
V(net)$degree <- centralization.degree(net)$res
df_net <- ggnetwork(net, layout = "fruchtermanreingold", weights="weight", niter=5000)
plot <- ggplot(df_net, aes(x = x, y = y, xend = xend, yend = yend)) +
    geom_edges(size=0.4, alpha=0.25) +
    geom_nodes(aes(size = degree, text=vertex.names)) +
    ggtitle("Network Graph of U.S. Flights Outbound from NYC in 2013") +
    theme_blank()
Ignoring unknown aesthetics: text
plot

plot %>% ggplotly(tooltip="text") %>% toWebGL()

plot %>% ggplotly(tooltip="text", height=400) %>%
    toWebGL() %>%
    saveWidget("ggplot-graph-3.html", selfcontained=F, libdir="plotly")

5 LICENSE

The MIT License (MIT)

Copyright (c) 2016 Max Woolf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

LS0tCnRpdGxlOiAiSG93IHRvIENyZWF0ZSBhbiBJbnRlcmFjdGl2ZSBXZWJHTCBOZXR3b3JrIEdyYXBoIFVzaW5nIFIgYW5kIFBsb3RseSIKYXV0aG9yOiAiTWF4IFdvb2xmIChAbWluaW1heGlyKSIKZGF0ZTogIkRlY2VtYmVyIDV0aCwgMjAxNiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBtYXRoamF4OiBudWxsCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpUaGlzIFIgTm90ZWJvb2sgaXMgdGhlIGNvbXBsZW1lbnQgdG8gbXkgYmxvZyBwb3N0IFtIb3cgdG8gQ3JlYXRlIGFuIEludGVyYWN0aXZlIFdlYkdMIE5ldHdvcmsgR3JhcGggVXNpbmcgUl0oaHR0cDovL21pbmltYXhpci5jb20vMjAxNi8xMi9pbnRlcmFjdGl2ZS1uZXR3b3JrLykuCgpUaGlzIG5vdGVib29rIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gSWYgeW91IHVzZSB0aGUgY29kZSBvciBkYXRhIHZpc3VhbGl6YXRpb24gZGVzaWducyBjb250YWluZWQgd2l0aGluIHRoaXMgbm90ZWJvb2ssIGl0IHdvdWxkIGJlIGdyZWF0bHkgYXBwcmVjaWF0ZWQgaWYgcHJvcGVyIGF0dHJpYnV0aW9uIGlzIGdpdmVuIGJhY2sgdG8gdGhpcyBub3RlYm9vayBhbmQvb3IgbXlzZWxmLiBUaGFua3MhIDopCgojIFNldHVwCgpTZXR1cCB0aGUgUiBwYWNrYWdlcy4KCmBgYHtyfQoKIyBtdXN0IGluc3RhbGwgZ2duZXR3b3JrIHVzaW5nIGZyb20gc291cmNlIHRvIGF2b2lkIGdncGxvdDIgMi4yLjAgaXNzdWUKIyBpbnN0YWxsLnBhY2thZ2VzKCJnZ25ldHdvcmsiLCB0eXBlPSJzb3VyY2UiKQoKbGlicmFyeShkcGx5cikKbGlicmFyeShueWNmbGlnaHRzMTMpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGludGVyZ3JhcGgpCmxpYnJhcnkoc25hKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2duZXR3b3JrKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShodG1sd2lkZ2V0cykKCnNlc3Npb25JbmZvKCkKYGBgCgpUaGUgYG55Y2ZsaWdodHMxM2AgcGFja2FnZSBjb250YWlucyBhIGBmbGlnaHRzYCBkYXRhc2V0LgoKYGBge3J9CmZsaWdodHMgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGZsaWdodHMgJT4lIG5yb3coKSAlPiUgZm9ybWF0KGJpZy5tYXJrPSIsIilgKiogZmxpZ2h0cyBpbiB0aGUgZGF0YXNldC4KCiMgQnVpbGQgdGhlIE5ldHdvcmsKCkdldHRpbmcgdGhlIGVkZ2Ugd2VpZ2h0cyBpcyBhIGBkcGx5cmAgYWdncmVnYXRpb24uCgpgYGB7cn0KZGZfZWRnZXMgPC0gZmxpZ2h0cyAlPiUgZ3JvdXBfYnkob3JpZ2luLCBkZXN0KSAlPiUgc3VtbWFyaXplKHdlaWdodCA9IG4oKSkKZGZfZWRnZXMgJT4lIGFycmFuZ2UoZGVzYyh3ZWlnaHQpKSAlPiUgaGVhZCgpCmBgYAoKVGhlcmUgYXJlICoqYHIgZGZfZWRnZXMgJT4lIG5yb3coKSAlPiUgZm9ybWF0KGJpZy5tYXJrPSIsIilgKiogdG90YWwgZWRnZXMuCgpBZGQgYSBjb2xvcnMgY29sdW1uIHRvIGVkZ2UgZm9yIGVhY2ggYG9yaWdpbmAgd2hpY2ggd2lsbCBldmVudHVhbGx5IGJlIHVzZWQgZm9yIGZpbmFsIGdncGxvdC4KCmBgYHtyfQojIGJsdWUsIHJlZCwgZ3JlZW4KY29sb3JzID0gYygiIzM0OThkYiIsICIjZTc0YzNjIiwgIiMyZWNjNzEiKQoKIyBzZXRpbmcgYWxwaGFiZXRpY2FsIG9yZGVyOyBhbGxvd3MgZm9yIHByZWRpY3RhYmxlIG9yZGVyaW5nIGxhdGVyCm9yaWdpbnMgPSBjKCJFV1IiLCAiSkZLIiwgIkxHQSIpCgpkZl9jb2xvcnMgPSB0YmxfZGYoZGF0YS5mcmFtZShvcmlnaW49b3JpZ2lucywgY29sb3I9b3JpZ2lucykpCmRmX2VkZ2VzIDwtIGRmX2VkZ2VzICU+JSBsZWZ0X2pvaW4oZGZfY29sb3JzKQoKZGZfZWRnZXMgJT4lIGFycmFuZ2UoZGVzYyh3ZWlnaHQpKSAlPiUgaGVhZCgpCmBgYAoKCmBgYHtyfQpuZXQgPC0gZ3JhcGguZGF0YS5mcmFtZShkZl9lZGdlcywgZGlyZWN0ZWQgPSBUKQpuZXQKClYobmV0KSRkZWdyZWUgPC0gY2VudHJhbGl6YXRpb24uZGVncmVlKG5ldCkkcmVzClYobmV0KSR3ZWlnaHRlZF9kZWdyZWUgPC0gZ3JhcGguc3RyZW5ndGgobmV0KQpWKG5ldCkkY29sb3JfdiA8LSBjKG9yaWdpbnMsIHJlcCgiT3RoZXJzIiwgZ29yZGVyKG5ldCkgLSBsZW5ndGgoY29sb3JzKSkpCmBgYAoKV3JpdGUgc3BlY2lhbGl6ZWQgaG92ZXJ0ZXh0IGZvciBlYWNoIHZlcnRleC4gTm90ZSB0aGF0IGFpcnBvcnQgYXR0cmlidXRlcyBtdXN0IGJlIG1hcHBlZCB0byBzYW1lIG9yZGVyIGFzIHZlcnRpY2VzLgoKCmBgYHtyfQoKZGZfYWlycG9ydHMgPC0gZGF0YS5mcmFtZSh2bmFtZT1WKG5ldCkkbmFtZSkgJT4lIGxlZnRfam9pbihhaXJwb3J0cywgYnk9Yygidm5hbWUiID0gImZhYSIpKQoKVihuZXQpJHRleHQgPC0gcGFzdGUoVihuZXQpJG5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgZGZfYWlycG9ydHMkbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShmb3JtYXQoVihuZXQpJHdlaWdodGVkX2RlZ3JlZSwgYmlnLm1hcms9IiwiLCB0cmltPVQpLCAiRmxpZ2h0cyIpLAogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiPGJyPiIpCgpWKG5ldCkkdGV4dCAlPiUgaGVhZCgpCmBgYAoKQWRkIGxhdGl0dWRlcy9sb25naXR1ZGVzIHRvIGJvdGggdmVydGljZXMgYW5kIGVkZ2VzIGZvciBzcGF0aWFsIG1hcDsKCmBgYHtyfQpWKG5ldCkkbGF0IDwtIGRmX2FpcnBvcnRzJGxhdApWKG5ldCkkbG9uIDwtIGRmX2FpcnBvcnRzJGxvbgoKIyBnaXZlcyB0by9mcm9tIGxvY2F0aW9uczsgbWFwIHRvIGNvcnJlc3BvbmRpbmcgZW5kaW5nIGxhdC9sb25nCmVuZF9sb2MgPC0gZGF0YS5mcmFtZShlbmFtZT1nZXQuZWRnZWxpc3QobmV0KVssMl0pICU+JSBsZWZ0X2pvaW4oYWlycG9ydHMsIGJ5PWMoImVuYW1lIiA9ICJmYWEiKSkKCkUobmV0KSRlbmRsYXQgPC0gZW5kX2xvYyRsYXQKRShuZXQpJGVuZGxvbiA8LSBlbmRfbG9jJGxvbgpgYGAKCiMgUGxvdHRpbmcgdGhlIE5ldHdvcmsgR3JhcGgKClVzZSBgZ2duZXR3b3JrYCB0byB0cmFuc2Zvcm0gdGhlIG5ldHdvcmsgdG8gYSBgZ2dwbG90YCBmcmllbmRseSBmb3JtYXQuCgpgYGB7cn0KIyBnZ25ldHdvcmsgc2V0cyBkZWZhdWx0IG5vZGVzIHJhbmRvbWx5OyBzZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDEyMykKCmRmX25ldCA8LSBnZ25ldHdvcmsobmV0LCBsYXlvdXQgPSAiZnJ1Y2h0ZXJtYW5yZWluZ29sZCIsIHdlaWdodHM9IndlaWdodCIsIG5pdGVyPTUwMDAwLCBhcnJvdy5nYXA9MCkKZGZfbmV0ICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfbmV0LCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geGVuZCwgeWVuZCA9IHllbmQpKSArCiAgICBnZW9tX2VkZ2VzKGFlcyhjb2xvciA9IGNvbG9yKSwgc2l6ZT0wLjQsIGFscGhhPTAuMjUpICsKICAgIGdlb21fbm9kZXMoYWVzKGNvbG9yID0gY29sb3Jfdiwgc2l6ZSA9IGRlZ3JlZSwgdGV4dD10ZXh0KSkgKwogICAgZ2d0aXRsZSgiTmV0d29yayBHcmFwaCBvZiBVLlMuIEZsaWdodHMgT3V0Ym91bmQgZnJvbSBOWUMgaW4gMjAxMyIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHM9YygiRVdSIiwgIkpGSyIsICJMR0EiLCAiT3RoZXJzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzPWMoY29sb3JzLCAiIzFhMWExYSIpLCBuYW1lPSJBaXJwb3J0cyIpICsKICAgIGd1aWRlcyhzaXplPUZBTFNFKSArCiAgICB0aGVtZV9ibGFuaygpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8iKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseT0iU291cmNlIFNhbnMgUHJvIiksCiAgICAgICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseT0iU291cmNlIFNhbnMgUHJvIikpCgpwbG90CmBgYAoKYGBge3J9CnBsb3QgJT4lIGdncGxvdGx5KHRvb2x0aXA9InRleHQiKSAlPiUgdG9XZWJHTCgpCmBgYAoKU2F2ZSBpbnRlcmFjdGl2ZSBwbG90IGxvY2FsbHkgdG8gZGlzayB1c2luZyBgaHRtbHdpZGdldHNgIGZvciB1cGxvYWRpbmcgdG8gbXkgd2Vic2l0ZS4gKHRoaXMgb25seSBzYXZlcyB0aGUgZGF0YS9sYXlvdXQ7IHlvdSB3aWxsIG5lZWQgdG8gcHJvdmlkZSB0aGUgcmVsZXZhbnQgcGxvdC5seSBqYXZhc2NyaXB0IG9uIHlvdXIgb3duIHdlYnNpdGUpCgpgYGB7cn0KcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIsIGhlaWdodD00MDApICU+JQogICAgdG9XZWJHTCgpICU+JSAKICAgIHNhdmVXaWRnZXQoImdncGxvdC1ncmFwaC0xLmh0bWwiLCBzZWxmY29udGFpbmVkPUYsIGxpYmRpcj0icGxvdGx5IikKYGBgCgpJIG1ha2UgYSBmZXcgbWFudWFsIGNoYW5nZXMgdG8gdGhlIG91dHB1dCBmb3IgdGhlIG91dHB1dCB1c2VkIG9uIHRoZSB3ZWJzaXRlOgoKKiBPbmx5IGV4dHJhY3QgdGhlIHNjcmlwdCBhbmQgdGhlIEhUTUwgY29udGFpbmVyLiBUaGUgc3RhdGljIGZpbGVzIGFyZSBsb2FkZWQgc2VwYXJhdGVseS4KKiBTZXQgdGhlIGJyb3dzZXIgcGFkZGluZyBDU1MgdG8gMC4KCiMjIFBsb3QgYnkgUGh5c2ljYWwgTG9jYXRpb24KCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9uZXQsIGFlcyh4ID0gbG9uLCB5ID0gbGF0LCB4ZW5kID0gZW5kbG9uLCB5ZW5kID0gZW5kbGF0KSkgKwogICAgZ2VvbV9lZGdlcyhhZXMoY29sb3IgPSBjb2xvciksIHNpemU9MC40LCBhbHBoYT0wLjI1KSArCiAgICBnZW9tX25vZGVzKGFlcyhjb2xvciA9IGNvbG9yX3YsIHNpemUgPSBkZWdyZWUsIHRleHQ9dGV4dCkpICsKICAgIGdndGl0bGUoIkxvY2F0aW9ucyBvZiBVLlMuIEZsaWdodHMgT3V0Ym91bmQgZnJvbSBOWUMgaW4gMjAxMyIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHM9YygiRVdSIiwgIkpGSyIsICJMR0EiLCAiT3RoZXJzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzPWMoY29sb3JzLCAiIzFhMWExYSIpLCBuYW1lPSJBaXJwb3J0cyIpICsKICAgIGd1aWRlcyhzaXplPUZBTFNFKSArCiAgICB0aGVtZV9ibGFuaygpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8iKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseT0iU291cmNlIFNhbnMgUHJvIiksCiAgICAgICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseT0iU291cmNlIFNhbnMgUHJvIikpCgpwbG90CmBgYAoKYGBge3J9CnBsb3QgJT4lIGdncGxvdGx5KHRvb2x0aXA9InRleHQiKSAlPiUgdG9XZWJHTCgpCmBgYAoKYGBge3J9CnBsb3QgJT4lIGdncGxvdGx5KHRvb2x0aXA9InRleHQiLCBoZWlnaHQ9NDAwKSAlPiUKICAgIHRvV2ViR0woKSAlPiUKICAgIHNhdmVXaWRnZXQoImdncGxvdC1ncmFwaC0yLmh0bWwiLCBzZWxmY29udGFpbmVkPUYsIGxpYmRpcj0icGxvdGx5IikKYGBgCgojIFNpbXBsaWZpZWQgVmVyc2lvbgoKTWluaW11bSBhbW91bnQgb2YgY29kZSBuZWVkZWQgdG8gZGVtb25zdGF0ZSBwcm9vZi1vZi1jb25jZXB0IGZvciBhcnRpY2xlLgoKYGBge3J9CmRmX2VkZ2VzIDwtIGZsaWdodHMgJT4lIGdyb3VwX2J5KG9yaWdpbiwgZGVzdCkgJT4lIHN1bW1hcml6ZSh3ZWlnaHQgPSBuKCkpCm5ldCA8LSBncmFwaC5kYXRhLmZyYW1lKGRmX2VkZ2VzLCBkaXJlY3RlZCA9IFQpClYobmV0KSRkZWdyZWUgPC0gY2VudHJhbGl6YXRpb24uZGVncmVlKG5ldCkkcmVzCmRmX25ldCA8LSBnZ25ldHdvcmsobmV0LCBsYXlvdXQgPSAiZnJ1Y2h0ZXJtYW5yZWluZ29sZCIsIHdlaWdodHM9IndlaWdodCIsIG5pdGVyPTUwMDApCgpwbG90IDwtIGdncGxvdChkZl9uZXQsIGFlcyh4ID0geCwgeSA9IHksIHhlbmQgPSB4ZW5kLCB5ZW5kID0geWVuZCkpICsKICAgIGdlb21fZWRnZXMoc2l6ZT0wLjQsIGFscGhhPTAuMjUpICsKICAgIGdlb21fbm9kZXMoYWVzKHNpemUgPSBkZWdyZWUsIHRleHQ9dmVydGV4Lm5hbWVzKSkgKwogICAgZ2d0aXRsZSgiTmV0d29yayBHcmFwaCBvZiBVLlMuIEZsaWdodHMgT3V0Ym91bmQgZnJvbSBOWUMgaW4gMjAxMyIpICsKICAgIHRoZW1lX2JsYW5rKCkKCnBsb3QKYGBgCgpgYGB7cn0KcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIpICU+JSB0b1dlYkdMKCkKcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIsIGhlaWdodD00MDApICU+JQogICAgdG9XZWJHTCgpICU+JQogICAgc2F2ZVdpZGdldCgiZ2dwbG90LWdyYXBoLTMuaHRtbCIsIHNlbGZjb250YWluZWQ9RiwgbGliZGlyPSJwbG90bHkiKQpgYGAKCgojIExJQ0VOU0UKClRoZSBNSVQgTGljZW5zZSAoTUlUKQoKQ29weXJpZ2h0IChjKSAyMDE2IE1heCBXb29sZgoKUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSAiU29mdHdhcmUiKSwgdG8gZGVhbCBpbiB0aGUgU29mdHdhcmUgd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCwgZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwgYW5kL29yIHNlbGwgY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzIGZ1cm5pc2hlZCB0byBkbyBzbywgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6CgpUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZCBpbiBhbGwgY29waWVzIG9yIHN1YnN0YW50aWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFWFBSRVNTIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SUyBPUiBDT1BZUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLCBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOIFRIRSBTT0ZUV0FSRS4=