R cafe | plot-a-thon

. ================================================== Analyzing ‘4TU.ResearchData’ statistics & visualizing website: https://delft-rcafe.github.io/home/Plotathon.html data: https://data.4tu.nl/private_datasets/fuCYKTarWe3ShLS3NQSqufXBpqHgAFHr_l0Lbi2APok groups: https://github.com/4TUResearchData/djehuty/blob/main/src/djehuty/backup/resources/groups.json

# Importing packages

#use_virtualenv("myenv")
library(rjson)
library(tidyverse)
Warning: package ‘tidyverse’ was built under R version 4.2.3Warning: package ‘ggplot2’ was built under R version 4.2.3Warning: package ‘tibble’ was built under R version 4.2.3Warning: package ‘tidyr’ was built under R version 4.2.3Warning: package ‘readr’ was built under R version 4.2.3Warning: package ‘purrr’ was built under R version 4.2.3Warning: package ‘dplyr’ was built under R version 4.2.3Warning: package ‘stringr’ was built under R version 4.2.3Warning: package ‘forcats’ was built under R version 4.2.3Warning: package ‘lubridate’ was built under R version 4.2.3── Attaching core tidyverse packages ─────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.3     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.3     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     ── Conflicts ───────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(tidyr)
library(dplyr)
library(purrr)
library(reshape2)
Warning: package ‘reshape2’ was built under R version 4.2.3
Attaching package: ‘reshape2’

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

    smiths
library(data.table)
Warning: package ‘data.table’ was built under R version 4.2.3Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
data.table 1.14.8 using 8 threads (see ?getDTthreads).  Latest news: r-datatable.com

Attaching package: ‘data.table’

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

    dcast, melt

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

    hour, isoweek, mday, minute, month, quarter, second, wday, week, yday, year

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

    between, first, last

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

    transpose
library(lubridate)

Read Data

# reading the data

dataset <- fromJSON(file="datasets.json") # main dataset file
data <- bind_rows(dataset) # json2df

datastat <- read.delim("views_downloads.tsv", header=TRUE, sep="\t", dec=".") # views/downloads stats file

groups <- fromJSON(file="groups.json") # name mapping file (from github @ 4TUResearchData/djehuty)
groups <- bind_rows(groups) # json2df

Data Wrangling

# keeping unique rows only, very crude way to filter data quickly

data.1 <- data %>% distinct(uuid, .keep_all = TRUE) # filter unique 'uuid'
datastat.1 <- datastat %>% distinct(uuid, .keep_all = TRUE)

data.all <- merge(data.1, datastat.1, by="uuid") # adding view/download stats
colnames(groups)[colnames(groups) == "id"] ="group_id"
data.all.label <- merge(data.all, groups, by="group_id") # adding university labels
data.all.label$date <- ymd_hms(data.all.label$published_date) # fixing datetime format

# select-data with required (useful) columns only
data.select <- data.all.label %>% select(
  group_id,
  #uuid,
  #title,
  #published_date,
  date,
  views,
  downloads,
  #name
)
# grouping based on university
data.group <- data.select %>% group_by(group_id)

# calculating different stats for different plot ideas
data.group.stat <- data.group %>% summarise(
  view_x = mean(views),
  view_m = median(views),
  view_std = sd(views, na.rm=TRUE),
  download_x = mean(downloads),
  download_m = median(downloads),
  download_std = sd(downloads, na.rm=TRUE)
)
data.group.stat

data.group.quant.view <- as.data.frame(do.call("rbind",tapply(data.group$views, data.group$group_id, quantile)))
colnames(data.group.quant.view) <- c( "v0", "v25", "v50", "v75", "v100")
data.group.quant.view <- rownames_to_column(data.group.quant.view, "group_id")

data.group.quant.download <- as.data.frame(do.call("rbind",tapply(data.group$downloads, data.group$group_id, quantile)))
colnames(data.group.quant.download) <- c("d0", "d25", "d50", "d75", "d100")
data.group.quant.download <- rownames_to_column(data.group.quant.download, "group_id")

