Exercise 1 - Customising simple plots
Plotting a baby’s growth rate.
read.delim("weight_chart.txt") -> weight.chart
weight.chart
We’re going to use a normal scatterplot to plot the two values against each other. We’ll add a bunch of customisation by modifying the parameters we pass.
plot(
weight.chart$Age,
weight.chart$Weight,
type="b",
pch=15,
cex=1.5,
lwd=2,
ylim=c(2,10),
xlab="Age (months)",
ylab="Weight (kg)",
main="Weigh gain during early infant development"
)
Now we can read in the feature counts data to plot a barplot of the counts for different types of features.
read.delim("feature_counts.txt") -> feature.counts
feature.counts
We’re going to draw a customised barplot from this data.
par(mar=c(5,12,4,2))
barplot(
feature.counts$Count,
horiz = TRUE,
xlab="Number of feature instances",
names.arg=feature.counts$Feature,
main="Number of different feature types found",
las=1
)
Finally we want to make a biased random dataset which we can plot out as a histogram.
hist.data <- c(rnorm(10000),rnorm(10000)+4)
hist(hist.data,breaks=60,main="Bimodal data")
Exercise 2 - Using colour
We want to plot out the male/female count data as a barplot and change the colour in different ways.
read.delim("male_female_counts.txt") -> male.female
male.female
Now we can plot this. The barplot just needs the count data, and we’ll specifically need to add the sample names as labels. We need to make the labels small so they all fit (we could also have turned the plot around the other way).
barplot(
male.female$Count,
names.arg = male.female$Sample,
cex.names = 0.5
)
To add a different colour to each point (which isn’t generally a great idea!) we can use the rainbow function to generate a set of colours. We simply need to tell the function how many colours to generate, which in this case is the number of rows in the data frame.
rainbow(nrow(male.female))
[1] "#FF0000FF" "#FF9900FF" "#CCFF00FF" "#33FF00FF" "#00FF66FF" "#00FFFFFF" "#0066FFFF"
[8] "#3300FFFF" "#CC00FFFF" "#FF0099FF"
We can then pass this vector as the col paramter when drawing the barplot.
barplot(
male.female$Count,
names.arg = male.female$Sample,
cex.names = 0.5,
col=rainbow(nrow(male.female))
)
Now we want to make the males and females different colours. We can’t just pass the Sample column as colour since all of the sample names are different (even though they contain Male and Female). In this specific case, because males and females alternate we can just pass a fixed 2 colour vector.
barplot(
male.female$Count,
names.arg = male.female$Sample,
cex.names = 0.5,
col=c("blue2","red2")
)
If we’d wanted a more generic solution then we’d need to use some of the text mainipulation techniques shown in the advanced R course, specifically we can remove the D1 etc from the start of each string to leave us with a Male/Female split which we can use as a colour directly.
substring(male.female$Sample,4)
[1] "Male" "Female" "Male" "Female" "Male" "Female" "Male" "Female" "Male" "Female"
barplot(
male.female$Count,
names.arg = male.female$Sample,
cex.names = 0.5,
col=as.factor(substring(male.female$Sample,4))
)
We next want to use this type of categorical colour definition to highlight specific points in a scatteprlot. We can start by reading in the data.
read.delim("up_down_expression.txt") -> up.down
up.down
You can see that the data to plot are the Condition1 and Condition2 columns, and the categories are in the State column.
We can start by doing a simple uncoloured plot.
plot(
up.down$Condition1,
up.down$Condition2,
pch=19
)
Now we can colour by the State column.
plot(
up.down$Condition1,
up.down$Condition2,
pch=19,
col=up.down$State
)
We can see that 3 different groups have been coloured, but we need to understand how. We can figure this out by looking at the levels of the state column and the colour vector contained in the current system palette.
palette()
[1] "black" "red" "green3" "blue" "cyan" "magenta" "yellow" "gray"
We can see that there is a direct mapping between the two vectors where down=black (position 1), unchanging=red and up=green. If we want to use our own colours then we need to set the palette to our colour choice in the order down,unchanging,up. In this case I’ve also added a legend so we can see which colour is which.
palette(c("blue2","grey","red2"))
plot(
up.down$Condition1,
up.down$Condition2,
pch=19,
col=up.down$State
)
legend("topleft",levels(up.down$State),fill=palette()[1:3])
Finally we are going to look at the use of quantitative colour. We’re going to use this to plot 3 variables on a single plot.
We can load the data first.
read.delim("expression_methylation.txt") -> expr.meth
expr.meth
We want to plot the promoter methylation against the gene methylation and colour by the expression. We can start by doing the uncoloured plot.
plot(
expr.meth$promoter.meth,
expr.meth$gene.meth,
pch=19
)
For the colouring we’re going to use a new function which has been provided to us. We can install this into the script first.
map.colours <- function (values,palette) {
range <- range(values)
proportion <- ((values-range[1])/(range[2]-range[1]))
index <- round ((length(palette)-1)*proportion)+1
return (palette[index])
}
This function will generate a vector of colours to match a quantitative vector we provide (the expression values in this case). We can then pass this as the colour vector to the plot and thereby colour the whole thing.
We need two pieces of data to run this - a vector of values (the expression column from our data frame), and a vector of colours in some kind of order from which to select.
To make our reference colour vector we’re going to make a colourRampPallete. This is a built in function for generating colour series.
ColourRampPalette needs a vector of reference colours and it will interpolte between these. In our plot we’re just going to run between grey and red.
The initial call to colorRampPalette returns us a function which can generate sets of colours.
colorRampPalette(c("grey","red2"))
function (n)
{
x <- ramp(seq.int(0, 1, length.out = n))
if (ncol(x) == 4L)
rgb(x[, 1L], x[, 2L], x[, 3L], x[, 4L], maxColorValue = 255)
else rgb(x[, 1L], x[, 2L], x[, 3L], maxColorValue = 255)
}
<bytecode: 0x00000000088e4a08>
<environment: 0x0000000008da55b0>
To generate the colour vector we need to then call this function, passing the number of colours to generate. We’ll make 100 colours this time.
colorRampPalette(c("grey","red2"))(100)
[1] "#BEBEBE" "#BEBCBC" "#BEBABA" "#BFB8B8" "#BFB6B6" "#C0B4B4" "#C0B2B2" "#C1B0B0" "#C1AEAE"
[10] "#C2ACAC" "#C2AAAA" "#C3A8A8" "#C3A6A6" "#C4A5A5" "#C4A3A3" "#C5A1A1" "#C59F9F" "#C69D9D"
[19] "#C69B9B" "#C79999" "#C79797" "#C89595" "#C89393" "#C99191" "#C98F8F" "#CA8E8E" "#CA8C8C"
[28] "#CB8A8A" "#CB8888" "#CC8686" "#CC8484" "#CD8282" "#CD8080" "#CE7E7E" "#CE7C7C" "#CE7A7A"
[37] "#CF7878" "#CF7676" "#D07575" "#D07373" "#D17171" "#D16F6F" "#D26D6D" "#D26B6B" "#D36969"
[46] "#D36767" "#D46565" "#D46363" "#D56161" "#D55F5F" "#D65E5E" "#D65C5C" "#D75A5A" "#D75858"
[55] "#D85656" "#D85454" "#D95252" "#D95050" "#DA4E4E" "#DA4C4C" "#DB4A4A" "#DB4848" "#DC4747"
[64] "#DC4545" "#DD4343" "#DD4141" "#DE3F3F" "#DE3D3D" "#DE3B3B" "#DF3939" "#DF3737" "#E03535"
[73] "#E03333" "#E13131" "#E12F2F" "#E22E2E" "#E22C2C" "#E32A2A" "#E32828" "#E42626" "#E42424"
[82] "#E52222" "#E52020" "#E61E1E" "#E61C1C" "#E71A1A" "#E71818" "#E81717" "#E81515" "#E91313"
[91] "#E91111" "#EA0F0F" "#EA0D0D" "#EB0B0B" "#EB0909" "#EC0707" "#EC0505" "#ED0303" "#ED0101"
[100] "#EE0000"
We’ve now got everything we need to call the map.colours function. This will generate a (large!) colour vector with all of the per-row colours we need for the plot.
head(custom.colours)
[1] "#D85656" "#DD4141" "#DE3D3D" "#D85454" "#DA4C4C" "#DF3737"
Finally,we can re-draw out plot using these custom colours to colour each point.
plot(
expr.meth$promoter.meth,
expr.meth$gene.meth,
pch=19,
col=custom.colours
)
Exercise 3 - Using overlays
We want to draw a line graph containing 3 lines for 3 different datasets. We’re going to do this using a base line plot for one of the datasets and then two overlays to add the other two.
read.delim("chromosome_position_data.txt") -> chr.pos
chr.pos
The 3 datasets we’re going to plot are WT, Mut1 and Mut2 and we’re going to plot them on the y axis agaist the position on the x axis.
We need to prepare a few things to make this work.
Firstly we need to get the full range of values in any of mut1, mut2 or wt so that we know we’ve got enough space on the y-axis to fit everything in.
max.value
[1] 68.15
We’re also going to use the RColourBrewer palette to provide the colours we’re going to use. We’ll take the first 3 colours in Set1.
library(RColorBrewer)
brewer.pal(3,"Set1") -> line.graph.colours
line.graph.colours
[1] "#E41A1C" "#377EB8" "#4DAF4A"
Now we can start to build the plot. We’ll start with the base plot which will be for the WT. Then we can add lines layers for the other two. Finally we’ll add a legend so we know what’s what.
plot(
chr.pos$Position,
chr.pos$WT,
type="l",
lwd=2,
col=line.graph.colours[1],
ylim=c(0,max.value),
las=1,
xlab="Chromosomal Position",
ylab="Value",
main="Values along a chromosome"
)
lines(chr.pos$Position,chr.pos$Mut1,lwd=2,col=line.graph.colours[2])
lines(chr.pos$Position,chr.pos$Mut2,lwd=2,col=line.graph.colours[3])
legend("topleft",c("WT","Mut1","Mut2"),fill=line.graph.colours)
In the brain/bodyweight data we want to plot a scatterplot of two sets of values, but we want to add to that some error bars using the arrows function, and some text labels.
read.delim("brain_bodyweight.txt") -> brain.body
brain.body
We can do a simple plot of the two weights to check that looks OK.
plot(
brain.body$Brainweight,
brain.body$Bodyweight,
pch=19
)
For the error bars we’re going to use the arrows function. The coordinates will be the weights plus and minus the SEMs. We’ll also use the brain/bodyweights to position the text labels.
plot(
brain.body$Brainweight,
brain.body$Bodyweight,
pch=19,
xlab="Brainweight",
ylab="Bodyweight"
)
# Brain SEM
arrows(
x0 = brain.body$Brainweight - brain.body$Brainweight.SEM,
y0 = brain.body$Bodyweight,
x1 = brain.body$Brainweight + brain.body$Brainweight.SEM,
y1 = brain.body$Bodyweight,
angle=90,
code = 3,
length=0.05
)
# Body SEM
arrows(
x0 = brain.body$Brainweight,
y0 = brain.body$Bodyweight - brain.body$Bodyweight.SEM,
x1 = brain.body$Brainweight,
y1 = brain.body$Bodyweight + brain.body$Bodyweight.SEM,
angle=90,
code = 3,
length=0.05
)
# Names
text(
brain.body$Brainweight,
brain.body$Bodyweight,
brain.body$Species,
pos = 1,
cex=0.5
)
LS0tDQp0aXRsZTogIkNvcmUgUiBQbG90dGluZyBFeGVyY2lzZSBBbnN3ZXJzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQpFeGVyY2lzZSAxIC0gQ3VzdG9taXNpbmcgc2ltcGxlIHBsb3RzDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNClBsb3R0aW5nIGEgYmFieSdzIGdyb3d0aCByYXRlLg0KDQpgYGB7cn0NCnJlYWQuZGVsaW0oIndlaWdodF9jaGFydC50eHQiKSAtPiB3ZWlnaHQuY2hhcnQNCg0Kd2VpZ2h0LmNoYXJ0DQpgYGANCg0KV2UncmUgZ29pbmcgdG8gdXNlIGEgbm9ybWFsIHNjYXR0ZXJwbG90IHRvIHBsb3QgdGhlIHR3byB2YWx1ZXMgYWdhaW5zdCBlYWNoIG90aGVyLiAgV2UnbGwgYWRkIGEgYnVuY2ggb2YgY3VzdG9taXNhdGlvbiBieSBtb2RpZnlpbmcgdGhlIHBhcmFtZXRlcnMgd2UgcGFzcy4NCg0KYGBge3J9DQpwbG90KA0KICB3ZWlnaHQuY2hhcnQkQWdlLA0KICB3ZWlnaHQuY2hhcnQkV2VpZ2h0LA0KICB0eXBlPSJiIiwNCiAgcGNoPTE1LA0KICBjZXg9MS41LA0KICBsd2Q9MiwNCiAgeWxpbT1jKDIsMTApLA0KICB4bGFiPSJBZ2UgKG1vbnRocykiLA0KICB5bGFiPSJXZWlnaHQgKGtnKSIsDQogIG1haW49IldlaWdoIGdhaW4gZHVyaW5nIGVhcmx5IGluZmFudCBkZXZlbG9wbWVudCINCikNCmBgYA0KDQoNCk5vdyB3ZSBjYW4gcmVhZCBpbiB0aGUgZmVhdHVyZSBjb3VudHMgZGF0YSB0byBwbG90IGEgYmFycGxvdCBvZiB0aGUgY291bnRzIGZvciBkaWZmZXJlbnQgdHlwZXMgb2YgZmVhdHVyZXMuDQoNCmBgYHtyfQ0KcmVhZC5kZWxpbSgiZmVhdHVyZV9jb3VudHMudHh0IikgLT4gZmVhdHVyZS5jb3VudHMNCg0KZmVhdHVyZS5jb3VudHMNCmBgYA0KDQoNCldlJ3JlIGdvaW5nIHRvIGRyYXcgYSBjdXN0b21pc2VkIGJhcnBsb3QgZnJvbSB0aGlzIGRhdGEuDQoNCmBgYHtyfQ0KcGFyKG1hcj1jKDUsMTIsNCwyKSkNCmJhcnBsb3QoDQogIGZlYXR1cmUuY291bnRzJENvdW50LA0KICBob3JpeiA9IFRSVUUsDQogIHhsYWI9Ik51bWJlciBvZiBmZWF0dXJlIGluc3RhbmNlcyIsDQogIG5hbWVzLmFyZz1mZWF0dXJlLmNvdW50cyRGZWF0dXJlLA0KICBtYWluPSJOdW1iZXIgb2YgZGlmZmVyZW50IGZlYXR1cmUgdHlwZXMgZm91bmQiLA0KICBsYXM9MQ0KICApDQpgYGANCg0KDQpGaW5hbGx5IHdlIHdhbnQgdG8gbWFrZSBhIGJpYXNlZCByYW5kb20gZGF0YXNldCB3aGljaCB3ZSBjYW4gcGxvdCBvdXQgYXMgYSBoaXN0b2dyYW0uDQoNCmBgYHtyfQ0KaGlzdC5kYXRhIDwtIGMocm5vcm0oMTAwMDApLHJub3JtKDEwMDAwKSs0KQ0KaGlzdChoaXN0LmRhdGEsYnJlYWtzPTYwLG1haW49IkJpbW9kYWwgZGF0YSIpDQpgYGANCg0KDQpFeGVyY2lzZSAyIC0gVXNpbmcgY29sb3VyDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCldlIHdhbnQgdG8gcGxvdCBvdXQgdGhlIG1hbGUvZmVtYWxlIGNvdW50IGRhdGEgYXMgYSBiYXJwbG90IGFuZCBjaGFuZ2UgdGhlIGNvbG91ciBpbiBkaWZmZXJlbnQgd2F5cy4NCg0KDQpgYGB7cn0NCnJlYWQuZGVsaW0oIm1hbGVfZmVtYWxlX2NvdW50cy50eHQiKSAtPiBtYWxlLmZlbWFsZQ0KDQptYWxlLmZlbWFsZQ0KYGBgDQoNCk5vdyB3ZSBjYW4gcGxvdCB0aGlzLiAgVGhlIGJhcnBsb3QganVzdCBuZWVkcyB0aGUgY291bnQgZGF0YSwgYW5kIHdlJ2xsIHNwZWNpZmljYWxseSBuZWVkIHRvIGFkZCB0aGUgc2FtcGxlIG5hbWVzIGFzIGxhYmVscy4gV2UgbmVlZCB0byBtYWtlIHRoZSBsYWJlbHMgc21hbGwgc28gdGhleSBhbGwgZml0ICh3ZSBjb3VsZCBhbHNvIGhhdmUgdHVybmVkIHRoZSBwbG90IGFyb3VuZCB0aGUgb3RoZXIgd2F5KS4NCg0KYGBge3J9DQpiYXJwbG90KA0KICBtYWxlLmZlbWFsZSRDb3VudCwNCiAgbmFtZXMuYXJnID0gbWFsZS5mZW1hbGUkU2FtcGxlLCANCiAgY2V4Lm5hbWVzID0gMC41DQopDQpgYGANCg0KVG8gYWRkIGEgZGlmZmVyZW50IGNvbG91ciB0byBlYWNoIHBvaW50ICh3aGljaCBpc24ndCBnZW5lcmFsbHkgYSBncmVhdCBpZGVhISkgd2UgY2FuIHVzZSB0aGUgcmFpbmJvdyBmdW5jdGlvbiB0byBnZW5lcmF0ZSBhIHNldCBvZiBjb2xvdXJzLiAgV2Ugc2ltcGx5IG5lZWQgdG8gdGVsbCB0aGUgZnVuY3Rpb24gaG93IG1hbnkgY29sb3VycyB0byBnZW5lcmF0ZSwgd2hpY2ggaW4gdGhpcyBjYXNlIGlzIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB0aGUgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQpyYWluYm93KG5yb3cobWFsZS5mZW1hbGUpKQ0KYGBgDQoNCldlIGNhbiB0aGVuIHBhc3MgdGhpcyB2ZWN0b3IgYXMgdGhlIGNvbCBwYXJhbXRlciB3aGVuIGRyYXdpbmcgdGhlIGJhcnBsb3QuDQoNCmBgYHtyfQ0KYmFycGxvdCgNCiAgbWFsZS5mZW1hbGUkQ291bnQsDQogIG5hbWVzLmFyZyA9IG1hbGUuZmVtYWxlJFNhbXBsZSwgDQogIGNleC5uYW1lcyA9IDAuNSwNCiAgY29sPXJhaW5ib3cobnJvdyhtYWxlLmZlbWFsZSkpDQopDQpgYGANCg0KTm93IHdlIHdhbnQgdG8gbWFrZSB0aGUgbWFsZXMgYW5kIGZlbWFsZXMgZGlmZmVyZW50IGNvbG91cnMuICBXZSBjYW4ndCBqdXN0IHBhc3MgdGhlIFNhbXBsZSBjb2x1bW4gYXMgY29sb3VyIHNpbmNlIGFsbCBvZiB0aGUgc2FtcGxlIG5hbWVzIGFyZSBkaWZmZXJlbnQgKGV2ZW4gdGhvdWdoIHRoZXkgY29udGFpbiBNYWxlIGFuZCBGZW1hbGUpLiAgSW4gdGhpcyBzcGVjaWZpYyBjYXNlLCBiZWNhdXNlIG1hbGVzIGFuZCBmZW1hbGVzIGFsdGVybmF0ZSB3ZSBjYW4ganVzdCBwYXNzIGEgZml4ZWQgMiBjb2xvdXIgdmVjdG9yLg0KDQpgYGB7cn0NCmJhcnBsb3QoDQogIG1hbGUuZmVtYWxlJENvdW50LA0KICBuYW1lcy5hcmcgPSBtYWxlLmZlbWFsZSRTYW1wbGUsIA0KICBjZXgubmFtZXMgPSAwLjUsDQogIGNvbD1jKCJibHVlMiIsInJlZDIiKQ0KKQ0KYGBgDQoNCklmIHdlJ2Qgd2FudGVkIGEgbW9yZSBnZW5lcmljIHNvbHV0aW9uIHRoZW4gd2UnZCBuZWVkIHRvIHVzZSBzb21lIG9mIHRoZSB0ZXh0IG1haW5pcHVsYXRpb24gdGVjaG5pcXVlcyBzaG93biBpbiB0aGUgYWR2YW5jZWQgUiBjb3Vyc2UsIHNwZWNpZmljYWxseSB3ZSBjYW4gcmVtb3ZlIHRoZSBEMSBldGMgZnJvbSB0aGUgc3RhcnQgb2YgZWFjaCBzdHJpbmcgdG8gbGVhdmUgdXMgd2l0aCBhIE1hbGUvRmVtYWxlIHNwbGl0IHdoaWNoIHdlIGNhbiB1c2UgYXMgYSBjb2xvdXIgZGlyZWN0bHkuDQoNCmBgYHtyfQ0Kc3Vic3RyaW5nKG1hbGUuZmVtYWxlJFNhbXBsZSw0KQ0KYGBgDQpgYGB7cn0NCmJhcnBsb3QoDQogIG1hbGUuZmVtYWxlJENvdW50LA0KICBuYW1lcy5hcmcgPSBtYWxlLmZlbWFsZSRTYW1wbGUsIA0KICBjZXgubmFtZXMgPSAwLjUsDQogIGNvbD1hcy5mYWN0b3Ioc3Vic3RyaW5nKG1hbGUuZmVtYWxlJFNhbXBsZSw0KSkNCikNCmBgYA0KDQoNCldlIG5leHQgd2FudCB0byB1c2UgdGhpcyB0eXBlIG9mIGNhdGVnb3JpY2FsIGNvbG91ciBkZWZpbml0aW9uIHRvIGhpZ2hsaWdodCBzcGVjaWZpYyBwb2ludHMgaW4gYSBzY2F0dGVwcmxvdC4gIFdlIGNhbiBzdGFydCBieSByZWFkaW5nIGluIHRoZSBkYXRhLg0KDQpgYGB7cn0NCnJlYWQuZGVsaW0oInVwX2Rvd25fZXhwcmVzc2lvbi50eHQiKSAtPiB1cC5kb3duDQoNCnVwLmRvd24NCmBgYA0KDQpZb3UgY2FuIHNlZSB0aGF0IHRoZSBkYXRhIHRvIHBsb3QgYXJlIHRoZSBDb25kaXRpb24xIGFuZCBDb25kaXRpb24yIGNvbHVtbnMsIGFuZCB0aGUgY2F0ZWdvcmllcyBhcmUgaW4gdGhlIFN0YXRlIGNvbHVtbi4NCg0KV2UgY2FuIHN0YXJ0IGJ5IGRvaW5nIGEgc2ltcGxlIHVuY29sb3VyZWQgcGxvdC4NCg0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnBsb3QoDQogIHVwLmRvd24kQ29uZGl0aW9uMSwNCiAgdXAuZG93biRDb25kaXRpb24yLA0KICBwY2g9MTkNCiAgKQ0KYGBgDQoNCg0KTm93IHdlIGNhbiBjb2xvdXIgYnkgdGhlIFN0YXRlIGNvbHVtbi4NCg0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnBsb3QoDQogIHVwLmRvd24kQ29uZGl0aW9uMSwNCiAgdXAuZG93biRDb25kaXRpb24yLA0KICBwY2g9MTksDQogIGNvbD11cC5kb3duJFN0YXRlDQogICkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgMyBkaWZmZXJlbnQgZ3JvdXBzIGhhdmUgYmVlbiBjb2xvdXJlZCwgYnV0IHdlIG5lZWQgdG8gdW5kZXJzdGFuZCBob3cuICBXZSBjYW4gZmlndXJlIHRoaXMgb3V0IGJ5IGxvb2tpbmcgYXQgdGhlIGxldmVscyBvZiB0aGUgc3RhdGUgY29sdW1uIGFuZCB0aGUgY29sb3VyIHZlY3RvciBjb250YWluZWQgaW4gdGhlIGN1cnJlbnQgc3lzdGVtIHBhbGV0dGUuDQoNCmBgYHtyfQ0KbGV2ZWxzKHVwLmRvd24kU3RhdGUpDQpwYWxldHRlKCkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgYSBkaXJlY3QgbWFwcGluZyBiZXR3ZWVuIHRoZSB0d28gdmVjdG9ycyB3aGVyZSBkb3duPWJsYWNrIChwb3NpdGlvbiAxKSwgdW5jaGFuZ2luZz1yZWQgYW5kIHVwPWdyZWVuLiAgSWYgd2Ugd2FudCB0byB1c2Ugb3VyIG93biBjb2xvdXJzIHRoZW4gd2UgbmVlZCB0byBzZXQgdGhlIHBhbGV0dGUgdG8gb3VyIGNvbG91ciBjaG9pY2UgaW4gdGhlIG9yZGVyIGRvd24sdW5jaGFuZ2luZyx1cC4gIEluIHRoaXMgY2FzZSBJJ3ZlIGFsc28gYWRkZWQgYSBsZWdlbmQgc28gd2UgY2FuIHNlZSB3aGljaCBjb2xvdXIgaXMgd2hpY2guDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9DQpwYWxldHRlKGMoImJsdWUyIiwiZ3JleSIsInJlZDIiKSkNCg0KcGxvdCgNCiAgdXAuZG93biRDb25kaXRpb24xLA0KICB1cC5kb3duJENvbmRpdGlvbjIsDQogIHBjaD0xOSwNCiAgY29sPXVwLmRvd24kU3RhdGUNCiAgKQ0KDQpsZWdlbmQoInRvcGxlZnQiLGxldmVscyh1cC5kb3duJFN0YXRlKSxmaWxsPXBhbGV0dGUoKVsxOjNdKQ0KDQpgYGANCg0KRmluYWxseSB3ZSBhcmUgZ29pbmcgdG8gbG9vayBhdCB0aGUgdXNlIG9mIHF1YW50aXRhdGl2ZSBjb2xvdXIuICBXZSdyZSBnb2luZyB0byB1c2UgdGhpcyB0byBwbG90IDMgdmFyaWFibGVzIG9uIGEgc2luZ2xlIHBsb3QuDQoNCldlIGNhbiBsb2FkIHRoZSBkYXRhIGZpcnN0Lg0KDQoNCmBgYHtyfQ0KcmVhZC5kZWxpbSgiZXhwcmVzc2lvbl9tZXRoeWxhdGlvbi50eHQiKSAtPiBleHByLm1ldGgNCg0KZXhwci5tZXRoDQpgYGANCg0KV2Ugd2FudCB0byBwbG90IHRoZSBwcm9tb3RlciBtZXRoeWxhdGlvbiBhZ2FpbnN0IHRoZSBnZW5lIG1ldGh5bGF0aW9uIGFuZCBjb2xvdXIgYnkgdGhlIGV4cHJlc3Npb24uICBXZSBjYW4gc3RhcnQgYnkgZG9pbmcgdGhlIHVuY29sb3VyZWQgcGxvdC4NCg0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnBsb3QoDQogIGV4cHIubWV0aCRwcm9tb3Rlci5tZXRoLA0KICBleHByLm1ldGgkZ2VuZS5tZXRoLA0KICBwY2g9MTkNCiAgKQ0KDQpgYGANCg0KRm9yIHRoZSBjb2xvdXJpbmcgd2UncmUgZ29pbmcgdG8gdXNlIGEgbmV3IGZ1bmN0aW9uIHdoaWNoIGhhcyBiZWVuIHByb3ZpZGVkIHRvIHVzLiAgV2UgY2FuIGluc3RhbGwgdGhpcyBpbnRvIHRoZSBzY3JpcHQgZmlyc3QuDQoNCmBgYHtyfQ0KbWFwLmNvbG91cnMgPC0gZnVuY3Rpb24gKHZhbHVlcyxwYWxldHRlKSB7DQoNCiAgcmFuZ2UgPC0gcmFuZ2UodmFsdWVzKQ0KDQogIHByb3BvcnRpb24gPC0gKCh2YWx1ZXMtcmFuZ2VbMV0pLyhyYW5nZVsyXS1yYW5nZVsxXSkpDQogIGluZGV4IDwtIHJvdW5kICgobGVuZ3RoKHBhbGV0dGUpLTEpKnByb3BvcnRpb24pKzENCiAgcmV0dXJuIChwYWxldHRlW2luZGV4XSkNCn0NCmBgYA0KDQpUaGlzIGZ1bmN0aW9uIHdpbGwgZ2VuZXJhdGUgYSB2ZWN0b3Igb2YgY29sb3VycyB0byBtYXRjaCBhIHF1YW50aXRhdGl2ZSB2ZWN0b3Igd2UgcHJvdmlkZSAodGhlIGV4cHJlc3Npb24gdmFsdWVzIGluIHRoaXMgY2FzZSkuICBXZSBjYW4gdGhlbiBwYXNzIHRoaXMgYXMgdGhlIGNvbG91ciB2ZWN0b3IgdG8gdGhlIHBsb3QgYW5kIHRoZXJlYnkgY29sb3VyIHRoZSB3aG9sZSB0aGluZy4NCg0KV2UgbmVlZCB0d28gcGllY2VzIG9mIGRhdGEgdG8gcnVuIHRoaXMgLSBhIHZlY3RvciBvZiB2YWx1ZXMgKHRoZSBleHByZXNzaW9uIGNvbHVtbiBmcm9tIG91ciBkYXRhIGZyYW1lKSwgYW5kIGEgdmVjdG9yIG9mIGNvbG91cnMgaW4gc29tZSBraW5kIG9mIG9yZGVyIGZyb20gd2hpY2ggdG8gc2VsZWN0Lg0KDQpUbyBtYWtlIG91ciByZWZlcmVuY2UgY29sb3VyIHZlY3RvciB3ZSdyZSBnb2luZyB0byBtYWtlIGEgY29sb3VyUmFtcFBhbGxldGUuICBUaGlzIGlzIGEgYnVpbHQgaW4gZnVuY3Rpb24gZm9yIGdlbmVyYXRpbmcgY29sb3VyIHNlcmllcy4NCg0KQ29sb3VyUmFtcFBhbGV0dGUgbmVlZHMgYSB2ZWN0b3Igb2YgcmVmZXJlbmNlIGNvbG91cnMgYW5kIGl0IHdpbGwgaW50ZXJwb2x0ZSBiZXR3ZWVuIHRoZXNlLiAgSW4gb3VyIHBsb3Qgd2UncmUganVzdCBnb2luZyB0byBydW4gYmV0d2VlbiBncmV5IGFuZCByZWQuDQoNClRoZSBpbml0aWFsIGNhbGwgdG8gY29sb3JSYW1wUGFsZXR0ZSByZXR1cm5zIHVzIGEgZnVuY3Rpb24gd2hpY2ggY2FuIGdlbmVyYXRlIHNldHMgb2YgY29sb3Vycy4NCg0KYGBge3J9DQpjb2xvclJhbXBQYWxldHRlKGMoImdyZXkiLCJyZWQyIikpDQpgYGANCg0KVG8gZ2VuZXJhdGUgdGhlIGNvbG91ciB2ZWN0b3Igd2UgbmVlZCB0byB0aGVuIGNhbGwgdGhpcyBmdW5jdGlvbiwgcGFzc2luZyB0aGUgbnVtYmVyIG9mIGNvbG91cnMgdG8gZ2VuZXJhdGUuICBXZSdsbCBtYWtlIDEwMCBjb2xvdXJzIHRoaXMgdGltZS4NCg0KYGBge3J9DQpjb2xvclJhbXBQYWxldHRlKGMoImdyZXkiLCJyZWQyIikpKDEwMCkNCmBgYA0KDQpXZSd2ZSBub3cgZ290IGV2ZXJ5dGhpbmcgd2UgbmVlZCB0byBjYWxsIHRoZSBtYXAuY29sb3VycyBmdW5jdGlvbi4gIFRoaXMgd2lsbCBnZW5lcmF0ZSBhIChsYXJnZSEpIGNvbG91ciB2ZWN0b3Igd2l0aCBhbGwgb2YgdGhlIHBlci1yb3cgY29sb3VycyB3ZSBuZWVkIGZvciB0aGUgcGxvdC4NCg0KYGBge3J9DQptYXAuY29sb3VycyhleHByLm1ldGgkZXhwcmVzc2lvbixjb2xvclJhbXBQYWxldHRlKGMoImdyZXkiLCJyZWQyIikpKDEwMCkpIC0+IGN1c3RvbS5jb2xvdXJzDQoNCmhlYWQoY3VzdG9tLmNvbG91cnMpDQpgYGANCg0KRmluYWxseSx3ZSBjYW4gcmUtZHJhdyBvdXQgcGxvdCB1c2luZyB0aGVzZSBjdXN0b20gY29sb3VycyB0byBjb2xvdXIgZWFjaCBwb2ludC4NCg0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnBsb3QoDQogIGV4cHIubWV0aCRwcm9tb3Rlci5tZXRoLA0KICBleHByLm1ldGgkZ2VuZS5tZXRoLA0KICBwY2g9MTksDQogIGNvbD1jdXN0b20uY29sb3Vycw0KICApDQoNCmBgYA0KDQpFeGVyY2lzZSAzIC0gVXNpbmcgb3ZlcmxheXMNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpXZSB3YW50IHRvIGRyYXcgYSBsaW5lIGdyYXBoIGNvbnRhaW5pbmcgMyBsaW5lcyBmb3IgMyBkaWZmZXJlbnQgZGF0YXNldHMuICBXZSdyZSBnb2luZyB0byBkbyB0aGlzIHVzaW5nIGEgYmFzZSBsaW5lIHBsb3QgZm9yIG9uZSBvZiB0aGUgZGF0YXNldHMgYW5kIHRoZW4gdHdvIG92ZXJsYXlzIHRvIGFkZCB0aGUgb3RoZXIgdHdvLg0KDQpgYGB7cn0NCnJlYWQuZGVsaW0oImNocm9tb3NvbWVfcG9zaXRpb25fZGF0YS50eHQiKSAtPiBjaHIucG9zDQoNCmNoci5wb3MNCmBgYA0KDQpUaGUgMyBkYXRhc2V0cyB3ZSdyZSBnb2luZyB0byBwbG90IGFyZSBXVCwgTXV0MSBhbmQgTXV0MiBhbmQgd2UncmUgZ29pbmcgdG8gcGxvdCB0aGVtIG9uIHRoZSB5IGF4aXMgYWdhaXN0IHRoZSBwb3NpdGlvbiBvbiB0aGUgeCBheGlzLg0KDQpXZSBuZWVkIHRvIHByZXBhcmUgYSBmZXcgdGhpbmdzIHRvIG1ha2UgdGhpcyB3b3JrLg0KDQpGaXJzdGx5IHdlIG5lZWQgdG8gZ2V0IHRoZSBmdWxsIHJhbmdlIG9mIHZhbHVlcyBpbiBhbnkgb2YgbXV0MSwgbXV0MiBvciB3dCBzbyB0aGF0IHdlIGtub3cgd2UndmUgZ290IGVub3VnaCBzcGFjZSBvbiB0aGUgeS1heGlzIHRvIGZpdCBldmVyeXRoaW5nIGluLg0KDQoNCmBgYHtyfQ0KbWF4KGNoci5wb3NbLDI6NF0pIC0+IG1heC52YWx1ZQ0KbWF4LnZhbHVlDQpgYGANCg0KV2UncmUgYWxzbyBnb2luZyB0byB1c2UgdGhlIFJDb2xvdXJCcmV3ZXIgcGFsZXR0ZSB0byBwcm92aWRlIHRoZSBjb2xvdXJzIHdlJ3JlIGdvaW5nIHRvIHVzZS4gIFdlJ2xsIHRha2UgdGhlIGZpcnN0IDMgY29sb3VycyBpbiBTZXQxLg0KDQpgYGB7cn0NCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KYnJld2VyLnBhbCgzLCJTZXQxIikgLT4gbGluZS5ncmFwaC5jb2xvdXJzDQpsaW5lLmdyYXBoLmNvbG91cnMNCmBgYA0KDQpOb3cgd2UgY2FuIHN0YXJ0IHRvIGJ1aWxkIHRoZSBwbG90LiAgV2UnbGwgc3RhcnQgd2l0aCB0aGUgYmFzZSBwbG90IHdoaWNoIHdpbGwgYmUgZm9yIHRoZSBXVC4gIFRoZW4gd2UgY2FuIGFkZCBsaW5lcyBsYXllcnMgZm9yIHRoZSBvdGhlciB0d28uICBGaW5hbGx5IHdlJ2xsIGFkZCBhIGxlZ2VuZCBzbyB3ZSBrbm93IHdoYXQncyB3aGF0Lg0KDQpgYGB7cn0NCnBsb3QoDQogIGNoci5wb3MkUG9zaXRpb24sDQogIGNoci5wb3MkV1QsDQogIHR5cGU9ImwiLA0KICBsd2Q9MiwNCiAgY29sPWxpbmUuZ3JhcGguY29sb3Vyc1sxXSwNCiAgeWxpbT1jKDAsbWF4LnZhbHVlKSwNCiAgbGFzPTEsDQogIHhsYWI9IkNocm9tb3NvbWFsIFBvc2l0aW9uIiwNCiAgeWxhYj0iVmFsdWUiLA0KICBtYWluPSJWYWx1ZXMgYWxvbmcgYSBjaHJvbW9zb21lIg0KKQ0KbGluZXMoY2hyLnBvcyRQb3NpdGlvbixjaHIucG9zJE11dDEsbHdkPTIsY29sPWxpbmUuZ3JhcGguY29sb3Vyc1syXSkNCmxpbmVzKGNoci5wb3MkUG9zaXRpb24sY2hyLnBvcyRNdXQyLGx3ZD0yLGNvbD1saW5lLmdyYXBoLmNvbG91cnNbM10pDQpsZWdlbmQoInRvcGxlZnQiLGMoIldUIiwiTXV0MSIsIk11dDIiKSxmaWxsPWxpbmUuZ3JhcGguY29sb3VycykNCmBgYA0KDQoNCkluIHRoZSBicmFpbi9ib2R5d2VpZ2h0IGRhdGEgd2Ugd2FudCB0byBwbG90IGEgc2NhdHRlcnBsb3Qgb2YgdHdvIHNldHMgb2YgdmFsdWVzLCBidXQgd2Ugd2FudCB0byBhZGQgdG8gdGhhdCBzb21lIGVycm9yIGJhcnMgdXNpbmcgdGhlIGFycm93cyBmdW5jdGlvbiwgYW5kIHNvbWUgdGV4dCBsYWJlbHMuDQoNCmBgYHtyfQ0KcmVhZC5kZWxpbSgiYnJhaW5fYm9keXdlaWdodC50eHQiKSAtPiBicmFpbi5ib2R5DQpicmFpbi5ib2R5DQpgYGANCg0KV2UgY2FuIGRvIGEgc2ltcGxlIHBsb3Qgb2YgdGhlIHR3byB3ZWlnaHRzIHRvIGNoZWNrIHRoYXQgbG9va3MgT0suDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9DQoNCnBsb3QoDQogIGJyYWluLmJvZHkkQnJhaW53ZWlnaHQsDQogIGJyYWluLmJvZHkkQm9keXdlaWdodCwNCiAgcGNoPTE5LA0KICB4bGFiPSJCcmFpbndlaWdodCIsDQogIHlsYWI9IkJvZHl3ZWlnaHQiDQogICkNCg0KDQpgYGANCg0KRm9yIHRoZSBlcnJvciBiYXJzIHdlJ3JlIGdvaW5nIHRvIHVzZSB0aGUgYXJyb3dzIGZ1bmN0aW9uLiAgVGhlIGNvb3JkaW5hdGVzIHdpbGwgYmUgdGhlIHdlaWdodHMgcGx1cyBhbmQgbWludXMgdGhlIFNFTXMuICBXZSdsbCBhbHNvIHVzZSB0aGUgYnJhaW4vYm9keXdlaWdodHMgdG8gcG9zaXRpb24gdGhlIHRleHQgbGFiZWxzLg0KDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9DQoNCnBsb3QoDQogIGJyYWluLmJvZHkkQnJhaW53ZWlnaHQsDQogIGJyYWluLmJvZHkkQm9keXdlaWdodCwNCiAgcGNoPTE5LA0KICB4bGFiPSJCcmFpbndlaWdodCIsDQogIHlsYWI9IkJvZHl3ZWlnaHQiDQogICkNCg0KIyBCcmFpbiBTRU0NCmFycm93cygNCiAgeDAgPSBicmFpbi5ib2R5JEJyYWlud2VpZ2h0IC0gYnJhaW4uYm9keSRCcmFpbndlaWdodC5TRU0sDQogIHkwID0gYnJhaW4uYm9keSRCb2R5d2VpZ2h0LA0KICB4MSA9IGJyYWluLmJvZHkkQnJhaW53ZWlnaHQgKyBicmFpbi5ib2R5JEJyYWlud2VpZ2h0LlNFTSwNCiAgeTEgPSBicmFpbi5ib2R5JEJvZHl3ZWlnaHQsDQogIGFuZ2xlPTkwLA0KICBjb2RlID0gMywNCiAgbGVuZ3RoPTAuMDUNCikNCg0KIyBCb2R5IFNFTQ0KYXJyb3dzKA0KICB4MCA9IGJyYWluLmJvZHkkQnJhaW53ZWlnaHQsDQogIHkwID0gYnJhaW4uYm9keSRCb2R5d2VpZ2h0IC0gYnJhaW4uYm9keSRCb2R5d2VpZ2h0LlNFTSwNCiAgeDEgPSBicmFpbi5ib2R5JEJyYWlud2VpZ2h0LA0KICB5MSA9IGJyYWluLmJvZHkkQm9keXdlaWdodCArIGJyYWluLmJvZHkkQm9keXdlaWdodC5TRU0sDQogIGFuZ2xlPTkwLA0KICBjb2RlID0gMywNCiAgbGVuZ3RoPTAuMDUNCikNCg0KIyBOYW1lcw0KdGV4dCgNCiAgYnJhaW4uYm9keSRCcmFpbndlaWdodCwNCiAgYnJhaW4uYm9keSRCb2R5d2VpZ2h0LA0KICBicmFpbi5ib2R5JFNwZWNpZXMsDQogIHBvcyA9IDEsDQogIGNleD0wLjUNCikNCg0KDQoNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg==