By Angus Ball

Welcome to learn how to make relative abundance graphs! Some say my codes actually pretty good so this should be a treat! This first graph will be a generic bar graph with all samples.

I’m working with FUNGuild data in this example. (This explains why I have more options to choose from in my taxVal)

First things first get some packages

library(phyloseq)
library(speedyseq)
library(ggplot2)
library(tidyverse)

Second choose what you want to plot!

taxVal <- "Phylum" #What taxa do you want
samdata1 <- "Location" #First sample metadata to facet_grid
samdata2 <- "Layer" #second sample metadata to facet_grid
#filterVal <- "p__Ascomycota"
#filterVal <- ""
filepathing <- "C:\\Users\\angus\\OneDrive - UNBC\\Angus Ball\\Lab work\\Bioinformatics\\Kenzies Data\\test.pdf" #this is where you want your plots to be stored on your computer, remember it has to change for each graph!, don't make fun of my folder structure...

This is where the glory of this code really starts, you NEVER need to modify code below this and it should just work! (The obvious exception being the ggplot function which you may want to modify for your own pastel driven dreams!)

Eitherway, you will put whatever taxa or FUNGuild column name in taxVal youd like What are your options? try:

physeq_Key %>% rank_names
 [1] "Kingdom"           "Phylum"            "Class"             "Order"             "Family"           
 [6] "Genus"             "Species"           "taxon"             "guid"              "mbNumber"         
[11] "taxonomicLevel"    "trophicMode"       "guild"             "confidenceRanking" "growthForm"       
[16] "trait"             "notes"             "citationSource"   

Any of the above options are valid.

The same goes for sample data (samdata)

physeq_Key %>% sample_variables()
[1] "Location"  "Layer"     "Treatment" "Site"      "FullName" 

Any of these are valid choices!, They do matter which order you put them in, ie samdata1 will facet_grid the data before samdata2 but just run the script once or twice and it’ll make sense.

What about filterVal? Say you only wanted to plot the p__Ascomycota species in your graph? to do a bulk comparison of just them. Well uncomment filterVal and put p__Ascomycota in the quotes. This can be done with any species taxonomy or any information/subsample from funguild. PS. you’ll also have to uncomment a line of code before plotting. I’ll include a graph of what this looks like at the very end

Quick side point: wanna separate your data into separate graphs? Use this command to choose what samples you want based on metadata for this run and then change it later on

#physeq_Subset <- subset_samples(physeq_Key, Location != "Fallow")

This removes all the samples that are from Fallow land

What if you only wanted specific taxa associated with trophic mode?

#physeq_Subset<-subset_taxa(physeq_Key, trophicMode == "Symbiotroph")

Note that these are sending to physeq_test and not _key or _Bar as expected in a second, this is to make sure you don’t accidentally throw these commands in your code and destroy something

And honestly, you can just run the code from here on out and it’ll just work, but for the fool who wants to understand~…

This command physically changes the column names of your phyloseq object, The names that are choosen are based off of what input you provided above. Notice that the first command reads from physeq_Key, if you wanted to subset any of your samples as above, replace this with physeq_Subset

physeq_Bar<- rename_tax_table(physeq_Key, TaxChoice = taxVal)
physeq_Bar <- rename_sample_data(physeq_Bar, SamChoice1 = samdata1)
physeq_Bar <- rename_sample_data(physeq_Bar, SamChoice2 = samdata2)

This commander transforms our samples to relative abundance Note this isn’t clr transformed, but I think thanks okay, we are accounting for compositionality with relative abundance sorta, and we arent using this data for statistical tests, just visualization.

physeq_rel = transform_sample_counts(physeq_Bar, function(x) x/sum(x)*100)

we first con(glom)erate the taxa to a specific taxrank dictated by your choice above, NArm means NA remove, don’t remove your NAs!!!! this looses data especially if you are using environmental data were not every microbe in the world has been categorized. The other two commands are modifying the tables so they can be read by ggplot easily

glom <- tax_glom(physeq_rel, taxrank = 'TaxChoice', NArm = FALSE)
physeq_melt <- psmelt(glom)
physeq_melt$TaxChoice <- as.character(physeq_melt$TaxChoice)