data.group.quant <- merge(data.group.quant.view, data.group.quant.download)
data.group.quant

data.group.quant <- merge(data.group.quant, groups)
data.group.quant$name_id <- c("4TU","TU/D","TU/e","UT","WUR","OI","TU/D (s)","TU/e (s)","UT (s)","NIOZ","LU","UU","EUR","D","RU","RUG","MU") # short-key for label
data.group.quant
NA

Plots

# testing different plot ideas

p1 <- boxplot(views ~ group_id, data=data.group) # views distribution
p2 <- boxplot(downloads ~ group_id, data=data.group) # downloads distribution
p1
p2

p3 <- ggplot() +
  geom_point(data = data.group.stat, 
            aes(x=view_m, y=download_m)
            ) # downloads vs views (with median)
p3

p4 <- ggplot() +
  geom_point(data = data.group.stat, 
            aes(x=view_x, y=download_x)
            ) # downloads vs views (with mean)
p4

p5 <- ggplot() +
  geom_point(data = data.group.stat, 
            aes(x=view_std, y=download_std)
            ) # downloads vs views (with standard deviation)
p5
# idea | cross-box -as- xy-err w/ quants
# plotting 1st-3rd quantiles (as error-bars) of downloads as-a-function-of views around median (i.e 2nd quantile) > to extract trends across universities

p6 <- png(file="4TU_usage_plot_v0.png",width=1200,height=500,res=78)
p6 <- par(mar = c(11.5, 5, 2, 3))
p6 <- plot(data.group.quant$v50, data.group.quant$d50,
           main="Institute-wise 4TU.ResearchData usage metric",
           xlab="Views", ylab="Downloads",
           pch=1, cex=1.5, lty="solid", lwd=2,
           xlim = c(0, max(data.group.quant$v50)*1.1),
           ylim = c(0, max(data.group.quant$d50)*1.1),
           ) # scatter of median
wrap_strings  <- function(vector_of_strings,width){as.character(sapply(vector_of_strings,FUN=function(x){paste(strwrap(x,width=width), collapse="\n")}))}
p6 <- graphics::title(sub=wrap_strings("Usage statistics of Institute's 4TU research data repositories: Dot represent median value (2nd quartile), while flat-arrows represent inter-quartile range (IQR) (1st-3rd quartile) for Institute's repositories views and downloads \n [Key: 4TU - 4TU.ResearchData, TU/D - Delft University of Technology, TU/e - Eindhoven University of Technology, UT - University of Twente, WUR - Wageningen University and Research, OI - Other institutions, TU/D (s) - Delft University of Technology Students, TU/e (s) - Eindhoven University of Technology Students, UT (s) - University of Twente Students, NIOZ - NIOZ Royal Netherlands Institute for Sea Research, LU - Leiden University, UU - Utrecht University, UA - University of Amsterdam, EUR - Erasmus University Rotterdam, D - Deltares, IHE - IHE Delft Institute for Water Education, RU - Radboud University, RUG - University of Groningen, MU - Maastricht University, UA (s) - University of Amsterdam Students]", 185), line=9.75)
p6 <- arrows(x0=data.group.quant$v50, y0=data.group.quant$d25,
         x1=data.group.quant$v50, y1=data.group.quant$d75,
         code=3, angle=90, length=0.1, col="gray") # quantiles of downloads
p6 <- arrows(x0=data.group.quant$v25, y0=data.group.quant$d50,
         x1=data.group.quant$v75, y1=data.group.quant$d50,
         code=3, angle=90, length=0.1, col="gray") # quantiles of views
p6 <- text(data.group.quant$v50, data.group.quant$d50, labels=data.group.quant$name_id, cex= 1, pos=4) # adding short-label/key

