This script recreates a network diagram showing the genealogy of
those involved in the development of Exploratory
Data Analysis following John W. Tukey,
Frederick Mosteller, … and others, together with some of their students.
It appears in my article, Remembrances
of Things EDA to be published on Nightingale shortly.
I’m creating this gist to allow other to work with the data and perhaps
produce something better.
The source information used here was the Mathematics Geneaology
Project. I selected only a handful of first-level students and some
of their descendants. The base dataset I’m using here includes:
advisor, student, institution,
PhDyear, and MGD_id, the ID number of the
student in the geneaology data base.
Creating such a diagram is problematic for a variety of reasons:
- It follows advisor—student relations only a few
steps removed from the principal participants. Not everyone’s students
are shown.
- The connections among all those involved in this history are only
incompletely conveyed in a network diagram.
- There are cross-connections of influence, not indicated by direct
mentorship.
- The main package,
ggraph, I used for the diagram at the
end gave messy results, and so I had to prune some branches to make it
readable. But more generally, it reflects the limitations of a
static graph of fixed resolution for
an article.
Let’s get started. This is published at: https://rpubs.com/friendly/EDA-network
Load packages
library(openxlsx) # Read excel files; this supports using a URL
library(dplyr) # A Grammar of Data Manipulation
library(ggraph) # An Implementation of Grammar of Graphics for Graphs and Networks
library(igraph) # Network Analysis and Visualization
library(ggplot2) # Create Elegant Data Visualisations Using the Grammar of Graphics
library(grid) # The Grid Graphics Package
library(rlang) # Functions for Base Types and Core R and 'Tidyverse' Features
library(glue) # Interpreted String Literals
library(here)
library(networkD3)
Read the data set
To make this script reproducible, read from a cloud URL. It seeme
that only {openxlsx} supports this.
EDA_geneaology <- read.xlsx("https://www.dropbox.com/s/oq3jwvg8bto93ln/EDA-geneaology.xlsx?dl=1")
str(EDA_geneaology)
## 'data.frame': 40 obs. of 5 variables:
## $ advisor : chr "Solomon Lefschetz" "John Tukey" "John Tukey" "John Tukey" ...
## $ institution: chr "Princeton" "Princeton" "Princeton" "Princeton" ...
## $ student : chr "John Tukey" "Arthur Dempster" "Leo Goodman" "David Hoaglin" ...
## $ PhDyear : num 1939 1956 1950 1971 1946 ...
## $ MGD_id : num 15860 15981 35023 35266 35033 ...
Fixup a few things
igraph wants parent and child
as the edge list variables. Make “Institution” appear in the legend, not
“institution”.
EDA_gen <- EDA_geneaology %>%
rename(parent = advisor,
child = student,
Institution = institution)
Clean up some links not to be shown. Add an explicit
link from MGD_id to genealogy data base.
EDA_gen <- EDA_gen %>%
mutate(main = (child %in% c("John Tukey", "Harold Gulliksen")) ) %>%
filter( !(parent %in% c("Solomon Lefschetz", "James Angell")) ) %>%
filter( !(child %in% c("Clyde Coombs", "Charles Lewis"))) %>%
mutate(link = glue::glue("https://www.genealogy.math.ndsu.nodak.edu/id.php?id={MGD_id}"))
Examine the igraph object
See: https://kateto.net/wp-content/uploads/2018/06/Polnet%202018%20R%20Network%20Visualization%20Workshop.pdf
for working with igraph
Extract edges as a data frame
edges <- igraph::as_data_frame(EDA_graph, what="edges")
glimpse(edges)
## Rows: 35
## Columns: 7
## $ from <chr> "John Tukey", "John Tukey", "John Tukey", "John Tukey", "S~
## $ to <chr> "Arthur Dempster", "Leo Goodman", "David Hoaglin", "Freder~
## $ Institution <chr> "Princeton", "Princeton", "Princeton", "Princeton", "Princ~
## $ PhDyear <dbl> 1956, 1950, 1971, 1946, 1946, 1974, 1968, 1977, 1975, 1953~
## $ MGD_id <dbl> 15981, 35023, 35266, 35033, 35033, 18747, 58815, 13739, 23~
## $ main <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FA~
## $ link <chr> "https://www.genealogy.math.ndsu.nodak.edu/id.php?id=15981~
Get graph vertices: V() returns vertices;
E() returns edges.
igraph::V(EDA_graph)
## + 37/37 vertices, named, from 6f9cbf9:
## [1] John Tukey Samuel Wilks Frederick Mosteller
## [4] Robert Abelson Harold Gulliksen Arthur Dempster
## [7] John Hartigan Louis Leon Thurstone Leo Goodman
## [10] Ledyard Tucker Andreas Buja Dianne Cook
## [13] Peter Huber Gordon Foster Antony Unwin
## [16] Heike Hofmann David Hoaglin Persi Diaconis
## [19] Stephen Fienberg Stanley Wasserman Lee Wilkinson
## [22] Michael Friendly Howard Wainer Paul Velleman
## [25] Richard Heiberger Karen Kafadar Jay Emerson
## [28] Sanford Weisberg James Ramsay William Eddy
## + ... omitted several vertices
Get node degrees. I use this to make Tukey/Mosteller most prominent,
but show all in relation to the number of links.
deg <- igraph::degree(EDA_graph, mode="all")
V(EDA_graph)$degree <- deg
Plotting with ggraph
The ggraph
package was proposed as a “Grammar of Graphics for Graphs and Networks”.
Yet, I’m finding it somewhat incomplete (had to add $degree
to the vertices using igraph::V()) and hard to totally
understand.
I’m using filled arrow in the edges, but these did not appear in the
legend. From this
stackoverflow post I found how to modify GeomEdgePath()
to create a custom key function.
draw_key_custom = function(data, params, size) {
segmentsGrob(0.1, 0.5, 0.9, 0.5,
gp = gpar(
col = alpha(data$edge_colour, data$edge_alpha),
fill = alpha(data$edge_colour, data$edge_alpha), # <- add fill to arrow head!
lwd = data$edge_width * .pt,
lty = data$edge_linetype,
lineend = 'butt'
),
arrow = params$arrow
)
}
Choice of layouts
There is a large variety of graph layouts that could be used here.
Details and examples at: https://www.data-imaginist.com/2017/ggraph-introduction-layouts/
See: https://i.stack.imgur.com/3QAMW.png for pictorial
examples. After some experimentation, I chose fr, the
Fruchter-Rheinhold force directed layout.
igraph_layouts <- c('star', 'circle', 'gem', 'dh', 'graphopt', 'grid', 'mds',
'randomly', 'fr', 'kk', 'drl', 'lgl')
Draw the diagram
This uses size = degree to set the vertex size. Edge
links are colored by Institution. The legend
key_glyph overrides the ggraph default, using
my draw_key_custom() function to fill the arrow heads.
ggraph(EDA_graph, layout="fr") +
geom_edge_link(aes(color=Institution),
arrow = grid::arrow(type = "closed",
angle=15,
length = unit(0.15, "inches")),
key_glyph = "custom" ) + # <- I'm new!
geom_node_point(aes(size=degree),
color = scales::alpha("black", .4),
show.legend = FALSE) +
geom_node_text(aes(label = name), repel = TRUE) +
ggtitle("Specimen of a Chart of Geneaology of EDA") +
theme_graph() +
theme(legend.position = 'bottom')

