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:

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}"))

Transform to igraph format

igraph wants parent, child in the first two columns. Other variables become attributes of vertices in the graph.

EDA_graph <- igraph::graph_from_data_frame(EDA_gen[,c(1,3,2,4:7)])
print(EDA_graph)
## IGRAPH 6f9cbf9 DN-- 37 35 -- 
## + attr: name (v/c), Institution (e/c), PhDyear (e/n), MGD_id (e/n),
## | main (e/l), link (e/c)
## + edges from 6f9cbf9 (vertex names):
##  [1] John Tukey         ->Arthur Dempster    
##  [2] John Tukey         ->Leo Goodman        
##  [3] John Tukey         ->David Hoaglin      
##  [4] John Tukey         ->Frederick Mosteller
##  [5] Samuel Wilks       ->Frederick Mosteller
##  [6] Frederick Mosteller->Persi Diaconis     
##  [7] Frederick Mosteller->Stephen Fienberg   
## + ... omitted several edges

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==