p6
dev.off()
LS0tDQp0aXRsZTogIlIgQ2FmZSBwbG90LWEtdGhvbiINCmF1dGhvcjogIllhc2ggSmF3YWxlIg0KZW1haWw6ICJ5LmsuamF3YWxlQHR1ZGVsZnQubmwiDQpkYXRlOiAiMDIvMTAvMjAyMyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgUiBjYWZlIHwgcGxvdC1hLXRob24NCi4gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCkFuYWx5emluZyAnNFRVLlJlc2VhcmNoRGF0YScgc3RhdGlzdGljcyAmIHZpc3VhbGl6aW5nDQp3ZWJzaXRlOiBodHRwczovL2RlbGZ0LXJjYWZlLmdpdGh1Yi5pby9ob21lL1Bsb3RhdGhvbi5odG1sDQpkYXRhOiBodHRwczovL2RhdGEuNHR1Lm5sL3ByaXZhdGVfZGF0YXNldHMvZnVDWUtUYXJXZTNTaExTM05RU3F1ZlhCcHFIZ0FGSHJfbDBMYmkyQVBvaw0KZ3JvdXBzOiBodHRwczovL2dpdGh1Yi5jb20vNFRVUmVzZWFyY2hEYXRhL2RqZWh1dHkvYmxvYi9tYWluL3NyYy9kamVodXR5L2JhY2t1cC9yZXNvdXJjZXMvZ3JvdXBzLmpzb24NCg0KYGBge3IgcGFja2FnZXN9DQojIEltcG9ydGluZyBwYWNrYWdlcw0KDQojdXNlX3ZpcnR1YWxlbnYoIm15ZW52IikNCmxpYnJhcnkocmpzb24pDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkocmVzaGFwZTIpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KYGBgDQoNCiMgUmVhZCBEYXRhDQpgYGB7ciBnZXQgZGF0YX0NCiMgcmVhZGluZyB0aGUgZGF0YQ0KDQpkYXRhc2V0IDwtIGZyb21KU09OKGZpbGU9ImRhdGFzZXRzLmpzb24iKSAjIG1haW4gZGF0YXNldCBmaWxlDQpkYXRhIDwtIGJpbmRfcm93cyhkYXRhc2V0KSAjIGpzb24yZGYNCg0KZGF0YXN0YXQgPC0gcmVhZC5kZWxpbSgidmlld3NfZG93bmxvYWRzLnRzdiIsIGhlYWRlcj1UUlVFLCBzZXA9Ilx0IiwgZGVjPSIuIikgIyB2aWV3cy9kb3dubG9hZHMgc3RhdHMgZmlsZQ0KDQpncm91cHMgPC0gZnJvbUpTT04oZmlsZT0iZ3JvdXBzLmpzb24iKSAjIG5hbWUgbWFwcGluZyBmaWxlIChmcm9tIGdpdGh1YiBAIDRUVVJlc2VhcmNoRGF0YS9kamVodXR5KQ0KZ3JvdXBzIDwtIGJpbmRfcm93cyhncm91cHMpICMganNvbjJkZg0KDQpgYGANCg0KIyBEYXRhIFdyYW5nbGluZw0KYGBge3IgZGF0YSB3cmFuZ2xpbmd9DQojIGtlZXBpbmcgdW5pcXVlIHJvd3Mgb25seSwgdmVyeSBjcnVkZSB3YXkgdG8gZmlsdGVyIGRhdGEgcXVpY2tseQ0KDQpkYXRhLjEgPC0gZGF0YSAlPiUgZGlzdGluY3QodXVpZCwgLmtlZXBfYWxsID0gVFJVRSkgIyBmaWx0ZXIgdW5pcXVlICd1dWlkJw0KZGF0YXN0YXQuMSA8LSBkYXRhc3RhdCAlPiUgZGlzdGluY3QodXVpZCwgLmtlZXBfYWxsID0gVFJVRSkNCg0KZGF0YS5hbGwgPC0gbWVyZ2UoZGF0YS4xLCBkYXRhc3RhdC4xLCBieT0idXVpZCIpICMgYWRkaW5nIHZpZXcvZG93bmxvYWQgc3RhdHMNCmNvbG5hbWVzKGdyb3VwcylbY29sbmFtZXMoZ3JvdXBzKSA9PSAiaWQiXSA9Imdyb3VwX2lkIg0KZGF0YS5hbGwubGFiZWwgPC0gbWVyZ2UoZGF0YS5hbGwsIGdyb3VwcywgYnk9Imdyb3VwX2lkIikgIyBhZGRpbmcgdW5pdmVyc2l0eSBsYWJlbHMNCmRhdGEuYWxsLmxhYmVsJGRhdGUgPC0geW1kX2htcyhkYXRhLmFsbC5sYWJlbCRwdWJsaXNoZWRfZGF0ZSkgIyBmaXhpbmcgZGF0ZXRpbWUgZm9ybWF0DQoNCiMgc2VsZWN0LWRhdGEgd2l0aCByZXF1aXJlZCAodXNlZnVsKSBjb2x1bW5zIG9ubHkNCmRhdGEuc2VsZWN0IDwtIGRhdGEuYWxsLmxhYmVsICU+JSBzZWxlY3QoDQogIGdyb3VwX2lkLA0KICAjdXVpZCwNCiAgI3RpdGxlLA0KICAjcHVibGlzaGVkX2RhdGUsDQogIGRhdGUsDQogIHZpZXdzLA0KICBkb3dubG9hZHMsDQogICNuYW1lDQopDQpgYGANCg0KYGBge3IgZGF0YSBwcm9jZXNzaW5nfQ0KIyBncm91cGluZyBiYXNlZCBvbiB1bml2ZXJzaXR5DQpkYXRhLmdyb3VwIDwtIGRhdGEuc2VsZWN0ICU+JSBncm91cF9ieShncm91cF9pZCkNCg0KIyBjYWxjdWxhdGluZyBkaWZmZXJlbnQgc3RhdHMgZm9yIGRpZmZlcmVudCBwbG90IGlkZWFzDQpkYXRhLmdyb3VwLnN0YXQgPC0gZGF0YS5ncm91cCAlPiUgc3VtbWFyaXNlKA0KICB2aWV3X3ggPSBtZWFuKHZpZXdzKSwNCiAgdmlld19tID0gbWVkaWFuKHZpZXdzKSwNCiAgdmlld19zdGQgPSBzZCh2aWV3cywgbmEucm09VFJVRSksDQogIGRvd25sb2FkX3ggPSBtZWFuKGRvd25sb2FkcyksDQogIGRvd25sb2FkX20gPSBtZWRpYW4oZG93bmxvYWRzKSwNCiAgZG93bmxvYWRfc3RkID0gc2QoZG93bmxvYWRzLCBuYS5ybT1UUlVFKQ0KKQ0KZGF0YS5ncm91cC5zdGF0DQoNCmRhdGEuZ3JvdXAucXVhbnQudmlldyA8LSBhcy5kYXRhLmZyYW1lKGRvLmNhbGwoInJiaW5kIix0YXBwbHkoZGF0YS5ncm91cCR2aWV3cywgZGF0YS5ncm91cCRncm91cF9pZCwgcXVhbnRpbGUpKSkNCmNvbG5hbWVzKGRhdGEuZ3JvdXAucXVhbnQudmlldykgPC0gYyggInYwIiwgInYyNSIsICJ2NTAiLCAidjc1IiwgInYxMDAiKQ0KZGF0YS5ncm91cC5xdWFudC52aWV3IDwtIHJvd25hbWVzX3RvX2NvbHVtbihkYXRhLmdyb3VwLnF1YW50LnZpZXcsICJncm91cF9pZCIpDQoNCmRhdGEuZ3JvdXAucXVhbnQuZG93bmxvYWQgPC0gYXMuZGF0YS5mcmFtZShkby5jYWxsKCJyYmluZCIsdGFwcGx5KGRhdGEuZ3JvdXAkZG93bmxvYWRzLCBkYXRhLmdyb3VwJGdyb3VwX2lkLCBxdWFudGlsZSkpKQ0KY29sbmFtZXMoZGF0YS5ncm91cC5xdWFudC5kb3dubG9hZCkgPC0gYygiZDAiLCAiZDI1IiwgImQ1MCIsICJkNzUiLCAiZDEwMCIpDQpkYXRhLmdyb3VwLnF1YW50LmRvd25sb2FkIDwtIHJvd25hbWVzX3RvX2NvbHVtbihkYXRhLmdyb3VwLnF1YW50LmRvd25sb2FkLCAiZ3JvdXBfaWQiKQ0KDQpkYXRhLmdyb3VwLnF1YW50IDwtIG1lcmdlKGRhdGEuZ3JvdXAucXVhbnQudmlldywgZGF0YS5ncm91cC5xdWFudC5kb3dubG9hZCkNCmRhdGEuZ3JvdXAucXVhbnQNCg0KZGF0YS5ncm91cC5xdWFudCA8LSBtZXJnZShkYXRhLmdyb3VwLnF1YW50LCBncm91cHMpDQpkYXRhLmdyb3VwLnF1YW50JG5hbWVfaWQgPC0gYygiNFRVIiwiVFUvRCIsIlRVL2UiLCJVVCIsIldVUiIsIk9JIiwiVFUvRCAocykiLCJUVS9lIChzKSIsIlVUIChzKSIsIk5JT1oiLCJMVSIsIlVVIiwiRVVSIiwiRCIsIlJVIiwiUlVHIiwiTVUiKSAjIHNob3J0LWtleSBmb3IgbGFiZWwNCmRhdGEuZ3JvdXAucXVhbnQNCg0KYGBgDQoNCiMgUGxvdHMNCmBgYHtyIHBsb3QsIHRlc3R9DQojIHRlc3RpbmcgZGlmZmVyZW50IHBsb3QgaWRlYXMNCg0KcDEgPC0gYm94cGxvdCh2aWV3cyB+IGdyb3VwX2lkLCBkYXRhPWRhdGEuZ3JvdXApICMgdmlld3MgZGlzdHJpYnV0aW9uDQpwMiA8LSBib3hwbG90KGRvd25sb2FkcyB+IGdyb3VwX2lkLCBkYXRhPWRhdGEuZ3JvdXApICMgZG93bmxvYWRzIGRpc3RyaWJ1dGlvbg0KcDENCnAyDQoNCnAzIDwtIGdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YS5ncm91cC5zdGF0LCANCiAgICAgICAgICAgIGFlcyh4PXZpZXdfbSwgeT1kb3dubG9hZF9tKQ0KICAgICAgICAgICAgKSAjIGRvd25sb2FkcyB2cyB2aWV3cyAod2l0aCBtZWRpYW4pDQpwMw0KDQpwNCA8LSBnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRhdGEuZ3JvdXAuc3RhdCwgDQogICAgICAgICAgICBhZXMoeD12aWV3X3gsIHk9ZG93bmxvYWRfeCkNCiAgICAgICAgICAgICkgIyBkb3dubG9hZHMgdnMgdmlld3MgKHdpdGggbWVhbikNCnA0DQoNCnA1IDwtIGdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YS5ncm91cC5zdGF0LCANCiAgICAgICAgICAgIGFlcyh4PXZpZXdfc3RkLCB5PWRvd25sb2FkX3N0ZCkNCiAgICAgICAgICAgICkgIyBkb3dubG9hZHMgdnMgdmlld3MgKHdpdGggc3RhbmRhcmQgZGV2aWF0aW9uKQ0KcDUNCg0KYGBgDQoNCmBgYHtyIHBsb3QsIHNldCAyfQ0KIyBpZGVhIHwgY3Jvc3MtYm94IC1hcy0geHktZXJyIHcvIHF1YW50cw0KIyBwbG90dGluZyAxc3QtM3JkIHF1YW50aWxlcyAoYXMgZXJyb3ItYmFycykgb2YgZG93bmxvYWRzIGFzLWEtZnVuY3Rpb24tb2Ygdmlld3MgYXJvdW5kIG1lZGlhbiAoaS5lIDJuZCBxdWFudGlsZSkgPiB0byBleHRyYWN0IHRyZW5kcyBhY3Jvc3MgdW5pdmVyc2l0aWVzDQoNCnA2IDwtIHBuZyhmaWxlPSI0VFVfdXNhZ2VfcGxvdF92MC5wbmciLHdpZHRoPTEyMDAsaGVpZ2h0PTUwMCxyZXM9NzgpDQpwNiA8LSBwYXIobWFyID0gYygxMS41LCA1LCAyLCAzKSkNCnA2IDwtIHBsb3QoZGF0YS5ncm91cC5xdWFudCR2NTAsIGRhdGEuZ3JvdXAucXVhbnQkZDUwLA0KICAgICAgICAgICBtYWluPSJJbnN0aXR1dGUtd2lzZSA0VFUuUmVzZWFyY2hEYXRhIHVzYWdlIG1ldHJpYyIsDQogICAgICAgICAgIHhsYWI9IlZpZXdzIiwgeWxhYj0iRG93bmxvYWRzIiwNCiAgICAgICAgICAgcGNoPTEsIGNleD0xLjUsIGx0eT0ic29saWQiLCBsd2Q9MiwNCiAgICAgICAgICAgeGxpbSA9IGMoMCwgbWF4KGRhdGEuZ3JvdXAucXVhbnQkdjUwKSoxLjEpLA0KICAgICAgICAgICB5bGltID0gYygwLCBtYXgoZGF0YS5ncm91cC5xdWFudCRkNTApKjEuMSksDQogICAgICAgICAgICkgIyBzY2F0dGVyIG9mIG1lZGlhbg0Kd3JhcF9zdHJpbmdzICA8LSBmdW5jdGlvbih2ZWN0b3Jfb2Zfc3RyaW5ncyx3aWR0aCl7YXMuY2hhcmFjdGVyKHNhcHBseSh2ZWN0b3Jfb2Zfc3RyaW5ncyxGVU49ZnVuY3Rpb24oeCl7cGFzdGUoc3Ryd3JhcCh4LHdpZHRoPXdpZHRoKSwgY29sbGFwc2U9IlxuIil9KSl9DQpwNiA8LSBncmFwaGljczo6dGl0bGUoc3ViPXdyYXBfc3RyaW5ncygiVXNhZ2Ugc3RhdGlzdGljcyBvZiBJbnN0aXR1dGUncyA0VFUgcmVzZWFyY2ggZGF0YSByZXBvc2l0b3JpZXM6IERvdCByZXByZXNlbnQgbWVkaWFuIHZhbHVlICgybmQgcXVhcnRpbGUpLCB3aGlsZSBmbGF0LWFycm93cyByZXByZXNlbnQgaW50ZXItcXVhcnRpbGUgcmFuZ2UgKElRUikgKDFzdC0zcmQgcXVhcnRpbGUpIGZvciBJbnN0aXR1dGUncyByZXBvc2l0b3JpZXMgdmlld3MgYW5kIGRvd25sb2FkcyBcbiBbS2V5OiA0VFUgLSA0VFUuUmVzZWFyY2hEYXRhLCBUVS9EIC0gRGVsZnQgVW5pdmVyc2l0eSBvZiBUZWNobm9sb2d5LCBUVS9lIC0gRWluZGhvdmVuIFVuaXZlcnNpdHkgb2YgVGVjaG5vbG9neSwgVVQgLSBVbml2ZXJzaXR5IG9mIFR3ZW50ZSwgV1VSIC0gV2FnZW5pbmdlbiBVbml2ZXJzaXR5IGFuZCBSZXNlYXJjaCwgT0kgLSBPdGhlciBpbnN0aXR1dGlvbnMsIFRVL0QgKHMpIC0gRGVsZnQgVW5pdmVyc2l0eSBvZiBUZWNobm9sb2d5IFN0dWRlbnRzLCBUVS9lIChzKSAtIEVpbmRob3ZlbiBVbml2ZXJzaXR5IG9mIFRlY2hub2xvZ3kgU3R1ZGVudHMsIFVUIChzKSAtIFVuaXZlcnNpdHkgb2YgVHdlbnRlIFN0dWRlbnRzLCBOSU9aIC0gTklPWiBSb3lhbCBOZXRoZXJsYW5kcyBJbnN0aXR1dGUgZm9yIFNlYSBSZXNlYXJjaCwgTFUgLSBMZWlkZW4gVW5pdmVyc2l0eSwgVVUgLSBVdHJlY2h0IFVuaXZlcnNpdHksIFVBIC0gVW5pdmVyc2l0eSBvZiBBbXN0ZXJkYW0sIEVVUiAtIEVyYXNtdXMgVW5pdmVyc2l0eSBSb3R0ZXJkYW0sIEQgLSBEZWx0YXJlcywgSUhFIC0gSUhFIERlbGZ0IEluc3RpdHV0ZSBmb3IgV2F0ZXIgRWR1Y2F0aW9uLCBSVSAtIFJhZGJvdWQgVW5pdmVyc2l0eSwgUlVHIC0gVW5pdmVyc2l0eSBvZiBHcm9uaW5nZW4sIE1VIC0gTWFhc3RyaWNodCBVbml2ZXJzaXR5LCBVQSAocykgLSBVbml2ZXJzaXR5IG9mIEFtc3RlcmRhbSBTdHVkZW50c10iLCAxODUpLCBsaW5lPTkuNzUpDQpwNiA8LSBhcnJvd3MoeDA9ZGF0YS5ncm91cC5xdWFudCR2NTAsIHkwPWRhdGEuZ3JvdXAucXVhbnQkZDI1LA0KICAgICAgICAgeDE9ZGF0YS5ncm91cC5xdWFudCR2NTAsIHkxPWRhdGEuZ3JvdXAucXVhbnQkZDc1LA0KICAgICAgICAgY29kZT0zLCBhbmdsZT05MCwgbGVuZ3RoPTAuMSwgY29sPSJncmF5IikgIyBxdWFudGlsZXMgb2YgZG93bmxvYWRzDQpwNiA8LSBhcnJvd3MoeDA9ZGF0YS5ncm91cC5xdWFudCR2MjUsIHkwPWRhdGEuZ3JvdXAucXVhbnQkZDUwLA0KICAgICAgICAgeDE9ZGF0YS5ncm91cC5xdWFudCR2NzUsIHkxPWRhdGEuZ3JvdXAucXVhbnQkZDUwLA0KICAgICAgICAgY29kZT0zLCBhbmdsZT05MCwgbGVuZ3RoPTAuMSwgY29sPSJncmF5IikgIyBxdWFudGlsZXMgb2Ygdmlld3MNCnA2IDwtIHRleHQoZGF0YS5ncm91cC5xdWFudCR2NTAsIGRhdGEuZ3JvdXAucXVhbnQkZDUwLCBsYWJlbHM9ZGF0YS5ncm91cC5xdWFudCRuYW1lX2lkLCBjZXg9IDEsIHBvcz00KSAjIGFkZGluZyBzaG9ydC1sYWJlbC9rZXkNCg0KcDYNCmRldi5vZmYoKQ0KDQpgYGANCg0K