Going further
One nice thing to do would be to make this diagram interactive, with
tool tips, so that hovering over a node would show the details of an
individual.
Here’s what I tried, making a text variable that could
be used as a tool tip.
add tooltip text to the dataset
EDA_gen2 <- EDA_gen %>%
mutate(text = glue(
"name: {child}<br>
{Institution}<br>
PhD year: {PhDyear}<br>
MGD_id: <a href='{link}'>{MGD_id}</a>"
))
add the text to the igraph representation
EDA_graph2 <- igraph::graph_from_data_frame(EDA_gen2[,c(1,3,2,4:8)])
Same plot as before
p2 <-
ggraph(EDA_graph, layout="fr") +
geom_edge_link(aes(color=Institution),
arrow = grid::arrow(type = "closed",
angle=15,
length = unit(0.15, "inches")),
key_glyph = "custom" ) + # <- I'm new!
geom_node_point() +
geom_node_text(aes(label = name), repel = TRUE) +
ggtitle("Specimen of a Chart of Geneaology of EDA") +
theme_graph() +
theme(legend.position = 'bottom')
Ugh!
I thought I could just use plotly::ggplotly() here.
However, most geoms used by ggraph here are not yet
implemented in plotly. Many warnings. All I get is the
points.
library(plotly)
ggplotly(p2, tooltip="text")
What I’d like
Here is one result I’d like to achieve, shown in a mock-up, but with
the tooltip box displaced for readability.

