1. Further customisation of plots
Recap
- We have seen how to use
plot()
, boxplot()
, hist()
etc to make simple plots
- These come with arguments that can be used to change the appearance of the plot
col
, pch
main
, xlab
, ylab
- etc….
- We will now look at ways to modify the plot appearance after it has been created
- Also, how to export the graphs
The painter’s model
- R employs a painter’s model to construct it’s plots
- Elements of the graph are added to the canvas one layer at a time, and the picture built up in levels.
- Lower levels are obscured by higher levels,
- allowing for blending, masking and overlaying of objects.
- You can’t undo the changes you make to the plot
- you have to re-run the code from scratch
http://www.inquisitr.com/309687/jesus-painting-restoration-goes-wrong-well-intentioned-old-lady-destroys-100-year-old-fresco/
- We will re-use the patients data from yesterday:
patients <- read.delim("patient-info.txt")
- Recall our patients dataset from yesterday
- we might want to display other characteristics on the plot, e.g. gender of individual:
plot(patients$Height, patients$Weight, pch=16)
The points function
points()
can be used to set of points to an existing plot
- It requires a vector of x and y coordinates
- These do not have to be the same length as the number of points in the initial plot:
- Hence we can use
points()
to highlight observations
- …or add a set of new observations
plot(patients$Height, patients$Weight, pch=16)
points(160,90, pch="X")
- Note that axis limits of the existing plot are not altered
- Often it is useful to create a blank ‘canvas’ with the correct labels and limits
plot(patients$Height, patients$Weight, type="n")
Adding points to differentiate gender
- Selecting males using the
==
comparison we saw yesterday
- Gives a
TRUE
or FALSE
value
- Can be used to index the data frame
- Which means we can get the relevant Age and Weight values
males <- patients$Sex == "Male"
males
[1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE FALSE
[15] FALSE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE FALSE FALSE TRUE TRUE TRUE
[29] TRUE TRUE FALSE TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE
[43] FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
[57] FALSE FALSE TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE TRUE FALSE
[71] FALSE TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE
[85] FALSE FALSE FALSE TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE FALSE FALSE FALSE
[99] FALSE FALSE
males
patients[males,]
patients$Age[males]
patients$Weight[males]
- The points we add have to be within the
x
and y
limits of the original plot axes, otherwise they won’t be displayed
- R won’t give a warning or error if you attempt to plot points outside the axis limits
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
pch=16, col="steelblue")
We can do the same for Females
females <- patients$Sex == "Female"
females
[1] FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE TRUE TRUE FALSE TRUE
[15] TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE TRUE TRUE FALSE FALSE FALSE
[29] FALSE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
[43] TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE
[57] TRUE TRUE FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE
[71] TRUE FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE FALSE TRUE
[85] TRUE TRUE TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE TRUE TRUE
[99] TRUE TRUE
patients[females,]
- Again, we have to be careful that all the points are within the
x
and y
limits
- this is why creating the blank plot containing the limits of the data is useful
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
pch=16, col="orangered1")
- Each set of points can have a different colour and shape
- Axis labels and title and limits are defined by the plot
- Once you’ve added points to a plot, they cannot be removed
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
pch=17, col="orangered1",
xlab="Age of Patient",
ylab="Weight",
main="Relationship between Age and Weights")
## The arguments xlab, ylab, main in the points functions are not used
## Need to specify these labels when you create the plot initially
Adding a legend
- Should also add a legend to help interpret the plot
- use the
legend
function
- can give x and y coordinates where legend will appear
- also recognises shortcuts such as topleft and bottomright…
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
pch=17, col="orangered1")
legend("topleft", legend=c("M","F"),
col=c("steelblue","orangered1"), pch=c(16,17))
Adding text
- Text can also be added to a plot in a similar manner
- the
labels
argument specifies the text we want to add
plot(patients$Height, patients$Weight, pch=16)
text(patients$Height, patients$Weight, labels=patients$Race)
- In the above we used to same co-ordinates from the original plot to place the text
- We can use arguments
pos
or adj
to offset the positions of the labels
pos
can be 1, 2, 3, 4 for below, left, above, right
adj
is an adjustment in the range 0 to 1
plot(patients$Height, patients$Weight, pch=16)
text(patients$Height, patients$Weight, labels=patients$Race,pos = 3)
plot(patients$Height, patients$Weight, pch=16)
text(patients$Height, patients$Weight, labels=patients$Race,adj =0.1)
- To aid our interpretation, it is often helpful to add guidelines
grid()
is one easy way of doing this:
plot(patients$Height, patients$Weight, pch=16)
grid(col="steelblue")
- Can also add lines that intersect the axes:
v =
for vertical lines
h =
for horizontal
- can specify multiple lines in a vector
plot(patients$Height, patients$Weight, pch=16)
abline(v=160, col="red")
abline(h=c(65,70,75), col="blue")
Plot layouts
- The
par
function can be used specify the appearance of a plot
- The settings persist until the plot is closed with
dev.off()
?par
and scroll to graphical parameters
- One example is
mfrow
:
- “multiple figures per row”
- needs to be a vector of rows and columns:
- e.g. a plot with one row and two columns
par(mfrow=c(1,2))
- don’t need the same kind of plot in each cell
par(mfrow=c(1,2))
plot(patients$Height, patients$Weight, pch=16)
boxplot(patients$Weight ~ patients$Sex)
- See also
mar
for setting the margins:
Exporting graphs from RStudio
- You can use the
pdf()
function to export one or more plots to a pdf file:
- You will see that the plot does not appear in RStudio
- You need to use the
dev.off()
to stop printing graphs to the pdf and ‘close’ the file
- It allows you to create a pdf document with multiple pages
pdf("ExampleGraph.pdf")
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device
1
- pdf is a good choice for publication as they can be imported into Photoshop, Inkscape, etc.
- Sometimes it is easier to edit in these tools than R!
- If it is taking too long to customise a plot in R, consider if you should be using one of these tools instead
- To save any graph you have created to a pdf, repeat the code you used to create the plot with
pdf()
before and dev.off()
afterwards
- you can have as many lines of code in-between as you like
- each new plot will be written to a different page
pdf("mygraph.pdf")
plot(patients$Height, patients$Weight, pch=16)
abline(v=40, col="red")
abline(h=c(65,70,75), col="blue")
boxplot(patients$Weight ~ patients$Sex)
x <- 1:10
y <- x^2 + 4*x
plot(x,y)
dev.off()
null device
1
- If no plots are appearing in RStudio, it could be you are still writing to a pdf file
- run
dev.off()
multiple times until you see a message cannot shut down device (the null device)
- We can specify the dimensions of the plot, and other properties of the file (
?pdf
)
pdf("ExampleGraph.pdf", width=10, height=5)
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device
1
- Other formats can be created:
- e.g. png, or others
?jpeg
- more appropriate for email, presentations, web page
png("ExampleGraph.png")
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device
1
Exercise: Exercise 5a
- Return to the weather data from yesterday:
weather <- read.csv("ozone.csv")
- Using the
par
function, create a layout with three columns
- Plot Ozone versus Solar Radiation, Wind Speed and Temperature on separate graphs
- use different colours and plotting characters on each plot
- Save the plot to a pdf
- HINT: Create the graph first in RStudio. When you’re happy with it, re-run the code preceeded by the
pdf
function to save to a file
- don’t forget to use
dev.off()
to close the file
### Your Answer Here ###
Exercise: Exercise 5b
- Temperature and Ozone level seem to be correlated
- However, there are some observations that do not seem to fit the trend
- those with Ozone level > 100
- Modify the plot so that these outlier observations are in a different colour
- Add a legend to help interpret the plot
HINT: You can break down the problem into the following steps
- Create a blank plot
- Identify observations with ozone > 100
- plot the corresponding Temperature and Ozone values for these in red
- Identify observations with ozone < 100
- plot the corresponding Temperature and Ozone values for these in orange
- Can you modify your code so that the cut-off is not hard-coded (fixed) to 100
- e.g. Ozone > 80, Ozone, 90 etc…
- can you re-generate the plots the minimal changes to the code
### Your Answer Here ###
An alternative, and equally-valid, solution involves creating a vector of colours which will either be red
or orange
depending whether on the particular value of ozone is an outlier
- we can use the
rep
function to create a vector of the required length for the entire dataset
- one vector for plotting colours, and another vector for plotting characters
weather <- read.csv("ozone.csv")
mycol <-rep("orange",nrow(weather))
mypch <- rep(17, nrow(weather))
- now use a logical expression to identify the high ozone level, and replace the corresponding entries in the colour and plotting character vectors
highO <- which(weather$Ozone > 100)
mycol[highO] <- "red"
mypch[highO] <- 18
- creating the plot can now be done with a single
plot
command
plot(weather$Temp,weather$Ozone,
col=mycol, pch=mypch,ylab="Ozone level",
xlab="Temperature")
abline(h=100,lty=2)
legend("topleft", legend = c("Ozone > 100","Normal Ozone"),col=c("red","orange"),pch=17)
Other plotting features
We can choose not to show the x- and y-axis in the initial plot
plot(patients$Age, patients$Weight, pch=16,axes=FALSE)
They can be added afterwards with the axis
function - first argument is whether the axis appears on the bottom (1), left (2), top (3) or right (4) - can also define where the tick marks are located, and the labels to display - the box
function can be used to enclose the plot in a box
plot(patients$Age, patients$Weight, pch=16,axes=FALSE)
axis(1)
axis(4, at = c(65,75,85,95),labels = c("65kg","75kg","85kg","95kg"))
box()
Ordering the boxes in a set of boxplots
N.B. The order in which the boxes are displayed on a boxplot isn’t something that can be controlled by graphical paramaters
- the order is derived from the order of the categories
- in this case
Female
comes first alphabetically
boxplot(patients$Weight~patients$Sex)
- a different order can be achieved by defining a new factor where the
levels
are in the order you want
patients$Sex <- factor(patients$Sex,levels = c("Male","Female"))
boxplot(patients$Weight~patients$Sex)
Need inspiration?
The R Graph Gallery has lots of examples (and code) showing what can be achieved with R graphics
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFNvbHZpbmcgQmlvbG9naWNhbCBQcm9ibGVtcyBVc2luZyBSIC0gRGF5IDIiCmF1dGhvcjogTWFyayBEdW5uaW5nLCBTdXJhaiBNZW5vbiBhbmQgQWlvcmEgWmFiYWxhLiBPcmlnaW5hbCBtYXRlcmlhbCBieSBSb2JlcnQgU3Rvam5pxIcsCiAgTGF1cmVudCBHYXR0bywgUm9iIEZveSwgSm9obiBEYXZleSwgRMOhdmlkIE1vbG7DoXIgYW5kIElhbiBSb2JlcnRzCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIkxhc3QgbW9kaWZpZWQ6ICVkICViICVZIilgJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCiMgRGF5IDIgU2NoZWR1bGUKCjEuIEZ1cnRoZXIgY3VzdG9taXNhdGlvbiBvZiBwbG90cwoyLiBTdGF0aXN0aWNzCjMuIERhdGEgTWFuaXB1bGF0aW9uIFRlY2huaXF1ZXMKNC4gUHJvZ3JhbW1pbmcgaW4gUgo1LiBGdXJ0aGVyIHJlcG9ydCB3cml0aW5nCgojMS4gRnVydGhlciBjdXN0b21pc2F0aW9uIG9mIHBsb3RzCgojIyBSZWNhcAoKLSBXZSBoYXZlIHNlZW4gaG93IHRvIHVzZSBgcGxvdCgpYCwgYGJveHBsb3QoKWAgLCBgaGlzdCgpYCBldGMgdG8gbWFrZSBzaW1wbGUgcGxvdHMKLSBUaGVzZSBjb21lIHdpdGggYXJndW1lbnRzIHRoYXQgY2FuIGJlIHVzZWQgdG8gY2hhbmdlIHRoZSBhcHBlYXJhbmNlIG9mIHRoZSBwbG90CiAgICArIGBjb2xgLCBgcGNoYAogICAgKyBgbWFpbmAsIGB4bGFiYCwgYHlsYWJgCiAgICArIGV0Yy4uLi4KLSBXZSB3aWxsIG5vdyBsb29rIGF0IHdheXMgdG8gbW9kaWZ5IHRoZSBwbG90IGFwcGVhcmFuY2UgYWZ0ZXIgaXQgaGFzIGJlZW4gY3JlYXRlZAotIEFsc28sIGhvdyB0byBleHBvcnQgdGhlIGdyYXBocyAKCgoKIyMgVGhlIHBhaW50ZXIncyBtb2RlbAoKLSBSIGVtcGxveXMgYSBwYWludGVyJ3MgbW9kZWwgdG8gY29uc3RydWN0IGl0J3MgcGxvdHMKLSBFbGVtZW50cyBvZiB0aGUgZ3JhcGggYXJlIGFkZGVkIHRvIHRoZSBjYW52YXMgb25lIGxheWVyIGF0IGEgdGltZSwgYW5kIHRoZSBwaWN0dXJlIGJ1aWx0IHVwIGluIGxldmVscy4KLSBMb3dlciBsZXZlbHMgYXJlIG9ic2N1cmVkIGJ5IGhpZ2hlciBsZXZlbHMsIAogICAgKyBhbGxvd2luZyBmb3IgYmxlbmRpbmcsIG1hc2tpbmcgYW5kIG92ZXJsYXlpbmcgb2Ygb2JqZWN0cy4KLSBZb3UgY2FuJ3QgdW5kbyB0aGUgY2hhbmdlcyB5b3UgbWFrZSB0byB0aGUgcGxvdAogICAgKyB5b3UgaGF2ZSB0byByZS1ydW4gdGhlIGNvZGUgZnJvbSBzY3JhdGNoCiAgICAKIVtdKGh0dHA6Ly9jZG4uaW5xdWlzaXRyLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxMi8wOC9qZXN1cy1jaHJpc3QtZnJlc2NvLmdpZikKCltodHRwOi8vd3d3LmlucXVpc2l0ci5jb20vMzA5Njg3L2plc3VzLXBhaW50aW5nLXJlc3RvcmF0aW9uLWdvZXMtd3Jvbmctd2VsbC1pbnRlbnRpb25lZC1vbGQtbGFkeS1kZXN0cm95cy0xMDAteWVhci1vbGQtZnJlc2NvL10oaHR0cDovL3d3dy5pbnF1aXNpdHIuY29tLzMwOTY4Ny9qZXN1cy1wYWludGluZy1yZXN0b3JhdGlvbi1nb2VzLXdyb25nLXdlbGwtaW50ZW50aW9uZWQtb2xkLWxhZHktZGVzdHJveXMtMTAwLXllYXItb2xkLWZyZXNjby8pCgoKLSBXZSB3aWxsIHJlLXVzZSB0aGUgcGF0aWVudHMgZGF0YSBmcm9tIHllc3RlcmRheToKCmBgYHtyfQpwYXRpZW50cyA8LSByZWFkLmRlbGltKCJwYXRpZW50LWluZm8udHh0IikKYGBgCgotIFJlY2FsbCBvdXIgcGF0aWVudHMgZGF0YXNldCBmcm9tIHllc3RlcmRheQogICAgKyB3ZSBtaWdodCB3YW50IHRvIGRpc3BsYXkgb3RoZXIgY2hhcmFjdGVyaXN0aWNzIG9uIHRoZSBwbG90LCBlLmcuIGdlbmRlciBvZiBpbmRpdmlkdWFsOgoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYGBgCgojI1RoZSBwb2ludHMgZnVuY3Rpb24KCi0gYHBvaW50cygpYCBjYW4gYmUgdXNlZCB0byBzZXQgb2YgcG9pbnRzIHRvIGFuICpleGlzdGluZyogcGxvdAotIEl0IHJlcXVpcmVzIGEgdmVjdG9yIG9mIHggYW5kIHkgY29vcmRpbmF0ZXMKICAgICsgVGhlc2UgZG8gbm90IGhhdmUgdG8gYmUgdGhlIHNhbWUgbGVuZ3RoIGFzIHRoZSBudW1iZXIgb2YgcG9pbnRzIGluIHRoZSBpbml0aWFsIHBsb3Q6CiAgICAgICAgKyBIZW5jZSB3ZSBjYW4gdXNlIGBwb2ludHMoKWAgdG8gaGlnaGxpZ2h0IG9ic2VydmF0aW9ucwogICAgICAgICsgLi4ub3IgYWRkIGEgc2V0IG9mIG5ldyBvYnNlcnZhdGlvbnMKYGBge3IgfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCnBvaW50cygxNjAsOTAsIHBjaD0iWCIpCmBgYAoKLSBOb3RlIHRoYXQgYXhpcyBsaW1pdHMgb2YgdGhlIGV4aXN0aW5nIHBsb3QgYXJlIG5vdCBhbHRlcmVkCi0gT2Z0ZW4gaXQgaXMgdXNlZnVsIHRvIGNyZWF0ZSBhIGJsYW5rICdjYW52YXMnIHdpdGggdGhlIGNvcnJlY3QgbGFiZWxzIGFuZCBsaW1pdHMKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCB0eXBlPSJuIikKYGBgCgojIyBBZGRpbmcgcG9pbnRzIHRvIGRpZmZlcmVudGlhdGUgZ2VuZGVyCgotIFNlbGVjdGluZyBtYWxlcyB1c2luZyB0aGUgKipgPT1gKiogY29tcGFyaXNvbiB3ZSBzYXcgeWVzdGVyZGF5CiAgICArIEdpdmVzIGEgYFRSVUVgIG9yIGBGQUxTRWAgdmFsdWUKICAgICsgQ2FuIGJlIHVzZWQgdG8gaW5kZXggdGhlIGRhdGEgZnJhbWUKICAgICsgV2hpY2ggbWVhbnMgd2UgY2FuIGdldCB0aGUgcmVsZXZhbnQgQWdlIGFuZCBXZWlnaHQgdmFsdWVzCmBgYHtyfQptYWxlcyA8LSBwYXRpZW50cyRTZXggPT0gIk1hbGUiCm1hbGVzCmBgYAoKYGBge3IsIGV2YWw9RkFMU0V9Cm1hbGVzCnBhdGllbnRzW21hbGVzLF0KcGF0aWVudHMkQWdlW21hbGVzXQpwYXRpZW50cyRXZWlnaHRbbWFsZXNdCmBgYAoKCgotIFRoZSBwb2ludHMgd2UgYWRkIGhhdmUgdG8gYmUgd2l0aGluIHRoZSBgeGAgYW5kIGB5YCBsaW1pdHMgb2YgdGhlIG9yaWdpbmFsIHBsb3QgYXhlcywgb3RoZXJ3aXNlIHRoZXkgd29uJ3QgYmUgZGlzcGxheWVkCiAgICArIFIgd29uJ3QgZ2l2ZSBhIHdhcm5pbmcgb3IgZXJyb3IgaWYgeW91IGF0dGVtcHQgdG8gcGxvdCBwb2ludHMgb3V0c2lkZSB0aGUgYXhpcyBsaW1pdHMKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCB0eXBlPSJuIikKcG9pbnRzKHBhdGllbnRzJEhlaWdodFttYWxlc10sIHBhdGllbnRzJFdlaWdodFttYWxlc10sIAogICAgICAgcGNoPTE2LCBjb2w9InN0ZWVsYmx1ZSIpCgpgYGAKCgpXZSBjYW4gZG8gdGhlIHNhbWUgZm9yIEZlbWFsZXMKICAgIApgYGB7cn0KZmVtYWxlcyA8LSBwYXRpZW50cyRTZXggPT0gIkZlbWFsZSIKZmVtYWxlcwpwYXRpZW50c1tmZW1hbGVzLF0KYGBgCgotIEFnYWluLCB3ZSBoYXZlIHRvIGJlIGNhcmVmdWwgdGhhdCBhbGwgdGhlIHBvaW50cyBhcmUgd2l0aGluIHRoZSBgeGAgYW5kIGB5YCBsaW1pdHMKICAgICsgdGhpcyBpcyB3aHkgY3JlYXRpbmcgdGhlIGJsYW5rIHBsb3QgY29udGFpbmluZyB0aGUgbGltaXRzIG9mIHRoZSBkYXRhIGlzIHVzZWZ1bAoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHR5cGU9Im4iKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W21hbGVzXSwgcGF0aWVudHMkV2VpZ2h0W21hbGVzXSwKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTYsIGNvbD0ib3JhbmdlcmVkMSIpCgpgYGAKCi0gRWFjaCBzZXQgb2YgcG9pbnRzIGNhbiBoYXZlIGEgZGlmZmVyZW50IGNvbG91ciBhbmQgc2hhcGUKLSBBeGlzIGxhYmVscyBhbmQgdGl0bGUgYW5kIGxpbWl0cyBhcmUgZGVmaW5lZCBieSB0aGUgcGxvdAotIE9uY2UgeW91J3ZlIGFkZGVkIHBvaW50cyB0byBhIHBsb3QsIHRoZXkgY2Fubm90IGJlIHJlbW92ZWQKCgpgYGB7ciB9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHR5cGU9Im4iKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W21hbGVzXSwgcGF0aWVudHMkV2VpZ2h0W21hbGVzXSwKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTcsIGNvbD0ib3JhbmdlcmVkMSIsCiAgICAgICAgeGxhYj0iQWdlIG9mIFBhdGllbnQiLAogICAgICAgeWxhYj0iV2VpZ2h0IiwKICAgICAgIG1haW49IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIEFnZSBhbmQgV2VpZ2h0cyIpCgojIyBUaGUgYXJndW1lbnRzIHhsYWIsIHlsYWIsIG1haW4gaW4gdGhlIHBvaW50cyBmdW5jdGlvbnMgYXJlIG5vdCB1c2VkCiMjIE5lZWQgdG8gc3BlY2lmeSB0aGVzZSBsYWJlbHMgd2hlbiB5b3UgY3JlYXRlIHRoZSBwbG90IGluaXRpYWxseQoKYGBgCgoKCgojIyBBZGRpbmcgYSBsZWdlbmQKCi0gU2hvdWxkIGFsc28gYWRkIGEgbGVnZW5kIHRvIGhlbHAgaW50ZXJwcmV0IHRoZSBwbG90CiAgICArIHVzZSB0aGUgYGxlZ2VuZGAgZnVuY3Rpb24KICAgICsgY2FuIGdpdmUgeCBhbmQgeSBjb29yZGluYXRlcyB3aGVyZSBsZWdlbmQgd2lsbCBhcHBlYXIKICAgICsgYWxzbyByZWNvZ25pc2VzIHNob3J0Y3V0cyBzdWNoIGFzICoqKnRvcGxlZnQqKiogYW5kICoqKmJvdHRvbXJpZ2h0KioqLi4uCgpgYGB7cn0KcGxvdChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgdHlwZT0ibiIpCnBvaW50cyhwYXRpZW50cyRIZWlnaHRbbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbbWFsZXNdLCAKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTcsIGNvbD0ib3JhbmdlcmVkMSIpCmxlZ2VuZCgidG9wbGVmdCIsIGxlZ2VuZD1jKCJNIiwiRiIpLCAKICAgICAgIGNvbD1jKCJzdGVlbGJsdWUiLCJvcmFuZ2VyZWQxIiksIHBjaD1jKDE2LDE3KSkKYGBgCgojI0FkZGluZyB0ZXh0CgotIFRleHQgY2FuIGFsc28gYmUgYWRkZWQgdG8gYSBwbG90IGluIGEgc2ltaWxhciBtYW5uZXIKICAgICsgdGhlIGBsYWJlbHNgIGFyZ3VtZW50IHNwZWNpZmllcyB0aGUgdGV4dCB3ZSB3YW50IHRvIGFkZAogICAgCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCiAgdGV4dChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgbGFiZWxzPXBhdGllbnRzJFJhY2UpCmBgYAoKLSBJbiB0aGUgYWJvdmUgd2UgdXNlZCB0byBzYW1lIGNvLW9yZGluYXRlcyBmcm9tIHRoZSBvcmlnaW5hbCBwbG90IHRvIHBsYWNlIHRoZSB0ZXh0Ci0gV2UgY2FuIHVzZSBhcmd1bWVudHMgYHBvc2Agb3IgYGFkamAgdG8gb2Zmc2V0IHRoZSBwb3NpdGlvbnMgb2YgdGhlIGxhYmVscwogICAgKyBgcG9zYCBjYW4gYmUgMSwgMiwgMywgNCBmb3IgYmVsb3csIGxlZnQsIGFib3ZlLCByaWdodAogICAgKyBgYWRqYCBpcyBhbiBhZGp1c3RtZW50IGluIHRoZSByYW5nZSAwIHRvIDEKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCiAgdGV4dChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgbGFiZWxzPXBhdGllbnRzJFJhY2UscG9zID0gMykKYGBgCgoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKICB0ZXh0KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBsYWJlbHM9cGF0aWVudHMkUmFjZSxhZGogPTAuMSkKYGBgCgoKLSBUbyBhaWQgb3VyIGludGVycHJldGF0aW9uLCBpdCBpcyBvZnRlbiBoZWxwZnVsIHRvIGFkZCBndWlkZWxpbmVzCiAgICArIGBncmlkKClgIGlzIG9uZSBlYXN5IHdheSBvZiBkb2luZyB0aGlzOgogICAgCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCmdyaWQoY29sPSJzdGVlbGJsdWUiKQpgYGAKCgotIENhbiBhbHNvIGFkZCBsaW5lcyB0aGF0IGludGVyc2VjdCB0aGUgYXhlczoKICAgICsgYHYgPWAgZm9yIHZlcnRpY2FsIGxpbmVzCiAgICArIGBoID1gIGZvciBob3Jpem9udGFsCiAgICArIGNhbiBzcGVjaWZ5IG11bHRpcGxlIGxpbmVzIGluIGEgdmVjdG9yCiAgICAKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYWJsaW5lKHY9MTYwLCBjb2w9InJlZCIpCmFibGluZShoPWMoNjUsNzAsNzUpLCBjb2w9ImJsdWUiKQpgYGAKCgojIyBQbG90IGxheW91dHMKCi0gVGhlIGBwYXJgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHNwZWNpZnkgdGhlIGFwcGVhcmFuY2Ugb2YgYSBwbG90Ci0gVGhlIHNldHRpbmdzIHBlcnNpc3QgdW50aWwgdGhlIHBsb3QgaXMgY2xvc2VkIHdpdGggKipgZGV2Lm9mZigpYCoqCi0gYD9wYXJgIGFuZCBzY3JvbGwgdG8gKioqZ3JhcGhpY2FsIHBhcmFtZXRlcnMqKioKLSBPbmUgZXhhbXBsZSBpcyBgbWZyb3dgOgogICAgKyAibXVsdGlwbGUgZmlndXJlcyBwZXIgcm93IgogICAgKyBuZWVkcyB0byBiZSBhIHZlY3RvciBvZiByb3dzIGFuZCBjb2x1bW5zOgogICAgICAgICsgZS5nLiBhIHBsb3Qgd2l0aCBvbmUgcm93IGFuZCB0d28gY29sdW1ucyBgcGFyKG1mcm93PWMoMSwyKSlgCiAgICAgICAgKyBkb24ndCBuZWVkIHRoZSBzYW1lIGtpbmQgb2YgcGxvdCBpbiBlYWNoIGNlbGwKICAgICAgICAKYGBge3J9CnBhcihtZnJvdz1jKDEsMikpCnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCgpgYGAKCi0gU2VlIGFsc28gYG1hcmAgZm9yIHNldHRpbmcgdGhlIG1hcmdpbnM6CiAgICArIGBwYXIobWFyPWMoLi4uKSlgCiAgICAKIyMgRXhwb3J0aW5nIGdyYXBocyBmcm9tIFJTdHVkaW8KCi0gWW91IGNhbiB1c2UgdGhlIGBwZGYoKWAgZnVuY3Rpb24gdG8gZXhwb3J0IG9uZSBvciBtb3JlIHBsb3RzIHRvIGEgcGRmIGZpbGU6CiAgICArIFlvdSB3aWxsIHNlZSB0aGF0IHRoZSBwbG90IGRvZXMgbm90IGFwcGVhciBpbiBSU3R1ZGlvCi0gWW91IG5lZWQgdG8gdXNlIHRoZSBgZGV2Lm9mZigpYCB0byBzdG9wIHByaW50aW5nIGdyYXBocyB0byB0aGUgcGRmIGFuZCAnY2xvc2UnIHRoZSBmaWxlCiAgICArIEl0IGFsbG93cyB5b3UgdG8gY3JlYXRlIGEgcGRmIGRvY3VtZW50IHdpdGggbXVsdGlwbGUgcGFnZXMKICAgIApgYGB7cn0KcGRmKCJFeGFtcGxlR3JhcGgucGRmIikKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCmRldi5vZmYoKQpgYGAKCi0gcGRmIGlzIGEgZ29vZCBjaG9pY2UgZm9yIHB1YmxpY2F0aW9uIGFzIHRoZXkgY2FuIGJlIGltcG9ydGVkIGludG8gUGhvdG9zaG9wLCBJbmtzY2FwZSwgZXRjLgogICAgLSBTb21ldGltZXMgaXQgaXMgZWFzaWVyIHRvIGVkaXQgaW4gdGhlc2UgdG9vbHMgdGhhbiBSIQogICAgLSBJZiBpdCBpcyB0YWtpbmcgdG9vIGxvbmcgdG8gY3VzdG9taXNlIGEgcGxvdCBpbiBSLCBjb25zaWRlciBpZiB5b3Ugc2hvdWxkIGJlIHVzaW5nIG9uZSBvZiB0aGVzZSB0b29scyBpbnN0ZWFkCiAgICAKCi0gVG8gc2F2ZSBhbnkgZ3JhcGggeW91IGhhdmUgY3JlYXRlZCB0byBhIHBkZiwgcmVwZWF0IHRoZSBjb2RlIHlvdSB1c2VkIHRvIGNyZWF0ZSB0aGUgcGxvdCB3aXRoIGBwZGYoKWAgYmVmb3JlIGFuZCBgZGV2Lm9mZigpYCBhZnRlcndhcmRzCiAgICArIHlvdSBjYW4gaGF2ZSBhcyBtYW55IGxpbmVzIG9mIGNvZGUgaW4tYmV0d2VlbiBhcyB5b3UgbGlrZQogICAgKyBlYWNoIG5ldyBwbG90IHdpbGwgYmUgd3JpdHRlbiB0byBhIGRpZmZlcmVudCBwYWdlCiAgICAKYGBge3J9CnBkZigibXlncmFwaC5wZGYiKQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCmFibGluZSh2PTQwLCBjb2w9InJlZCIpCmFibGluZShoPWMoNjUsNzAsNzUpLCBjb2w9ImJsdWUiKQpib3hwbG90KHBhdGllbnRzJFdlaWdodCB+IHBhdGllbnRzJFNleCkKeCA8LSAxOjEwCnkgPC0geF4yICsgNCp4CnBsb3QoeCx5KQpkZXYub2ZmKCkKYGBgCgotIElmIG5vIHBsb3RzIGFyZSBhcHBlYXJpbmcgaW4gUlN0dWRpbywgaXQgY291bGQgYmUgeW91IGFyZSBzdGlsbCB3cml0aW5nIHRvIGEgcGRmIGZpbGUKICAgICsgcnVuIGBkZXYub2ZmKClgIG11bHRpcGxlIHRpbWVzIHVudGlsIHlvdSBzZWUgYSBtZXNzYWdlIGBjYW5ub3Qgc2h1dCBkb3duIGRldmljZSAodGhlIG51bGwgZGV2aWNlKWAKCgotIFdlIGNhbiBzcGVjaWZ5IHRoZSBkaW1lbnNpb25zIG9mIHRoZSBwbG90LCBhbmQgb3RoZXIgcHJvcGVydGllcyBvZiB0aGUgZmlsZSAoYD9wZGZgKQoKYGBge3J9CnBkZigiRXhhbXBsZUdyYXBoLnBkZiIsIHdpZHRoPTEwLCBoZWlnaHQ9NSkKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCmRldi5vZmYoKQpgYGAKCi0gT3RoZXIgZm9ybWF0cyBjYW4gYmUgY3JlYXRlZDoKICAgICsgZS5nLiAqKipwbmcqKiosIG9yIG90aGVycyBgP2pwZWdgCiAgICArIG1vcmUgYXBwcm9wcmlhdGUgZm9yIGVtYWlsLCBwcmVzZW50YXRpb25zLCB3ZWIgcGFnZQogICAgCmBgYHtyfQpwbmcoIkV4YW1wbGVHcmFwaC5wbmciKQpib3hwbG90KHBhdGllbnRzJFdlaWdodCB+IHBhdGllbnRzJFNleCkKZGV2Lm9mZigpCmBgYAoKIyNFeGVyY2lzZTogRXhlcmNpc2UgNWEKLSBSZXR1cm4gdG8gdGhlIHdlYXRoZXIgZGF0YSBmcm9tIHllc3RlcmRheToKCmBgYHtyfQp3ZWF0aGVyIDwtIHJlYWQuY3N2KCJvem9uZS5jc3YiKQpgYGAKCi0gVXNpbmcgdGhlIGBwYXJgIGZ1bmN0aW9uLCBjcmVhdGUgYSBsYXlvdXQgd2l0aCB0aHJlZSBjb2x1bW5zCi0gUGxvdCBPem9uZSB2ZXJzdXMgU29sYXIgUmFkaWF0aW9uLCBXaW5kIFNwZWVkIGFuZCBUZW1wZXJhdHVyZSBvbiBzZXBhcmF0ZSBncmFwaHMKICAgICsgdXNlIGRpZmZlcmVudCBjb2xvdXJzIGFuZCBwbG90dGluZyBjaGFyYWN0ZXJzIG9uIGVhY2ggcGxvdAotIFNhdmUgdGhlIHBsb3QgdG8gYSBwZGYKLSBISU5UOiBDcmVhdGUgdGhlIGdyYXBoIGZpcnN0IGluIFJTdHVkaW8uIFdoZW4geW91J3JlIGhhcHB5IHdpdGggaXQsIHJlLXJ1biB0aGUgY29kZSBwcmVjZWVkZWQgYnkgdGhlIGBwZGZgIGZ1bmN0aW9uIHRvIHNhdmUgdG8gYSBmaWxlCiAgICArIGRvbid0IGZvcmdldCB0byB1c2UgYGRldi5vZmYoKWAgdG8gY2xvc2UgdGhlIGZpbGUKCiFbXShpbWFnZXMvZXhlcmNpc2U1YS5wbmcpCgpgYGB7cn0KIyMjIFlvdXIgQW5zd2VyIEhlcmUgIyMjCgoKYGBgCgoKIyNFeGVyY2lzZTogRXhlcmNpc2UgNWIKLSBUZW1wZXJhdHVyZSBhbmQgT3pvbmUgbGV2ZWwgc2VlbSB0byBiZSBjb3JyZWxhdGVkCi0gSG93ZXZlciwgdGhlcmUgYXJlIHNvbWUgb2JzZXJ2YXRpb25zIHRoYXQgZG8gbm90IHNlZW0gdG8gZml0IHRoZSB0cmVuZAogICAgKyB0aG9zZSB3aXRoIE96b25lIGxldmVsID4gMTAwCi0gTW9kaWZ5IHRoZSBwbG90IHNvIHRoYXQgdGhlc2Ugb3V0bGllciBvYnNlcnZhdGlvbnMgYXJlIGluIGEgZGlmZmVyZW50IGNvbG91cgotIEFkZCBhIGxlZ2VuZCB0byBoZWxwIGludGVycHJldCB0aGUgcGxvdAoKCiFbXShpbWFnZXMvZXhlcmNpc2U1Yi5wbmcpCgoKSElOVDogWW91IGNhbiBicmVhayBkb3duIHRoZSBwcm9ibGVtIGludG8gdGhlIGZvbGxvd2luZyBzdGVwcwoKLSBDcmVhdGUgYSBibGFuayBwbG90Ci0gSWRlbnRpZnkgb2JzZXJ2YXRpb25zIHdpdGggb3pvbmUgPiAxMDAKICAgICsgcGxvdCB0aGUgY29ycmVzcG9uZGluZyBUZW1wZXJhdHVyZSBhbmQgT3pvbmUgdmFsdWVzIGZvciB0aGVzZSBpbiByZWQKLSBJZGVudGlmeSBvYnNlcnZhdGlvbnMgd2l0aCBvem9uZSA8IDEwMAogICAgKyBwbG90IHRoZSBjb3JyZXNwb25kaW5nIFRlbXBlcmF0dXJlIGFuZCBPem9uZSB2YWx1ZXMgZm9yIHRoZXNlIGluIG9yYW5nZQotIENhbiB5b3UgbW9kaWZ5IHlvdXIgY29kZSBzbyB0aGF0IHRoZSBjdXQtb2ZmIGlzIG5vdCBoYXJkLWNvZGVkIChmaXhlZCkgdG8gMTAwCiAgICArIGUuZy4gT3pvbmUgPiA4MCwgT3pvbmUsIDkwIGV0Yy4uLgogICAgKyBjYW4geW91IHJlLWdlbmVyYXRlIHRoZSBwbG90cyB0aGUgbWluaW1hbCBjaGFuZ2VzIHRvIHRoZSBjb2RlCgpgYGB7cn0KCiMjIyBZb3VyIEFuc3dlciBIZXJlICMjIwoKCmBgYAoKQW4gYWx0ZXJuYXRpdmUsIGFuZCBlcXVhbGx5LXZhbGlkLCBzb2x1dGlvbiBpbnZvbHZlcyBjcmVhdGluZyBhIHZlY3RvciBvZiBjb2xvdXJzIHdoaWNoIHdpbGwgZWl0aGVyIGJlIGByZWRgIG9yIGBvcmFuZ2VgIGRlcGVuZGluZyB3aGV0aGVyIG9uIHRoZSBwYXJ0aWN1bGFyIHZhbHVlIG9mIG96b25lIGlzIGFuIG91dGxpZXIKCi0gd2UgY2FuIHVzZSB0aGUgYHJlcGAgZnVuY3Rpb24gdG8gY3JlYXRlIGEgdmVjdG9yIG9mIHRoZSByZXF1aXJlZCBsZW5ndGggZm9yIHRoZSBlbnRpcmUgZGF0YXNldAogICAgKyBvbmUgdmVjdG9yIGZvciBwbG90dGluZyBjb2xvdXJzLCBhbmQgYW5vdGhlciB2ZWN0b3IgZm9yIHBsb3R0aW5nIGNoYXJhY3RlcnMKICAgIApgYGB7cn0Kd2VhdGhlciA8LSByZWFkLmNzdigib3pvbmUuY3N2IikKbXljb2wgPC1yZXAoIm9yYW5nZSIsbnJvdyh3ZWF0aGVyKSkKbXlwY2ggPC0gcmVwKDE3LCBucm93KHdlYXRoZXIpKQoKYGBgCgotIG5vdyB1c2UgYSBsb2dpY2FsIGV4cHJlc3Npb24gdG8gaWRlbnRpZnkgdGhlIGhpZ2ggb3pvbmUgbGV2ZWwsIGFuZCByZXBsYWNlIHRoZSBjb3JyZXNwb25kaW5nIGVudHJpZXMgaW4gdGhlIGNvbG91ciBhbmQgcGxvdHRpbmcgY2hhcmFjdGVyIHZlY3RvcnMKCmBgYHtyfQpoaWdoTyA8LSB3aGljaCh3ZWF0aGVyJE96b25lID4gMTAwKQpteWNvbFtoaWdoT10gPC0gInJlZCIKbXlwY2hbaGlnaE9dIDwtIDE4CmBgYAoKLSBjcmVhdGluZyB0aGUgcGxvdCBjYW4gbm93IGJlIGRvbmUgd2l0aCBhIHNpbmdsZSBgcGxvdGAgY29tbWFuZAoKYGBge3J9CnBsb3Qod2VhdGhlciRUZW1wLHdlYXRoZXIkT3pvbmUsIAogICAgIGNvbD1teWNvbCwgcGNoPW15cGNoLHlsYWI9Ik96b25lIGxldmVsIiwKICAgICB4bGFiPSJUZW1wZXJhdHVyZSIpCmFibGluZShoPTEwMCxsdHk9MikKbGVnZW5kKCJ0b3BsZWZ0IiwgbGVnZW5kID0gYygiT3pvbmUgPiAxMDAiLCJOb3JtYWwgT3pvbmUiKSxjb2w9YygicmVkIiwib3JhbmdlIikscGNoPTE3KQpgYGAKCiMjIE90aGVyIHBsb3R0aW5nIGZlYXR1cmVzCgpXZSBjYW4gY2hvb3NlIG5vdCB0byBzaG93IHRoZSB4LSBhbmQgeS1heGlzIGluIHRoZSBpbml0aWFsIHBsb3QKCmBgYHtyfQpwbG90KHBhdGllbnRzJEFnZSwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYsYXhlcz1GQUxTRSkKYGBgCgpUaGV5IGNhbiBiZSBhZGRlZCBhZnRlcndhcmRzIHdpdGggdGhlIGBheGlzYCBmdW5jdGlvbgogICAgLSBmaXJzdCBhcmd1bWVudCBpcyB3aGV0aGVyIHRoZSBheGlzIGFwcGVhcnMgb24gdGhlIGJvdHRvbSAoMSksIGxlZnQgKDIpLCB0b3AgKDMpIG9yIHJpZ2h0ICg0KQogICAgLSBjYW4gYWxzbyBkZWZpbmUgd2hlcmUgdGhlIHRpY2sgbWFya3MgYXJlIGxvY2F0ZWQsIGFuZCB0aGUgbGFiZWxzIHRvIGRpc3BsYXkKICAgIC0gdGhlIGBib3hgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGVuY2xvc2UgdGhlIHBsb3QgaW4gYSBib3gKYGBge3J9CnBsb3QocGF0aWVudHMkQWdlLCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNixheGVzPUZBTFNFKQpheGlzKDEpCmF4aXMoNCwgYXQgPSBjKDY1LDc1LDg1LDk1KSxsYWJlbHMgPSBjKCI2NWtnIiwiNzVrZyIsIjg1a2ciLCI5NWtnIikpCmJveCgpCgpgYGAKCgoKIyMgT3JkZXJpbmcgdGhlIGJveGVzIGluIGEgc2V0IG9mIGJveHBsb3RzCgpOLkIuIFRoZSBvcmRlciBpbiB3aGljaCB0aGUgYm94ZXMgYXJlIGRpc3BsYXllZCBvbiBhIGJveHBsb3QgaXNuJ3Qgc29tZXRoaW5nIHRoYXQgY2FuIGJlIGNvbnRyb2xsZWQgYnkgZ3JhcGhpY2FsIHBhcmFtYXRlcnMKCi0gdGhlIG9yZGVyIGlzIGRlcml2ZWQgZnJvbSB0aGUgb3JkZXIgb2YgdGhlIGNhdGVnb3JpZXMKICAgICsgaW4gdGhpcyBjYXNlIGBGZW1hbGVgIGNvbWVzIGZpcnN0IGFscGhhYmV0aWNhbGx5CgpgYGB7cn0KYm94cGxvdChwYXRpZW50cyRXZWlnaHR+cGF0aWVudHMkU2V4KQpgYGAKCi0gYSBkaWZmZXJlbnQgb3JkZXIgY2FuIGJlIGFjaGlldmVkIGJ5IGRlZmluaW5nIGEgbmV3IGZhY3RvciB3aGVyZSB0aGUgYGxldmVsc2AgYXJlIGluIHRoZSBvcmRlciB5b3Ugd2FudAoKYGBge3J9CnBhdGllbnRzJFNleCA8LSBmYWN0b3IocGF0aWVudHMkU2V4LGxldmVscyA9IGMoIk1hbGUiLCJGZW1hbGUiKSkKYm94cGxvdChwYXRpZW50cyRXZWlnaHR+cGF0aWVudHMkU2V4KQpgYGAKCgojIyBOZWVkIGluc3BpcmF0aW9uPwoKVGhlIFtSIEdyYXBoIEdhbGxlcnldKGh0dHA6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8pIGhhcyBsb3RzIG9mIGV4YW1wbGVzIChhbmQgY29kZSkgc2hvd2luZyB3aGF0IGNhbiBiZSBhY2hpZXZlZCB3aXRoIFIgZ3JhcGhpY3M=