This code takes the groups we’ve chosen before and figures out the median abundance of each OTU in each sample

physeq_melt <- physeq_melt %>%
  group_by(SamChoice1, SamChoice2,TaxChoice) %>%
  mutate(median=median(Abundance))

Which is immediately useful, because we remove OTUs that are less than 1% in a sample. They are still represented in the graph, but they don’t get their own category. You Don’t Have to do this, or you can make the value higher, its just to make the graph a bit more readable

keep <- unique(physeq_melt$TaxChoice[physeq_melt$median > 1])
physeq_melt$TaxChoice[!(physeq_melt$TaxChoice %in% keep)] <- "< 1%"

Similar command as above, but summarizes all the information

physeq_melt_sum <- physeq_melt %>%
  group_by(Sample,SamChoice1, SamChoice2,TaxChoice) %>%
  summarise(Abundance=sum(Abundance))
`summarise()` has grouped output by 'Sample', 'SamChoice1', 'SamChoice2'. You can override using the `.groups` argument.

Hey I said filterVal would come up again! Uncomment this code if you want to apply the filter!

#physeq_melt_sum <- filter(physeq_melt_sum, TaxChoice == filterVal)

Lets plot it!

ggplot(physeq_melt_sum, aes(x = Sample, y = Abundance, fill = TaxChoice)) + 
  geom_bar(stat = "identity", aes(fill=TaxChoice)) + 
  labs(x="", y="%") +
  facet_wrap(SamChoice1~SamChoice2, scales= "free_x", nrow=1) +
  scale_fill_discrete(name = taxVal)+
  theme_classic() + 
  #scale_x_discrete(guide=guide_axis(n.dodge=3))+
  theme(strip.background = element_blank(), 
        axis.text.x.bottom = element_text(angle = -90))        
ggsave(filepathing)
Saving 7.29 x 4.5 in image

You’ll have to decide what your ggsave location is.

Looking at the graph, one of the important, non-normal things added is scale_fill_discrete, we need this to rename the the legend because of the object, taxVal, shenanigans earlier.

Looks great right? well, sorta I guess, see how the sample type text is crumpled on the screen, well try expanding your plots view in R to expand the spacing in the sample (you can also do this in ggsave by setting sizes), or playing around with the n.dodge function I’ve commented out in the gg plot formula (I dont like the look of it tho). You can also plot some samples by themselves to manage images.

Hey hey HEY, whats NA, and why are there phylum’s under <1%? NA stands for not available, and are reflective of sequences that exist but we havent categorized them into species yet. but there being phylum’s under <1%?, rerun the same code without the <1% modification.

ggplot(physeq_melt_sum, aes(x = Sample, y = Abundance, fill = TaxChoice)) + 
  geom_bar(stat = "identity", aes(fill=TaxChoice)) + 
  labs(x="", y="%") +
  facet_wrap(SamChoice1~SamChoice2, scales= "free_x", nrow=1) +
  scale_fill_discrete(name = taxVal)+
  theme_classic() + 
  #scale_x_discrete(guide=guide_axis(n.dodge=3))+
  theme(strip.background = element_blank(), 
        axis.text.x.bottom = element_text(angle = -90)) 

what the frick, why are there anthropod samples in my fungal data!

This is a good example of why you must always review your data! and relative abundance plots are a good way to do it! I’m going to go back and remove these phylums so they dont affect downstream analysis

As promised heres a graph of only the Ascomycota samples

ggplot(physeq_melt_sum, aes(x = Sample, y = Abundance, fill = TaxChoice)) + 
  geom_bar(stat = "identity", aes(fill=TaxChoice)) + 
  labs(x="", y="%") +
  facet_wrap(SamChoice1~SamChoice2, scales= "free_x", nrow=1) +
  scale_fill_discrete(name = taxVal)+
  theme_classic() + 
  #scale_x_discrete(guide=guide_axis(n.dodge=3))+
  theme(strip.background = element_blank(), 
        axis.text.x.bottom = element_text(angle = -90)) 