Interactive JS visualization with networkD3 ?
One alternative, using the networkD3 package was
suggested by Udi Alter. It solves the problem of resolution in the graph
via zoom and pan: drag/click to focus on a node, mouse wheel to zoom
in/out.
It is only the beginning: missing Institution as grouping variable to
color the nodes, hover options, etc.
simpleNetwork(edges, height="100px", width="100px", zoom = TRUE)
IycgLS0tDQojJyB0aXRsZTogTmV0d29yayBkaWFncmFtIG9mIEVEQSBnZW5lYWxvZ3kNCiMnIGF1dGhvcjogTWljaGFlbCBGcmllbmRseQ0KIycgZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpKWAiDQojJyBvdXRwdXQ6DQojJyAgIGh0bWxfZG9jdW1lbnQ6DQojJyAgICAgdGhlbWU6IHJlYWRhYmxlDQojJyAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KIycgLS0tDQoNCiMrIHNldHVwLCBpbmNsdWRlPUZBTFNFDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIG1lc3NhZ2UgPSBGQUxTRSwNCiAgd2FybmluZyA9IEZBTFNFDQoNCikNCg0KIycgVGhpcyBzY3JpcHQgcmVjcmVhdGVzIGEgbmV0d29yayBkaWFncmFtIHNob3dpbmcgdGhlIGdlbmVhbG9neSBvZiB0aG9zZSBpbnZvbHZlZCBpbiB0aGUgZGV2ZWxvcG1lbnQNCiMnIG9mICoqRSoqeHBsb3JhdG9yeSAqKkQqKmF0YSAqKkEqKm5hbHlzaXMgZm9sbG93aW5nIEpvaG4gVy4gVHVrZXksIEZyZWRlcmljayBNb3N0ZWxsZXIsIC4uLiBhbmQgb3RoZXJzLCB0b2dldGhlciB3aXRoDQojJyBzb21lIG9mIHRoZWlyIHN0dWRlbnRzLiBJdCBhcHBlYXJzIGluDQojJyBteSBhcnRpY2xlLCBbX1JlbWVtYnJhbmNlcyBvZiBUaGluZ3MgRURBX10oaHR0cHM6Ly93d3cucmVzZWFyY2hnYXRlLm5ldC9wdWJsaWNhdGlvbi8zNjExOTEzMzVfUmVtZW1iZXJhbmNlc19vZl9UaGluZ3NfRURBKQ0KIycgdG8gYmUgcHVibGlzaGVkIG9uIF9OaWdodGluZ2FsZV8gc2hvcnRseS4gSSdtIGNyZWF0aW5nIHRoaXMgZ2lzdCB0byBhbGxvdyBvdGhlciB0byB3b3JrIHdpdGggdGhlIGRhdGEgYW5kIHBlcmhhcHMgcHJvZHVjZSBzb21ldGhpbmcgYmV0dGVyLg0KIycgDQojJyANCiMnIFRoZSBzb3VyY2UgaW5mb3JtYXRpb24gdXNlZCBoZXJlIHdhcyB0aGUgW01hdGhlbWF0aWNzIEdlbmVhb2xvZ3kgUHJvamVjdF0oaHR0cHM6Ly93d3cuZ2VuZWFsb2d5Lm1hdGgubmRzdS5ub2Rhay5lZHUvKS4gSSBzZWxlY3RlZCBvbmx5IGEgaGFuZGZ1bA0KIycgb2YgZmlyc3QtbGV2ZWwgc3R1ZGVudHMgYW5kIHNvbWUgb2YgdGhlaXIgZGVzY2VuZGFudHMuIFRoZSBiYXNlIGRhdGFzZXQgSSdtIHVzaW5nIGhlcmUgaW5jbHVkZXM6DQojJyBgYWR2aXNvcmAsIGBzdHVkZW50YCwgYGluc3RpdHV0aW9uYCwgYFBoRHllYXJgLCBhbmQgYE1HRF9pZGAsIHRoZSBJRCBudW1iZXIgb2YgdGhlIHN0dWRlbnQgaW4gdGhlDQojJyBnZW5lYW9sb2d5IGRhdGEgYmFzZS4NCiMnIA0KIycgQ3JlYXRpbmcgc3VjaCBhIGRpYWdyYW0gaXMgcHJvYmxlbWF0aWMgZm9yIGEgdmFyaWV0eSBvZiByZWFzb25zOg0KIycgDQojJyAqIEl0IGZvbGxvd3MgKiphZHZpc29yLS0tc3R1ZGVudCoqIHJlbGF0aW9ucyBvbmx5IGEgZmV3IHN0ZXBzIHJlbW92ZWQgZnJvbSB0aGUgcHJpbmNpcGFsIHBhcnRpY2lwYW50cy4gTm90IGV2ZXJ5b25lJ3Mgc3R1ZGVudHMgYXJlIHNob3duLiANCiMnICogVGhlIGNvbm5lY3Rpb25zIGFtb25nIGFsbCB0aG9zZSBpbnZvbHZlZCBpbiB0aGlzIGhpc3RvcnkgYXJlIG9ubHkgaW5jb21wbGV0ZWx5IGNvbnZleWVkIGluIGEgDQojJyBuZXR3b3JrIGRpYWdyYW0uIA0KIycgKiBUaGVyZSBhcmUgY3Jvc3MtY29ubmVjdGlvbnMgb2YgaW5mbHVlbmNlLCBub3QgaW5kaWNhdGVkIGJ5IGRpcmVjdCBtZW50b3JzaGlwLg0KIycgKiBUaGUgbWFpbiBwYWNrYWdlLCBgZ2dyYXBoYCwgIEkgdXNlZCBmb3IgdGhlIGRpYWdyYW0gYXQgdGhlIGVuZCBnYXZlIG1lc3N5IHJlc3VsdHMsIGFuZCBzbyBJIGhhZCB0byBwcnVuZSBzb21lIGJyYW5jaGVzIHRvIG1ha2UgaXQgcmVhZGFibGUuIA0KIycgICBCdXQgbW9yZSBnZW5lcmFsbHksIGl0IHJlZmxlY3RzIHRoZSBsaW1pdGF0aW9ucyBvZiBhICoqc3RhdGljKiogZ3JhcGggb2YgKipmaXhlZCByZXNvbHV0aW9uKiogZm9yIGFuIGFydGljbGUuDQojJyAgIA0KIycgDQojJyANCiMnIExldCdzIGdldCBzdGFydGVkLiBUaGlzIGlzIHB1Ymxpc2hlZCBhdDogaHR0cHM6Ly9ycHVicy5jb20vZnJpZW5kbHkvRURBLW5ldHdvcmsNCiMnDQojJyAjIyBMb2FkIHBhY2thZ2VzDQpsaWJyYXJ5KG9wZW54bHN4KSAjIFJlYWQgZXhjZWwgZmlsZXM7IHRoaXMgc3VwcG9ydHMgdXNpbmcgYSBVUkwNCmxpYnJhcnkoZHBseXIpICAgICMgQSBHcmFtbWFyIG9mIERhdGEgTWFuaXB1bGF0aW9uDQpsaWJyYXJ5KGdncmFwaCkgICAjIEFuIEltcGxlbWVudGF0aW9uIG9mIEdyYW1tYXIgb2YgR3JhcGhpY3MgZm9yIEdyYXBocyBhbmQgTmV0d29ya3MNCmxpYnJhcnkoaWdyYXBoKSAgICMgTmV0d29yayBBbmFseXNpcyBhbmQgVmlzdWFsaXphdGlvbg0KbGlicmFyeShnZ3Bsb3QyKSAgIyBDcmVhdGUgRWxlZ2FudCBEYXRhIFZpc3VhbGlzYXRpb25zIFVzaW5nIHRoZSBHcmFtbWFyIG9mIEdyYXBoaWNzDQpsaWJyYXJ5KGdyaWQpICAgICAjIFRoZSBHcmlkIEdyYXBoaWNzIFBhY2thZ2UNCmxpYnJhcnkocmxhbmcpICAgICMgRnVuY3Rpb25zIGZvciBCYXNlIFR5cGVzIGFuZCBDb3JlIFIgYW5kICdUaWR5dmVyc2UnIEZlYXR1cmVzDQpsaWJyYXJ5KGdsdWUpICAgICAjIEludGVycHJldGVkIFN0cmluZyBMaXRlcmFscw0KbGlicmFyeShoZXJlKQ0KbGlicmFyeShuZXR3b3JrRDMpDQoNCiMnICMjIFJlYWQgdGhlIGRhdGEgc2V0DQojJyBUbyBtYWtlIHRoaXMgc2NyaXB0IHJlcHJvZHVjaWJsZSwgcmVhZCBmcm9tIGEgY2xvdWQgVVJMLiBJdCBzZWVtZSB0aGF0IG9ubHkge29wZW54bHN4fSBzdXBwb3J0cyB0aGlzLg0KRURBX2dlbmVhb2xvZ3kgPC0gcmVhZC54bHN4KCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL29xM2p3dmc4YnRvOTNsbi9FREEtZ2VuZWFvbG9neS54bHN4P2RsPTEiKQ0Kc3RyKEVEQV9nZW5lYW9sb2d5KQ0KDQojJyAjIyBGaXh1cCBhIGZldyB0aGluZ3MNCiMnIGBpZ3JhcGhgIHdhbnRzIGBwYXJlbnRgIGFuZCBgY2hpbGRgIGFzIHRoZSBlZGdlIGxpc3QgdmFyaWFibGVzLg0KIycgTWFrZSAiSW5zdGl0dXRpb24iIGFwcGVhciBpbiB0aGUgbGVnZW5kLCBub3QgImluc3RpdHV0aW9uIi4NCkVEQV9nZW4gPC0gRURBX2dlbmVhb2xvZ3kgJT4lDQogIHJlbmFtZShwYXJlbnQgPSBhZHZpc29yLCANCiAgICAgICAgIGNoaWxkID0gc3R1ZGVudCwNCiAgICAgICAgIEluc3RpdHV0aW9uID0gaW5zdGl0dXRpb24pIA0KDQojJyBDbGVhbiB1cCBzb21lIGxpbmtzIG5vdCB0byBiZSBzaG93bi4NCiMnIEFkZCBhbiBleHBsaWNpdCBgbGlua2AgZnJvbSBgTUdEX2lkYCB0byBnZW5lYWxvZ3kgZGF0YSBiYXNlLg0KRURBX2dlbiA8LSBFREFfZ2VuICU+JSANCiAgbXV0YXRlKG1haW4gPSAoY2hpbGQgJWluJSBjKCJKb2huIFR1a2V5IiwgIkhhcm9sZCBHdWxsaWtzZW4iKSkgKSAlPiUgDQogIGZpbHRlciggIShwYXJlbnQgJWluJSBjKCJTb2xvbW9uIExlZnNjaGV0eiIsICJKYW1lcyBBbmdlbGwiKSkgKSAlPiUgDQogIGZpbHRlciggIShjaGlsZCAlaW4lIGMoIkNseWRlIENvb21icyIsICJDaGFybGVzIExld2lzIikpKSAlPiUNCiAgbXV0YXRlKGxpbmsgPSBnbHVlOjpnbHVlKCJodHRwczovL3d3dy5nZW5lYWxvZ3kubWF0aC5uZHN1Lm5vZGFrLmVkdS9pZC5waHA/aWQ9e01HRF9pZH0iKSkNCg0KIycgIyMgVHJhbnNmb3JtIHRvIGBpZ3JhcGhgIGZvcm1hdA0KIycgYGlncmFwaGAgd2FudHMgcGFyZW50LCBjaGlsZCBpbiB0aGUgZmlyc3QgdHdvIGNvbHVtbnMuIE90aGVyIHZhcmlhYmxlcyBiZWNvbWUgIGF0dHJpYnV0ZXMgb2YgdmVydGljZXMgaW4gdGhlIGdyYXBoLg0KRURBX2dyYXBoIDwtIGlncmFwaDo6Z3JhcGhfZnJvbV9kYXRhX2ZyYW1lKEVEQV9nZW5bLGMoMSwzLDIsNDo3KV0pDQpwcmludChFREFfZ3JhcGgpDQoNCiMnICMjIEV4YW1pbmUgdGhlIGBpZ3JhcGhgIG9iamVjdA0KIycgU2VlOiBodHRwczovL2thdGV0by5uZXQvd3AtY29udGVudC91cGxvYWRzLzIwMTgvMDYvUG9sbmV0JTIwMjAxOCUyMFIlMjBOZXR3b3JrJTIwVmlzdWFsaXphdGlvbiUyMFdvcmtzaG9wLnBkZg0KIycgZm9yIHdvcmtpbmcgd2l0aCBgaWdyYXBoYA0KIycgDQojJyBFeHRyYWN0IGVkZ2VzIGFzIGEgZGF0YSBmcmFtZQ0KZWRnZXMgPC0gaWdyYXBoOjphc19kYXRhX2ZyYW1lKEVEQV9ncmFwaCwgd2hhdD0iZWRnZXMiKQ0KZ2xpbXBzZShlZGdlcykNCg0KIycgR2V0IGdyYXBoIHZlcnRpY2VzOiBgVigpYCByZXR1cm5zIHZlcnRpY2VzOyBgRSgpYCByZXR1cm5zIGVkZ2VzLg0KaWdyYXBoOjpWKEVEQV9ncmFwaCkNCg0KIycgR2V0IG5vZGUgZGVncmVlcy4gSSB1c2UgdGhpcyB0byBtYWtlIFR1a2V5L01vc3RlbGxlciBtb3N0IHByb21pbmVudCwgYnV0IHNob3cgYWxsIGluIHJlbGF0aW9uIHRvIHRoZSBudW1iZXIgb2YgbGlua3MuDQpkZWcgPC0gaWdyYXBoOjpkZWdyZWUoRURBX2dyYXBoLCBtb2RlPSJhbGwiKQ0KVihFREFfZ3JhcGgpJGRlZ3JlZSA8LSBkZWcNCg0KIycgIyMgUGxvdHRpbmcgd2l0aCBgZ2dyYXBoYA0KIycgVGhlIFtgZ2dyYXBoYF0oaHR0cHM6Ly9naXRodWIuY29tL3Rob21hc3A4NS9nZ3JhcGgpIHBhY2thZ2Ugd2FzIHByb3Bvc2VkIGFzIGEgIkdyYW1tYXIgb2YgR3JhcGhpY3MgZm9yIEdyYXBocyBhbmQgTmV0d29ya3MiLg0KIycgWWV0LCBJJ20gZmluZGluZyBpdCBzb21ld2hhdCBpbmNvbXBsZXRlIChoYWQgdG8gYWRkIGAkZGVncmVlYCB0byB0aGUgdmVydGljZXMgdXNpbmcgYGlncmFwaDo6VigpYCkgYW5kIGhhcmQgdG8gdG90YWxseSB1bmRlcnN0YW5kLg0KIycgDQojJyBJJ20gdXNpbmcgZmlsbGVkIGFycm93IGluIHRoZSBlZGdlcywgYnV0IHRoZXNlIGRpZCBub3QgYXBwZWFyIGluIHRoZSBsZWdlbmQuDQojJyBGcm9tIFt0aGlzIHN0YWNrb3ZlcmZsb3cgcG9zdF0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNzIzOTU0MTAvZ2dyYXBoLW1ha2UtZmlsbGVkLWFycm93LWVkZ2VzLWFwcGVhci1pbi1sZWdlbmQpDQojJyBJIGZvdW5kIGhvdyB0byBtb2RpZnkgYEdlb21FZGdlUGF0aCgpYCB0byBjcmVhdGUgYSBjdXN0b20ga2V5IGZ1bmN0aW9uLg0KZHJhd19rZXlfY3VzdG9tID0gZnVuY3Rpb24oZGF0YSwgcGFyYW1zLCBzaXplKSB7DQogIHNlZ21lbnRzR3JvYigwLjEsIDAuNSwgMC45LCAwLjUsDQogICAgICAgICAgICAgICBncCA9IGdwYXIoDQogICAgICAgICAgICAgICAgIGNvbCA9IGFscGhhKGRhdGEkZWRnZV9jb2xvdXIsIGRhdGEkZWRnZV9hbHBoYSksDQogICAgICAgICAgICAgICAgIGZpbGwgPSBhbHBoYShkYXRhJGVkZ2VfY29sb3VyLCBkYXRhJGVkZ2VfYWxwaGEpLCAgIyA8LSBhZGQgZmlsbCB0byBhcnJvdyBoZWFkIQ0KICAgICAgICAgICAgICAgICBsd2QgPSBkYXRhJGVkZ2Vfd2lkdGggKiAucHQsDQogICAgICAgICAgICAgICAgIGx0eSA9IGRhdGEkZWRnZV9saW5ldHlwZSwgDQogICAgICAgICAgICAgICAgIGxpbmVlbmQgPSAnYnV0dCcNCiAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgICBhcnJvdyA9IHBhcmFtcyRhcnJvdw0KICApDQp9DQoNCiMnICMjIENob2ljZSBvZiBsYXlvdXRzDQojJyBUaGVyZSBpcyBhIGxhcmdlIHZhcmlldHkgb2YgZ3JhcGggbGF5b3V0cyB0aGF0IGNvdWxkIGJlIHVzZWQgaGVyZS4NCiMnIERldGFpbHMgYW5kIGV4YW1wbGVzIGF0OiBodHRwczovL3d3dy5kYXRhLWltYWdpbmlzdC5jb20vMjAxNy9nZ3JhcGgtaW50cm9kdWN0aW9uLWxheW91dHMvDQojJyANCiMnIFNlZTogaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS8zUUFNVy5wbmcgZm9yIHBpY3RvcmlhbCBleGFtcGxlcy4NCiMnIEFmdGVyIHNvbWUgZXhwZXJpbWVudGF0aW9uLCBJIGNob3NlIGBmcmAsIHRoZSBGcnVjaHRlci1SaGVpbmhvbGQgZm9yY2UgZGlyZWN0ZWQgbGF5b3V0Lg0KaWdyYXBoX2xheW91dHMgPC0gYygnc3RhcicsICdjaXJjbGUnLCAnZ2VtJywgJ2RoJywgJ2dyYXBob3B0JywgJ2dyaWQnLCAnbWRzJywgDQogICAgICAgICAgICAgICAgICAgICdyYW5kb21seScsICdmcicsICdraycsICdkcmwnLCAnbGdsJykNCg0KIycgIyMgRHJhdyB0aGUgZGlhZ3JhbQ0KIycgVGhpcyB1c2VzIGBzaXplID0gZGVncmVlYCB0byBzZXQgdGhlIHZlcnRleCBzaXplLiBFZGdlIGxpbmtzIGFyZSBjb2xvcmVkIGJ5IGBJbnN0aXR1dGlvbmAuDQojJyBUaGUgbGVnZW5kIGBrZXlfZ2x5cGhgIG92ZXJyaWRlcyB0aGUgYGdncmFwaGAgZGVmYXVsdCwgdXNpbmcgbXkgYGRyYXdfa2V5X2N1c3RvbSgpYCBmdW5jdGlvbg0KIycgdG8gZmlsbCB0aGUgYXJyb3cgaGVhZHMuDQpnZ3JhcGgoRURBX2dyYXBoLCBsYXlvdXQ9ImZyIikgKyANCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbG9yPUluc3RpdHV0aW9uKSwNCiAgICAgICAgICAgICAgICAgYXJyb3cgPSBncmlkOjphcnJvdyh0eXBlID0gImNsb3NlZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuZ2xlPTE1LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGggPSB1bml0KDAuMTUsICJpbmNoZXMiKSksDQogICAgICAgICAgICAgICAgIGtleV9nbHlwaCA9ICJjdXN0b20iICkgKyAgICAgICAgICAgICAgICMgPC0gSSdtIG5ldyENCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplPWRlZ3JlZSksIA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSBzY2FsZXM6OmFscGhhKCJibGFjayIsIC40KSwNCiAgICAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKw0KICBnZ3RpdGxlKCJTcGVjaW1lbiBvZiBhIENoYXJ0IG9mIEdlbmVhb2xvZ3kgb2YgRURBIikgKyANCiAgdGhlbWVfZ3JhcGgoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKSANCg0KDQojJyAjIyBHb2luZyBmdXJ0aGVyDQojJyBPbmUgbmljZSB0aGluZyB0byBkbyB3b3VsZCBiZSB0byBtYWtlIHRoaXMgZGlhZ3JhbSBpbnRlcmFjdGl2ZSwgd2l0aCB0b29sIHRpcHMsIHNvIHRoYXQgaG92ZXJpbmcgb3ZlciBhIG5vZGUNCiMnIHdvdWxkIHNob3cgdGhlIGRldGFpbHMgb2YgYW4gaW5kaXZpZHVhbC4NCiMnIA0KIycgSGVyZSdzIHdoYXQgSSB0cmllZCwgbWFraW5nIGEgYHRleHRgIHZhcmlhYmxlIHRoYXQgY291bGQgYmUgdXNlZCBhcyBhIHRvb2wgdGlwLg0KIycgDQoNCiMnICMjIyBhZGQgdG9vbHRpcCBgdGV4dGAgdG8gdGhlIGRhdGFzZXQNCkVEQV9nZW4yIDwtIEVEQV9nZW4gJT4lIA0KICBtdXRhdGUodGV4dCA9IGdsdWUoDQogICAgIm5hbWU6IHtjaGlsZH08YnI+DQogICAge0luc3RpdHV0aW9ufTxicj4NCiAgICBQaEQgeWVhcjoge1BoRHllYXJ9PGJyPg0KICAgIE1HRF9pZDogPGEgaHJlZj0ne2xpbmt9Jz57TUdEX2lkfTwvYT4iDQogICkpDQoNCiMnICBhZGQgdGhlIGB0ZXh0YCB0byB0aGUgaWdyYXBoIHJlcHJlc2VudGF0aW9uDQpFREFfZ3JhcGgyIDwtIGlncmFwaDo6Z3JhcGhfZnJvbV9kYXRhX2ZyYW1lKEVEQV9nZW4yWyxjKDEsMywyLDQ6OCldKQ0KDQojJyBTYW1lIHBsb3QgYXMgYmVmb3JlDQpwMiA8LQ0KICBnZ3JhcGgoRURBX2dyYXBoLCBsYXlvdXQ9ImZyIikgKyANCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbG9yPUluc3RpdHV0aW9uKSwNCiAgICAgICAgICAgICAgICAgYXJyb3cgPSBncmlkOjphcnJvdyh0eXBlID0gImNsb3NlZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuZ2xlPTE1LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGggPSB1bml0KDAuMTUsICJpbmNoZXMiKSksDQogICAgICAgICAgICAgICAgIGtleV9nbHlwaCA9ICJjdXN0b20iICkgKyAgICAgICAgICAgICAgICMgPC0gSSdtIG5ldyENCiAgZ2VvbV9ub2RlX3BvaW50KCkgKw0KICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgcmVwZWwgPSBUUlVFKSArDQogIGdndGl0bGUoIlNwZWNpbWVuIG9mIGEgQ2hhcnQgb2YgR2VuZWFvbG9neSBvZiBFREEiKSArIA0KICB0aGVtZV9ncmFwaCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpIA0KDQojJyAjIyMgVWdoISANCiMnIEkgdGhvdWdodCBJIGNvdWxkIGp1c3QgdXNlIGBwbG90bHk6OmdncGxvdGx5KClgIGhlcmUuIA0KIycgSG93ZXZlciwgbW9zdCBnZW9tcyB1c2VkIGJ5IGBnZ3JhcGhgIGhlcmUgYXJlIG5vdCB5ZXQgaW1wbGVtZW50ZWQgaW4gYHBsb3RseWAuICBNYW55IHdhcm5pbmdzLiBBbGwgSSBnZXQgaXMgdGhlIHBvaW50cy4NCmxpYnJhcnkocGxvdGx5KQ0KZ2dwbG90bHkocDIsIHRvb2x0aXA9InRleHQiKQ0KDQojJyAjIyMgV2hhdCBJJ2QgbGlrZQ0KIycgSGVyZSBpcyBvbmUgcmVzdWx0IEknZCBsaWtlIHRvIGFjaGlldmUsIHNob3duIGluIGEgbW9jay11cCwgYnV0IHdpdGggdGhlIHRvb2x0aXAgYm94IGRpc3BsYWNlZCBmb3IgcmVhZGFiaWxpdHkuDQojJyANCg0KIysgdG9vbHRpcC1kZW1vLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjkwJSINCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImZpZyIsICJFREEtZ2dyYXBoLWZyLXRvb2x0aXAucG5nIikpDQoNCiMnICMjIEludGVyYWN0aXZlIEpTIHZpc3VhbGl6YXRpb24gd2l0aCBuZXR3b3JrRDMgPw0KIycgDQojJyBPbmUgYWx0ZXJuYXRpdmUsIHVzaW5nIHRoZSBgbmV0d29ya0QzYCBwYWNrYWdlIHdhcyBzdWdnZXN0ZWQgYnkgVWRpIEFsdGVyLg0KIycgSXQgc29sdmVzIHRoZSBwcm9ibGVtIG9mIHJlc29sdXRpb24gaW4gdGhlIGdyYXBoIHZpYSB6b29tIGFuZCBwYW46IGRyYWcvY2xpY2sgdG8gZm9jdXMgb24gYSBub2RlLA0KIycgbW91c2Ugd2hlZWwgdG8gem9vbSBpbi9vdXQuIA0KIycgDQojJyBJdCBpcyBvbmx5IHRoZSBiZWdpbm5pbmc6IG1pc3NpbmcgSW5zdGl0dXRpb24gYXMgZ3JvdXBpbmcgdmFyaWFibGUgdG8gY29sb3IgdGhlIG5vZGVzLCBob3ZlciBvcHRpb25zLCBldGMuDQojJyANCiMnIA0KIycgDQpzaW1wbGVOZXR3b3JrKGVkZ2VzLCBoZWlnaHQ9IjEwMHB4Iiwgd2lkdGg9IjEwMHB4Iiwgem9vbSA9IFRSVUUpDQoNCg==