LS0tDQp0aXRsZTogIlJlbGF0aXZlIEFidW5kYW5jZSBHcmFwaHMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpCeSBBbmd1cyBCYWxsDQoNCg0KV2VsY29tZSB0byBsZWFybiBob3cgdG8gbWFrZSByZWxhdGl2ZSBhYnVuZGFuY2UgZ3JhcGhzIQ0KU29tZSBzYXkgbXkgY29kZXMgYWN0dWFsbHkgcHJldHR5IGdvb2Qgc28gdGhpcyBzaG91bGQgYmUgYSB0cmVhdCENClRoaXMgZmlyc3QgZ3JhcGggd2lsbCBiZSBhIGdlbmVyaWMgYmFyIGdyYXBoIHdpdGggYWxsIHNhbXBsZXMuDQoNCkknbSB3b3JraW5nIHdpdGggRlVOR3VpbGQgZGF0YSBpbiB0aGlzIGV4YW1wbGUuIChUaGlzIGV4cGxhaW5zIHdoeSBJIGhhdmUgbW9yZSBvcHRpb25zIHRvIGNob29zZSBmcm9tIGluIG15IHRheFZhbCkNCg0KDQpGaXJzdCB0aGluZ3MgZmlyc3QgZ2V0IHNvbWUgcGFja2FnZXMNCmBgYHtyLCBlY2hvID0gVCwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkocGh5bG9zZXEpDQpsaWJyYXJ5KHNwZWVkeXNlcSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClNlY29uZCBjaG9vc2Ugd2hhdCB5b3Ugd2FudCB0byBwbG90IQ0KYGBge3J9DQp0YXhWYWwgPC0gIlBoeWx1bSIgI1doYXQgdGF4YSBsZXZlbCBkbyB5b3UgY2FyZSBhYm91dA0Kc2FtZGF0YTEgPC0gIkxvY2F0aW9uIiAjRmlyc3Qgc2FtcGxlIG1ldGFkYXRhIHRvIGZhY2V0X2dyaWQNCnNhbWRhdGEyIDwtICJMYXllciIgI3NlY29uZCBzYW1wbGUgbWV0YWRhdGEgdG8gZmFjZXRfZ3JpZA0KI2ZpbHRlclZhbCA8LSAicF9fQXNjb215Y290YSIgI2ZpbHRlciB0byBvbmx5IHRoaXMgZ3JvdXAsIGlmIHlvdSB3YW50DQpmaWxlcGF0aGluZyA8LSAiQzpcXFVzZXJzXFxhbmd1c1xcT25lRHJpdmUgLSBVTkJDXFxBbmd1cyBCYWxsXFxMYWIgd29ya1xcQmlvaW5mb3JtYXRpY3NcXEtlbnppZXMgRGF0YVxcdGVzdC5wZGYiICN0aGlzIGlzIHdoZXJlIHlvdSB3YW50IHlvdXIgcGxvdHMgdG8gYmUgc3RvcmVkIG9uIHlvdXIgY29tcHV0ZXIsIHJlbWVtYmVyIGl0IGhhcyB0byBjaGFuZ2UgZm9yIGVhY2ggZ3JhcGghLCBkb24ndCBtYWtlIGZ1biBvZiBteSBmb2xkZXIgc3RydWN0dXJlLi4uDQpgYGANClRoaXMgaXMgd2hlcmUgdGhlIGdsb3J5IG9mIHRoaXMgY29kZSByZWFsbHkgc3RhcnRzLCB5b3UgTkVWRVIgbmVlZCB0byBtb2RpZnkgY29kZSBiZWxvdyB0aGlzIGFuZCBpdCBzaG91bGQganVzdCB3b3JrISAoVGhlIG9idmlvdXMgZXhjZXB0aW9uIGJlaW5nIHRoZSBnZ3Bsb3QgZnVuY3Rpb24gd2hpY2ggeW91IG1heSB3YW50IHRvIG1vZGlmeSBmb3IgeW91ciBvd24gcGFzdGVsIGRyaXZlbiBkcmVhbXMhKQ0KDQpFaXRoZXJ3YXksIHlvdSB3aWxsIHB1dCB3aGF0ZXZlciB0YXhhIG9yIEZVTkd1aWxkIGNvbHVtbiBuYW1lIGluIHRheFZhbCB5b3VkIGxpa2UNCldoYXQgYXJlIHlvdXIgb3B0aW9ucz8gdHJ5Og0KYGBge3J9DQpwaHlzZXFfS2V5ICU+JSByYW5rX25hbWVzDQpgYGANCkFueSBvZiB0aGUgYWJvdmUgb3B0aW9ucyBhcmUgdmFsaWQuDQoNClRoZSBzYW1lIGdvZXMgZm9yIHNhbXBsZSBkYXRhIChzYW1kYXRhKQ0KYGBge3J9DQpwaHlzZXFfS2V5ICU+JSBzYW1wbGVfdmFyaWFibGVzKCkNCmBgYA0KQW55IG9mIHRoZXNlIGFyZSB2YWxpZCBjaG9pY2VzISwgVGhleSBkbyBtYXR0ZXIgd2hpY2ggb3JkZXIgeW91IHB1dCB0aGVtIGluLCBpZSBzYW1kYXRhMSB3aWxsIGZhY2V0X2dyaWQgdGhlIGRhdGEgYmVmb3JlIHNhbWRhdGEyIGJ1dCBqdXN0IHJ1biB0aGUgc2NyaXB0IG9uY2Ugb3IgdHdpY2UgYW5kIGl0J2xsIG1ha2Ugc2Vuc2UuDQoNCldoYXQgYWJvdXQgZmlsdGVyVmFsPyBTYXkgeW91IG9ubHkgd2FudGVkIHRvIHBsb3QgdGhlIHBfX0FzY29teWNvdGEgc3BlY2llcyBpbiB5b3VyIGdyYXBoPyB0byBkbyBhIGJ1bGsgY29tcGFyaXNvbiBvZiBqdXN0IHRoZW0uIFdlbGwgdW5jb21tZW50IGZpbHRlclZhbCBhbmQgcHV0IHBfX0FzY29teWNvdGEgaW4gdGhlIHF1b3Rlcy4gVGhpcyBjYW4gYmUgZG9uZSB3aXRoIGFueSBzcGVjaWVzIHRheG9ub215IG9yIGFueSBpbmZvcm1hdGlvbi9zdWJzYW1wbGUgZnJvbSBmdW5ndWlsZC4gUFMuIHlvdSdsbCBhbHNvIGhhdmUgdG8gdW5jb21tZW50IGEgbGluZSBvZiBjb2RlIGJlZm9yZSBwbG90dGluZy4gSSdsbCBpbmNsdWRlIGEgZ3JhcGggb2Ygd2hhdCB0aGlzIGxvb2tzIGxpa2UgYXQgdGhlIHZlcnkgZW5kDQoNCg0KDQoNClF1aWNrIHNpZGUgcG9pbnQ6IHdhbm5hIHNlcGFyYXRlIHlvdXIgZGF0YSBpbnRvIHNlcGFyYXRlIGdyYXBocz8gVXNlIHRoaXMgY29tbWFuZCB0byBjaG9vc2Ugd2hhdCBzYW1wbGVzIHlvdSB3YW50IGJhc2VkIG9uIG1ldGFkYXRhIGZvciB0aGlzIHJ1biBhbmQgdGhlbiBjaGFuZ2UgaXQgbGF0ZXIgb24NCg0KYGBge3J9DQojcGh5c2VxX1N1YnNldCA8LSBzdWJzZXRfc2FtcGxlcyhwaHlzZXFfS2V5LCBMb2NhdGlvbiAhPSAiRmFsbG93IikNCmBgYA0KVGhpcyByZW1vdmVzIGFsbCB0aGUgc2FtcGxlcyB0aGF0IGFyZSBmcm9tIEZhbGxvdyBsYW5kDQoNCldoYXQgaWYgeW91IG9ubHkgd2FudGVkIHNwZWNpZmljIHRheGEgYXNzb2NpYXRlZCB3aXRoIHRyb3BoaWMgbW9kZT8NCg0KYGBge3J9DQojcGh5c2VxX1N1YnNldDwtc3Vic2V0X3RheGEocGh5c2VxX0tleSwgdHJvcGhpY01vZGUgPT0gIlN5bWJpb3Ryb3BoIikNCmBgYA0KDQpOb3RlIHRoYXQgdGhlc2UgYXJlIHNlbmRpbmcgdG8gcGh5c2VxX3Rlc3QgYW5kIG5vdCBfa2V5IG9yIF9CYXIgYXMgZXhwZWN0ZWQgaW4gYSBzZWNvbmQsIHRoaXMgaXMgdG8gbWFrZSBzdXJlIHlvdSBkb24ndCBhY2NpZGVudGFsbHkgdGhyb3cgdGhlc2UgY29tbWFuZHMgaW4geW91ciBjb2RlIGFuZCBkZXN0cm95IHNvbWV0aGluZw0KDQoNCg0KDQoNCkFuZCBob25lc3RseSwgeW91IGNhbiBqdXN0IHJ1biB0aGUgY29kZSBmcm9tIGhlcmUgb24gb3V0IGFuZCBpdCdsbCBqdXN0IHdvcmssIGJ1dCBmb3IgdGhlIGZvb2wgd2hvIHdhbnRzIHRvIHVuZGVyc3RhbmR+Li4uDQoNClRoaXMgY29tbWFuZCBwaHlzaWNhbGx5IGNoYW5nZXMgdGhlIGNvbHVtbiBuYW1lcyBvZiB5b3VyIHBoeWxvc2VxIG9iamVjdCwgVGhlIG5hbWVzIHRoYXQgYXJlIGNob29zZW4gYXJlIGJhc2VkIG9mZiBvZiB3aGF0IGlucHV0IHlvdSBwcm92aWRlZCBhYm92ZS4gTm90aWNlIHRoYXQgdGhlIGZpcnN0IGNvbW1hbmQgcmVhZHMgZnJvbSBwaHlzZXFfS2V5LCBpZiB5b3Ugd2FudGVkIHRvIHN1YnNldCBhbnkgb2YgeW91ciBzYW1wbGVzIGFzIGFib3ZlLCByZXBsYWNlIHRoaXMgd2l0aCBwaHlzZXFfU3Vic2V0DQpgYGB7cn0NCnBoeXNlcV9CYXI8LSByZW5hbWVfdGF4X3RhYmxlKHBoeXNlcV9LZXksIFRheENob2ljZSA9IHRheFZhbCkNCnBoeXNlcV9CYXIgPC0gcmVuYW1lX3NhbXBsZV9kYXRhKHBoeXNlcV9CYXIsIFNhbUNob2ljZTEgPSBzYW1kYXRhMSkNCnBoeXNlcV9CYXIgPC0gcmVuYW1lX3NhbXBsZV9kYXRhKHBoeXNlcV9CYXIsIFNhbUNob2ljZTIgPSBzYW1kYXRhMikNCmBgYA0KDQoNCg0KDQpUaGlzIGNvbW1hbmRlciB0cmFuc2Zvcm1zIG91ciBzYW1wbGVzIHRvIHJlbGF0aXZlIGFidW5kYW5jZSANCk5vdGUgdGhpcyBpc24ndCBjbHIgdHJhbnNmb3JtZWQsIGJ1dCBJIHRoaW5rIHRoYW5rcyBva2F5LCB3ZSBhcmUgYWNjb3VudGluZyBmb3IgY29tcG9zaXRpb25hbGl0eSB3aXRoIHJlbGF0aXZlIGFidW5kYW5jZSBzb3J0YSwgYW5kIHdlIGFyZW50IHVzaW5nIHRoaXMgZGF0YSBmb3Igc3RhdGlzdGljYWwgdGVzdHMsIGp1c3QgdmlzdWFsaXphdGlvbi4NCmBgYHtyfQ0KcGh5c2VxX3JlbCA9IHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKHBoeXNlcV9CYXIsIGZ1bmN0aW9uKHgpIHgvc3VtKHgpKjEwMCkNCmBgYA0KDQp3ZSBmaXJzdCBjb24oZ2xvbSllcmF0ZSB0aGUgdGF4YSB0byBhIHNwZWNpZmljIHRheHJhbmsgZGljdGF0ZWQgYnkgeW91ciBjaG9pY2UgYWJvdmUsIE5Bcm0gbWVhbnMgTkEgcmVtb3ZlLCBkb24ndCByZW1vdmUgeW91ciBOQXMhISEhIHRoaXMgbG9vc2VzIGRhdGEgZXNwZWNpYWxseSBpZiB5b3UgYXJlIHVzaW5nIGVudmlyb25tZW50YWwgZGF0YSB3ZXJlIG5vdCBldmVyeSBtaWNyb2JlIGluIHRoZSB3b3JsZCBoYXMgYmVlbiBjYXRlZ29yaXplZC4NClRoZSBvdGhlciB0d28gY29tbWFuZHMgYXJlIG1vZGlmeWluZyB0aGUgdGFibGVzIHNvIHRoZXkgY2FuIGJlIHJlYWQgYnkgZ2dwbG90IGVhc2lseQ0KYGBge3J9DQpnbG9tIDwtIHRheF9nbG9tKHBoeXNlcV9yZWwsIHRheHJhbmsgPSAnVGF4Q2hvaWNlJywgTkFybSA9IEZBTFNFKQ0KcGh5c2VxX21lbHQgPC0gcHNtZWx0KGdsb20pDQpwaHlzZXFfbWVsdCRUYXhDaG9pY2UgPC0gYXMuY2hhcmFjdGVyKHBoeXNlcV9tZWx0JFRheENob2ljZSkNCmBgYA0KDQpUaGlzIGNvZGUgdGFrZXMgdGhlIGdyb3VwcyB3ZSd2ZSBjaG9zZW4gYmVmb3JlIGFuZCBmaWd1cmVzIG91dCB0aGUgbWVkaWFuIGFidW5kYW5jZSBvZiBlYWNoIE9UVSBpbiBlYWNoIHNhbXBsZSANCmBgYHtyfQ0KcGh5c2VxX21lbHQgPC0gcGh5c2VxX21lbHQgJT4lDQogIGdyb3VwX2J5KFNhbUNob2ljZTEsIFNhbUNob2ljZTIsVGF4Q2hvaWNlKSAlPiUNCiAgbXV0YXRlKG1lZGlhbj1tZWRpYW4oQWJ1bmRhbmNlKSkNCmBgYA0KDQpXaGljaCBpcyBpbW1lZGlhdGVseSB1c2VmdWwsIGJlY2F1c2Ugd2UgcmVtb3ZlIE9UVXMgdGhhdCBhcmUgbGVzcyB0aGFuIDElIGluIGEgc2FtcGxlLiBUaGV5IGFyZSBzdGlsbCByZXByZXNlbnRlZCBpbiB0aGUgZ3JhcGgsIGJ1dCB0aGV5IGRvbid0IGdldCB0aGVpciBvd24gY2F0ZWdvcnkuIFlvdSBEb24ndCBIYXZlIHRvIGRvIHRoaXMsIG9yIHlvdSBjYW4gbWFrZSB0aGUgdmFsdWUgaGlnaGVyLCBpdHMganVzdCB0byBtYWtlIHRoZSBncmFwaCBhIGJpdCBtb3JlIHJlYWRhYmxlDQoNCmBgYHtyfQ0Ka2VlcCA8LSB1bmlxdWUocGh5c2VxX21lbHQkVGF4Q2hvaWNlW3BoeXNlcV9tZWx0JG1lZGlhbiA+IDFdKQ0KcGh5c2VxX21lbHQkVGF4Q2hvaWNlWyEocGh5c2VxX21lbHQkVGF4Q2hvaWNlICVpbiUga2VlcCldIDwtICI8IDElIg0KYGBgDQoNClNpbWlsYXIgY29tbWFuZCBhcyBhYm92ZSwgYnV0IHN1bW1hcml6ZXMgYWxsIHRoZSBpbmZvcm1hdGlvbg0KYGBge3J9DQpwaHlzZXFfbWVsdF9zdW0gPC0gcGh5c2VxX21lbHQgJT4lDQogIGdyb3VwX2J5KFNhbXBsZSxTYW1DaG9pY2UxLCBTYW1DaG9pY2UyLFRheENob2ljZSkgJT4lDQogIHN1bW1hcmlzZShBYnVuZGFuY2U9c3VtKEFidW5kYW5jZSkpDQpgYGANCg0KSGV5IEkgc2FpZCBmaWx0ZXJWYWwgd291bGQgY29tZSB1cCBhZ2FpbiENClVuY29tbWVudCB0aGlzIGNvZGUgaWYgeW91IHdhbnQgdG8gYXBwbHkgdGhlIGZpbHRlciENCmBgYHtyfQ0KI3BoeXNlcV9tZWx0X3N1bSA8LSBmaWx0ZXIocGh5c2VxX21lbHRfc3VtLCBUYXhDaG9pY2UgPT0gZmlsdGVyVmFsKQ0KYGBgDQoNCg0KDQpMZXRzIHBsb3QgaXQhDQoNCmBgYHtyfQ0KZ2dwbG90KHBoeXNlcV9tZWx0X3N1bSwgYWVzKHggPSBTYW1wbGUsIHkgPSBBYnVuZGFuY2UsIGZpbGwgPSBUYXhDaG9pY2UpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWVzKGZpbGw9VGF4Q2hvaWNlKSkgKyANCiAgbGFicyh4PSIiLCB5PSIlIikgKw0KICBmYWNldF93cmFwKFNhbUNob2ljZTF+U2FtQ2hvaWNlMiwgc2NhbGVzPSAiZnJlZV94IiwgbnJvdz0xKSArDQogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9IHRheFZhbCkrDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICAjc2NhbGVfeF9kaXNjcmV0ZShndWlkZT1ndWlkZV9heGlzKG4uZG9kZ2U9MykpKw0KICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgYXhpcy50ZXh0LnguYm90dG9tID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gLTkwKSkgICAgICAgIA0KZ2dzYXZlKGZpbGVwYXRoaW5nKQ0KDQpgYGANCg0KDQpZb3UnbGwgaGF2ZSB0byBkZWNpZGUgd2hhdCB5b3VyIGdnc2F2ZSBsb2NhdGlvbiBpcy4NCg0KTG9va2luZyBhdCB0aGUgZ3JhcGgsIG9uZSBvZiB0aGUgaW1wb3J0YW50LCBub24tbm9ybWFsIHRoaW5ncyBhZGRlZCBpcyBzY2FsZV9maWxsX2Rpc2NyZXRlLCB3ZSBuZWVkIHRoaXMgdG8gcmVuYW1lIHRoZSB0aGUgbGVnZW5kIGJlY2F1c2Ugb2YgdGhlIG9iamVjdCwgdGF4VmFsLCBzaGVuYW5pZ2FucyBlYXJsaWVyLiAgDQoNCg0KDQpMb29rcyBncmVhdCByaWdodD8gd2VsbCwgc29ydGEgSSBndWVzcywgc2VlIGhvdyB0aGUgc2FtcGxlIHR5cGUgdGV4dCBpcyBjcnVtcGxlZCBvbiB0aGUgc2NyZWVuLCB3ZWxsIHRyeSBleHBhbmRpbmcgeW91ciBwbG90cyB2aWV3IGluIFIgdG8gZXhwYW5kIHRoZSBzcGFjaW5nIGluIHRoZSBzYW1wbGUgKHlvdSBjYW4gYWxzbyBkbyB0aGlzIGluIGdnc2F2ZSBieSBzZXR0aW5nIHNpemVzKSwgb3IgcGxheWluZyBhcm91bmQgd2l0aCB0aGUgbi5kb2RnZSBmdW5jdGlvbiBJJ3ZlIGNvbW1lbnRlZCBvdXQgaW4gdGhlIGdnIHBsb3QgZm9ybXVsYSAoSSBkb250IGxpa2UgdGhlIGxvb2sgb2YgaXQgdGhvKS4gWW91IGNhbiBhbHNvIHBsb3Qgc29tZSBzYW1wbGVzIGJ5IHRoZW1zZWx2ZXMgdG8gbWFuYWdlIGltYWdlcy4gDQoNCg0KSGV5IGhleSBIRVksIHdoYXRzIE5BLCBhbmQgd2h5IGFyZSB0aGVyZSBwaHlsdW0ncyB1bmRlciA8MSU/DQpOQSBzdGFuZHMgZm9yIG5vdCBhdmFpbGFibGUsIGFuZCBhcmUgcmVmbGVjdGl2ZSBvZiBzZXF1ZW5jZXMgdGhhdCBleGlzdCBidXQgd2UgaGF2ZW50IGNhdGVnb3JpemVkIHRoZW0gaW50byBzcGVjaWVzIHlldC4gDQpidXQgdGhlcmUgYmVpbmcgcGh5bHVtJ3MgdW5kZXIgPDElPywgcmVydW4gdGhlIHNhbWUgY29kZSB3aXRob3V0IHRoZSA8MSUgbW9kaWZpY2F0aW9uLiANCg0KDQpgYGB7cn0NCmdncGxvdChwaHlzZXFfbWVsdF9zdW0sIGFlcyh4ID0gU2FtcGxlLCB5ID0gQWJ1bmRhbmNlLCBmaWxsID0gVGF4Q2hvaWNlKSkgKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGFlcyhmaWxsPVRheENob2ljZSkpICsgDQogIGxhYnMoeD0iIiwgeT0iJSIpICsNCiAgZmFjZXRfd3JhcChTYW1DaG9pY2UxflNhbUNob2ljZTIsIHNjYWxlcz0gImZyZWVfeCIsIG5yb3c9MSkgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSB0YXhWYWwpKw0KICB0aGVtZV9jbGFzc2ljKCkgKyANCiAgI3NjYWxlX3hfZGlzY3JldGUoZ3VpZGU9Z3VpZGVfYXhpcyhuLmRvZGdlPTMpKSsNCiAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgIGF4aXMudGV4dC54LmJvdHRvbSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IC05MCkpIA0KYGBgDQoNCg0Kd2hhdCB0aGUgZnJpY2ssIHdoeSBhcmUgdGhlcmUgYW50aHJvcG9kIHNhbXBsZXMgaW4gbXkgZnVuZ2FsIGRhdGEhDQoNClRoaXMgaXMgYSBnb29kIGV4YW1wbGUgb2Ygd2h5IHlvdSBtdXN0IGFsd2F5cyByZXZpZXcgeW91ciBkYXRhISBhbmQgcmVsYXRpdmUgYWJ1bmRhbmNlIHBsb3RzIGFyZSBhIGdvb2Qgd2F5IHRvIGRvIGl0ISBJJ20gZ29pbmcgdG8gZ28gYmFjayBhbmQgcmVtb3ZlIHRoZXNlIHBoeWx1bXMgc28gdGhleSBkb250IGFmZmVjdCBkb3duc3RyZWFtIGFuYWx5c2lzDQoNCg0KDQoNCkFzIHByb21pc2VkIGhlcmVzIGEgZ3JhcGggb2Ygb25seSB0aGUgQXNjb215Y290YSBzYW1wbGVzDQoNCmBgYHtyfQ0KZ2dwbG90KHBoeXNlcV9tZWx0X3N1bSwgYWVzKHggPSBTYW1wbGUsIHkgPSBBYnVuZGFuY2UsIGZpbGwgPSBUYXhDaG9pY2UpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWVzKGZpbGw9VGF4Q2hvaWNlKSkgKyANCiAgbGFicyh4PSIiLCB5PSIlIikgKw0KICBmYWNldF93cmFwKFNhbUNob2ljZTF+U2FtQ2hvaWNlMiwgc2NhbGVzPSAiZnJlZV94IiwgbnJvdz0xKSArDQogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9IHRheFZhbCkrDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICAjc2NhbGVfeF9kaXNjcmV0ZShndWlkZT1ndWlkZV9heGlzKG4uZG9kZ2U9MykpKw0KICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgYXhpcy50ZXh0LnguYm90dG9tID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gLTkwKSkgDQpgYGANCg==