R memiliki sekumpulan fasilitas grafik yang sangat kaya. Informasi ini dapat dilihat pada R Graph Gallery (Matloff, 2011)

Salah satu keunggulan R dibandingkan dengan software statistika lainnya adalah kemampuan menghasilkan grafik yang sangat kaya, baik untuk membuat plot untuk eksplorasi data awal, validasi model, atau untuk keperluan publikasi. Setidaknya ada tiga sistem utama untuk menghasilkan grafik dalam R yaitu grafik R dasar (base R graphics), lattice dan ggplot2. Masing-masing sistem ini memiliki kelebihan dan kelemahannya masing-masing.

Grafik R dasar

Untuk memulai mari lihat fungsi dasar dalam membuat grafik: plot(). Kemudian eksplorasi tahapan membuat grafik dengan menambahkan garis dan titik untuk melampirkan legenda.

Fungsi plot()

Pilihan tipe yang digunakan:

  • p” for points,
  • l” for lines,
  • b” for both,
  • c” for the lines part alone of “b”,
  • o” for both ‘overplotted’,
  • h” for ‘histogram’ like (or ‘high-density’) vertical lines,
  • s” for stair steps,
  • S” for other steps, see ‘Details’ below,
  • n” for no plotting.

Buatlah scatter plot seperti gambar berikut

# data awal
x <- 1:40
y <- rnorm(40,5,1)

# data baru
x1 <- 41:50
y1 <- rnorm(10,5,1)

# scatter plot dg data awal
plot(x, y, type="p", 
     xlab="Sumbu x",
     ylab="Sumbu y",
     main="Bilangan Acak Normal",
     col=topo.colors(40),
     pch=16, cex=2, xlim=c(0,50), ylim=c(2.5,7.5))

# plot data baru
points(x1, y1, cex=2)

#menambahkan garis
x2 <- rep(40.5,20)
y2 <- seq(min(c(y,y1)), max(c(y,y1)), length=20)

abline(v = 40.2, col="blue")
abline(h=mean(y), col="red", lwd=2.5)
abline(a=2, b=1/10, col="purple", lwd=2, lty=2)

#menambahkan tanda panah
arrows(x0=30, y0=3.5, x1=40, y1=mean(y)-.1, lwd=2)

#menambahkan tulisan
text(x=29,y=3.3, labels="Titik potong", cex=0.7)
text(x=3,y=7.3, labels="Data awal", cex=0.7)
text(x=46,y=7.3, labels="Data baru", cex=0.7)

data("mtcars")
head(mtcars)

Histogram

Membuat histogram dapat dilakukan dengan fungsi hist()

hist(mtcars$mpg)

Bar plot

counts <- table(mtcars$cyl)
counts 

 4  6  8 
11  7 14 
barplot(counts)

barplot(counts,
        col = c("#ffd166","#06d6a0"), #custom colors
        legend = rownames(counts),
        cex.names=.8)

Scatter plot

x = seq(from = -2*pi, to = 2*pi, length.out = 100)
y = 2*sin(x)
plot(x,y)

plot(mtcars$hp, mtcars$mpg)

Line plot

plot(x,y, type = "l")

plot(x,y, type = "h")

x <- 1:40
y <- rnorm(40,5,1)
plot(x,y,type="o")

Pie chart

pie(table(iris$Species))

mytable <- table(iris$Species)
lbls <- paste(names(mytable), "\n", mytable, sep="")
pie(mytable, labels = lbls,
   main="Pie Chart of Species\n (with sample sizes)")

Boxplot

boxplot(iris$Sepal.Length)

boxplot(Sepal.Length ~ Species, data = iris, cex.axis=.8)

boxplot(Sepal.Length ~ Species, data = iris,
        col = RColorBrewer::brewer.pal(length(unique(iris$Species)), 'Set2'),
        cex.axis=.8)

Layout

pairs(mtcars[,c(1:5)])

Buatlah grafik seperti berikut

Latihan plot()

Latihan 1

Buatlah density histogram dan kurva dari contoh acak yang menyebar Chi-Squared dengan derajat bebas 4.

Latihan 2

Buat empat grafik dalam satu windows, dengan format berikut:

ggplot2

library(ggplot2)

Histogram

ggplot(data.frame(value=rnorm(100)), aes(x=value)) + 
  geom_histogram()

Scatterplot

ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
    geom_point(size=6) 

Barplot

ggplot(mtcars, aes(x=as.factor(cyl), fill=as.factor(cyl) )) + 
  geom_bar( ) +
  theme(legend.position="none")

Pie chart

library(dplyr)

Attaching package: ‘dplyr’

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

    filter, lag

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

    intersect, setdiff, setequal, union
# Create Data
data <- data.frame(
  group=LETTERS[1:5],
  value=c(13,7,9,21,2)
)

# Compute the position of labels
data <- data %>% 
  arrange(desc(group)) %>%
  mutate(prop = value / sum(data$value) *100) %>%
  mutate(ypos = cumsum(prop)- 0.5*prop )

# Basic piechart
ggplot(data, aes(x="", y=prop, fill=group)) +
  geom_bar(stat="identity", width=1, color="white") +
  coord_polar("y", start=0) +
  theme_void() + 
  theme(legend.position="none") +
  
  geom_text(aes(y = ypos, label = group), color = "white", size=6) +
  scale_fill_brewer(palette="Set1")

Line plot

ggplot(data.frame(xValue=1:10,yValue=cumsum(rnorm(10))), aes(x=xValue, y=yValue)) +
  geom_line()

plotly

library(plotly)
# https://plotly.com/r/heatmaps/
fig <- plot_ly(z = volcano, type = "heatmap")
fig

from Data to Viz

# https://www.r-graph-gallery.com/321-introduction-to-interactive-sankey-diagram-2.html
library(networkD3)
Warning: package ‘networkD3’ was built under R version 4.2.2
library(dplyr)
 
# A connection data frame is a list of flows with intensity for each flow
links <- data.frame(
  source=c("group_A","group_A", "group_B", "group_C", "group_C", "group_E"), 
  target=c("group_C","group_D", "group_E", "group_F", "group_G", "group_H"), 
  value=c(2,3, 2, 3, 1, 3)
  )
 
# From these flows we need to create a node data frame: it lists every entities involved in the flow
nodes <- data.frame(
  name=c(as.character(links$source), 
  as.character(links$target)) %>% unique()
)
 
# With networkD3, connection must be provided using id, not using real name like in the links dataframe.. So we need to reformat it.
links$IDsource <- match(links$source, nodes$name)-1 
links$IDtarget <- match(links$target, nodes$name)-1
 
# Make the Network
p <- sankeyNetwork(Links = links, Nodes = nodes,
              Source = "IDsource", Target = "IDtarget",
              Value = "value", NodeID = "name", 
              sinksRight=FALSE)
p

Remove to improve

Visualisasi data spasial

Membaca data

library(sf)
mapBogor <- st_read("data/bogor.shp", quiet = TRUE)

Shape file diperoleh dari: data.humdata.org

dplyr::glimpse(mapBogor)
Rows: 6
Columns: 17
$ Shape_Leng <dbl> 0.3644271, 0.3413273, 0.1849809, 0.1943379, 0.2430896, 0.2357695
$ Shape_Area <dbl> 0.0018624008, 0.0025144096, 0.0006540943, 0.0008671386, 0.0015239343,…
$ ADM3_EN    <chr> "Bogor Barat", "Bogor Selatan", "Bogor Tengah", "Bogor Timur", "Bogor…
$ ADM3_PCODE <chr> "ID3271050", "ID3271010", "ID3271040", "ID3271020", "ID3271030", "ID3…
$ ADM3_REF   <chr> NA, NA, NA, NA, NA, NA
$ ADM3ALT1EN <chr> NA, NA, NA, NA, NA, NA
$ ADM3ALT2EN <chr> NA, NA, NA, NA, NA, NA
$ ADM2_EN    <chr> "Kota Bogor", "Kota Bogor", "Kota Bogor", "Kota Bogor", "Kota Bogor",…
$ ADM2_PCODE <chr> "ID3271", "ID3271", "ID3271", "ID3271", "ID3271", "ID3271"
$ ADM1_EN    <chr> "Jawa Barat", "Jawa Barat", "Jawa Barat", "Jawa Barat", "Jawa Barat",…
$ ADM1_PCODE <chr> "ID32", "ID32", "ID32", "ID32", "ID32", "ID32"
$ ADM0_EN    <chr> "Indonesia", "Indonesia", "Indonesia", "Indonesia", "Indonesia", "Ind…
$ ADM0_PCODE <chr> "ID", "ID", "ID", "ID", "ID", "ID"
$ date       <date> 2019-12-20, 2019-12-20, 2019-12-20, 2019-12-20, 2019-12-20, 2019-12-2…
$ validOn    <date> 2020-04-01, 2020-04-01, 2020-04-01, 2020-04-01, 2020-04-01, 2020-04-0…
$ validTo    <date> NA, NA, NA, NA, NA, NA
$ geometry   <POLYGON [°]> POLYGON ((106.7649 -6.53983..., POLYGON ((106.7961 -6.60695..., POLYG…
dataBogor <- readRDS("data/bogor.rds")
dataBogor
mapBogor<- mapBogor %>%
  dplyr::inner_join(dataBogor, by = c("ADM3_PCODE" = "KodeBPS"))
mapBogor
Simple feature collection with 6 features and 22 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 106.735 ymin: -6.679585 xmax: 106.8488 ymax: -6.510802
Geodetic CRS:  WGS 84
  Shape_Leng   Shape_Area       ADM3_EN ADM3_PCODE ADM3_REF ADM3ALT1EN ADM3ALT2EN
1  0.3644271 0.0018624008   Bogor Barat  ID3271050     <NA>       <NA>       <NA>
2  0.3413273 0.0025144096 Bogor Selatan  ID3271010     <NA>       <NA>       <NA>
3  0.1849809 0.0006540943  Bogor Tengah  ID3271040     <NA>       <NA>       <NA>
4  0.1943379 0.0008671386   Bogor Timur  ID3271020     <NA>       <NA>       <NA>
5  0.2430896 0.0015239343   Bogor Utara  ID3271030     <NA>       <NA>       <NA>
6  0.2357695 0.0017147180  Tanah Sereal  ID3271060     <NA>       <NA>       <NA>
     ADM2_EN ADM2_PCODE    ADM1_EN ADM1_PCODE   ADM0_EN ADM0_PCODE       date    validOn
1 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
2 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
3 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
4 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
5 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
6 Kota Bogor     ID3271 Jawa Barat       ID32 Indonesia         ID 2019-12-20 2020-04-01
  validTo       Kota     Kecamatan KodeKemendagri JumlahPenduduk LuasWilayah
1    <NA> Kota Bogor   Bogor Barat       32.71.04         244708       23.08
2    <NA> Kota Bogor Bogor Selatan       32.71.04         208185       31.16
3    <NA> Kota Bogor  Bogor Tengah       32.71.04         106359        8.11
4    <NA> Kota Bogor   Bogor Timur       32.71.04         105233       10.75
5    <NA> Kota Bogor   Bogor Utara       32.71.04         192837       18.88
6    <NA> Kota Bogor  Tanah Sareal       32.71.04         218135       21.25
  KepadatanPenduduk                       geometry
1             10603 POLYGON ((106.7649 -6.53983...
2              6681 POLYGON ((106.7961 -6.60695...
3             13115 POLYGON ((106.7885 -6.57350...
4              9789 POLYGON ((106.8315 -6.60529...
5             10214 POLYGON ((106.8183 -6.53854...
6             10265 POLYGON ((106.7822 -6.51169...

Peta Statis

Peta statis dapat dibuat menggunakan ggplot2

p <- ggplot() + geom_sf(data=mapBogor, aes(fill=JumlahPenduduk))
p

colorPalette = RColorBrewer::brewer.pal(5,"YlGnBu")
legendBreak = c(120,170,230)*1000
yBreak = seq(106.72, 106.86, by=0.04)

p + scale_fill_gradientn(colors = colorPalette, 
                       breaks = legendBreak, 
                       name = "Jumlah Penduduk") +
  labs(title = "Jumlah Penduduk Kota Bogor")  +
  theme(legend.text = element_text(size=7),
        legend.title = element_text(size=7),
        axis.text.x = element_text(size = 7),
        axis.text.y = element_text(size = 7),
        title = element_text(size=12, face='bold')) +
  scale_x_continuous(breaks = yBreak) 

Peta Interaktif

Peta statis dapat dibuat menggunakan leaflet

# opsional, jika belum terinstal
install.packages("leaflet")
library(leaflet)

Berikut perintah-perintah untuk menampilkan jumlah penduduk Kota Bogor dengan peta leaflet.

  1. leaflet(): inisiasi peta dengan memanggil fungsi leaflet()
  2. addProviderTiles(): menambahkan peta dasar (base map) dengan perintah
  3. addPolygons(): menabahkan poligon dengan gradasi warna berdasarkan jumlah penduduk. Pengaturan warna gradasi menggunakan colorNumeric(). Ditambahkan pula opsi label untu menampilkan popup, yang akan muncul ketika pengguna menyorot area tertentu.
  4. addLegend(): menambahkan legenda
  5. addLayersControl(): menampilkan tombol untuk memilih layer yang akan ditampilkan
  6. setView(): mengatur posisi dan zooming default
# membuat custom palette warna
populationPalette <- colorNumeric(
  palette = "YlGnBu",
  domain = mapBogor$JumlahPenduduk
)

# membuat custom popup
popupLabel <- paste0(
    "<b>Kecamatan ", mapBogor$Kecamatan,"</b><br/>", 
    "Jumlah Penduduk (jiwa): ", mapBogor$JumlahPenduduk, "<br/>", 
    "Luas Wilayah (km2): ", mapBogor$LuasWilayah, "<br/>", 
    "Kepadatan Penduduk (jiwa/km2): ", mapBogor$KepadatanPenduduk) %>%
  lapply(htmltools::HTML)

# membuat peta leaflet
leaflet(mapBogor) %>% 
  addProviderTiles(providers$CartoDB.PositronNoLabels, group = "Light Mode") %>%
  addProviderTiles(providers$CartoDB.DarkMatterNoLabels, group = "Dark Mode") %>%
  
  addPolygons(weight = 1,
              opacity = 1, 
              fillOpacity = 0.9,
              label = popupLabel,
              color = ~populationPalette(JumlahPenduduk),
              highlightOptions = highlightOptions(color = "white", 
                                                  weight = 2, 
                                                  bringToFront = TRUE) ) %>%
  addLegend(position = "bottomright", 
            pal = populationPalette, 
            values = ~JumlahPenduduk,
            title = "Jumlah\nPenduduk",
            opacity = 1) %>%
  
  addLayersControl(position = 'topright',
                   baseGroups = c("Light Mode", "Dark Mode"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  
  setView(lat = - 6.595, lng = 106.87, zoom = 12)

  1. Statistika dan Sains Data, IPB University, ↩︎

LS0tDQp0aXRsZTogIkdyYWZpayBkYWxhbSBSIg0KYXV0aG9yOiBBbGZhIE51Z3JhaGFeW1N0YXRpc3Rpa2EgZGFuIFNhaW5zIERhdGEsIElQQiBVbml2ZXJzaXR5LCBhbGZhbnVncmFoYUBhcHBzLmlwYi5hYy5pZF0NCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQoNCj5SIG1lbWlsaWtpIHNla3VtcHVsYW4gZmFzaWxpdGFzIGdyYWZpayB5YW5nIHNhbmdhdCBrYXlhLiBJbmZvcm1hc2kgaW5pIGRhcGF0IGRpbGloYXQgcGFkYSBbUiBHcmFwaCBHYWxsZXJ5XShodHRwczovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tLykgKE1hdGxvZmYsIDIwMTEpDQoNClNhbGFoIHNhdHUga2V1bmdndWxhbiBSIGRpYmFuZGluZ2thbiBkZW5nYW4gKnNvZnR3YXJlKiBzdGF0aXN0aWthIGxhaW5ueWEgYWRhbGFoIGtlbWFtcHVhbiBtZW5naGFzaWxrYW4gZ3JhZmlrIHlhbmcgc2FuZ2F0IGtheWEsIGJhaWsgdW50dWsgbWVtYnVhdCBwbG90IHVudHVrIGVrc3Bsb3Jhc2kgZGF0YSBhd2FsLCB2YWxpZGFzaSBtb2RlbCwgYXRhdSB1bnR1ayBrZXBlcmx1YW4gcHVibGlrYXNpLiBTZXRpZGFrbnlhIGFkYSB0aWdhIHNpc3RlbSB1dGFtYSB1bnR1ayBtZW5naGFzaWxrYW4gZ3JhZmlrIGRhbGFtIFIgeWFpdHUgZ3JhZmlrIFIgZGFzYXIgKCpiYXNlIFIgZ3JhcGhpY3MqKSwgYGxhdHRpY2VgIGRhbiBgZ2dwbG90MmAuIE1hc2luZy1tYXNpbmcgc2lzdGVtIGluaSBtZW1pbGlraSBrZWxlYmloYW4gZGFuIGtlbGVtYWhhbm55YSBtYXNpbmctbWFzaW5nLg0KDQojIyBHcmFmaWsgUiBkYXNhcg0KDQpVbnR1ayBtZW11bGFpIG1hcmkgbGloYXQgZnVuZ3NpIGRhc2FyIGRhbGFtIG1lbWJ1YXQgZ3JhZmlrOiBgcGxvdCgpYC4gS2VtdWRpYW4gZWtzcGxvcmFzaSB0YWhhcGFuIG1lbWJ1YXQgZ3JhZmlrIGRlbmdhbiBtZW5hbWJhaGthbiBnYXJpcyBkYW4gdGl0aWsgdW50dWsgbWVsYW1waXJrYW4gbGVnZW5kYS4NCg0KIyMjIEZ1bmdzaSBgcGxvdCgpYA0KDQpQaWxpaGFuIHRpcGUgeWFuZyBkaWd1bmFrYW46DQoNCi0gImBwYCIgZm9yIHBvaW50cywNCi0gImBsYCIgZm9yIGxpbmVzLA0KLSAiYGJgIiBmb3IgYm90aCwNCi0gImBjYCIgZm9yIHRoZSBsaW5lcyBwYXJ0IGFsb25lIG9mICJiIiwNCi0gImBvYCIgZm9yIGJvdGgg4oCYb3ZlcnBsb3R0ZWTigJksDQotICJgaGAiIGZvciDigJhoaXN0b2dyYW3igJkgbGlrZSAob3Ig4oCYaGlnaC1kZW5zaXR54oCZKSB2ZXJ0aWNhbCBsaW5lcywNCi0gImBzYCIgZm9yIHN0YWlyIHN0ZXBzLA0KLSAiYFNgIiBmb3Igb3RoZXIgc3RlcHMsIHNlZSDigJhEZXRhaWxz4oCZIGJlbG93LA0KLSAiYG5gIiBmb3Igbm8gcGxvdHRpbmcuDQoNCkJ1YXRsYWggKnNjYXR0ZXIgcGxvdCogc2VwZXJ0aSBnYW1iYXIgYmVyaWt1dA0KDQohW10oaW1nL3Bsb3QtdHJlbmQucG5nKQ0KDQpgYGB7cn0NCiMgZGF0YSBhd2FsDQp4IDwtIDE6NDANCnkgPC0gcm5vcm0oNDAsNSwxKQ0KDQojIGRhdGEgYmFydQ0KeDEgPC0gNDE6NTANCnkxIDwtIHJub3JtKDEwLDUsMSkNCg0KIyBzY2F0dGVyIHBsb3QgZGcgZGF0YSBhd2FsDQpwbG90KHgsIHksIHR5cGU9InAiLCANCiAgICAgeGxhYj0iU3VtYnUgeCIsDQogICAgIHlsYWI9IlN1bWJ1IHkiLA0KICAgICBtYWluPSJCaWxhbmdhbiBBY2FrIE5vcm1hbCIsDQogICAgIGNvbD10b3BvLmNvbG9ycyg0MCksDQogICAgIHBjaD0xNiwgY2V4PTIsIHhsaW09YygwLDUwKSwgeWxpbT1jKDIuNSw3LjUpKQ0KDQojIHBsb3QgZGF0YSBiYXJ1DQpwb2ludHMoeDEsIHkxLCBjZXg9MikNCg0KI21lbmFtYmFoa2FuIGdhcmlzDQp4MiA8LSByZXAoNDAuNSwyMCkNCnkyIDwtIHNlcShtaW4oYyh5LHkxKSksIG1heChjKHkseTEpKSwgbGVuZ3RoPTIwKQ0KDQphYmxpbmUodiA9IDQwLjIsIGNvbD0iYmx1ZSIpDQphYmxpbmUoaD1tZWFuKHkpLCBjb2w9InJlZCIsIGx3ZD0yLjUpDQphYmxpbmUoYT0yLCBiPTEvMTAsIGNvbD0icHVycGxlIiwgbHdkPTIsIGx0eT0yKQ0KDQojbWVuYW1iYWhrYW4gdGFuZGEgcGFuYWgNCmFycm93cyh4MD0zMCwgeTA9My41LCB4MT00MCwgeTE9bWVhbih5KS0uMSwgbHdkPTIpDQoNCiNtZW5hbWJhaGthbiB0dWxpc2FuDQp0ZXh0KHg9MjkseT0zLjMsIGxhYmVscz0iVGl0aWsgcG90b25nIiwgY2V4PTAuNykNCnRleHQoeD0zLHk9Ny4zLCBsYWJlbHM9IkRhdGEgYXdhbCIsIGNleD0wLjcpDQp0ZXh0KHg9NDYseT03LjMsIGxhYmVscz0iRGF0YSBiYXJ1IiwgY2V4PTAuNykNCmBgYA0KDQoNCmBgYHtyfQ0KZGF0YSgibXRjYXJzIikNCmhlYWQobXRjYXJzKQ0KYGBgDQoNCg0KIyMjIEhpc3RvZ3JhbQ0KDQpNZW1idWF0IGhpc3RvZ3JhbSBkYXBhdCBkaWxha3VrYW4gZGVuZ2FuIGZ1bmdzaSBgaGlzdCgpYA0KDQpgYGB7cn0NCmhpc3QobXRjYXJzJG1wZykNCmBgYA0KDQojIyMgQmFyIHBsb3QNCg0KYGBge3J9DQpjb3VudHMgPC0gdGFibGUobXRjYXJzJGN5bCkNCmNvdW50cyANCmBgYA0KDQpgYGB7cn0NCmJhcnBsb3QoY291bnRzKQ0KYGBgDQoNCmBgYHtyfQ0KYmFycGxvdChjb3VudHMsDQogICAgICAgIGNvbCA9IGMoIiNmZmQxNjYiLCIjMDZkNmEwIiksICNjdXN0b20gY29sb3JzDQogICAgICAgIGxlZ2VuZCA9IHJvd25hbWVzKGNvdW50cyksDQogICAgICAgIGNleC5uYW1lcz0uOCkNCmBgYA0KDQojIyMgU2NhdHRlciBwbG90DQoNCmBgYHtyfQ0KeCA9IHNlcShmcm9tID0gLTIqcGksIHRvID0gMipwaSwgbGVuZ3RoLm91dCA9IDEwMCkNCnkgPSAyKnNpbih4KQ0KcGxvdCh4LHkpDQpgYGANCg0KYGBge3J9DQpwbG90KG10Y2FycyRocCwgbXRjYXJzJG1wZykNCmBgYA0KDQojIyMgTGluZSBwbG90DQoNCmBgYHtyfQ0KcGxvdCh4LHksIHR5cGUgPSAibCIpDQpgYGANCg0KYGBge3J9DQpwbG90KHgseSwgdHlwZSA9ICJoIikNCmBgYA0KDQpgYGB7cn0NCnggPC0gMTo0MA0KeSA8LSBybm9ybSg0MCw1LDEpDQpwbG90KHgseSx0eXBlPSJvIikNCmBgYA0KDQoNCiMjIyBQaWUgY2hhcnQNCg0KYGBge3J9DQpwaWUodGFibGUoaXJpcyRTcGVjaWVzKSkNCmBgYA0KDQpgYGB7cn0NCm15dGFibGUgPC0gdGFibGUoaXJpcyRTcGVjaWVzKQ0KbGJscyA8LSBwYXN0ZShuYW1lcyhteXRhYmxlKSwgIlxuIiwgbXl0YWJsZSwgc2VwPSIiKQ0KcGllKG15dGFibGUsIGxhYmVscyA9IGxibHMsDQogICBtYWluPSJQaWUgQ2hhcnQgb2YgU3BlY2llc1xuICh3aXRoIHNhbXBsZSBzaXplcykiKQ0KYGBgDQoNCg0KIyMjIEJveHBsb3QNCg0KYGBge3J9DQpib3hwbG90KGlyaXMkU2VwYWwuTGVuZ3RoKQ0KYGBgDQoNCmBgYHtyfQ0KYm94cGxvdChTZXBhbC5MZW5ndGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcywgY2V4LmF4aXM9LjgpDQpgYGANCg0KYGBge3J9DQpib3hwbG90KFNlcGFsLkxlbmd0aCB+IFNwZWNpZXMsIGRhdGEgPSBpcmlzLA0KICAgICAgICBjb2wgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwobGVuZ3RoKHVuaXF1ZShpcmlzJFNwZWNpZXMpKSwgJ1NldDInKSwNCiAgICAgICAgY2V4LmF4aXM9LjgpDQpgYGANCg0KIyMjIExheW91dA0KDQpgYGB7cn0NCnBhaXJzKG10Y2Fyc1ssYygxOjUpXSkNCmBgYA0KDQpCdWF0bGFoIGdyYWZpayBzZXBlcnRpIGJlcmlrdXQNCg0KIVtdKGltZy9wbG90LTJ4Mi5wbmcpIA0KDQoNCiMjIExhdGloYW4gYHBsb3QoKWANCg0KIyMjIExhdGloYW4gMQ0KDQpCdWF0bGFoICpkZW5zaXR5KiBoaXN0b2dyYW0gZGFuIGt1cnZhIGRhcmkgY29udG9oIGFjYWsgeWFuZyBtZW55ZWJhciBDaGktU3F1YXJlZCBkZW5nYW4gZGVyYWphdCBiZWJhcyA0Lg0KDQoNCiMjIyBMYXRpaGFuIDINCg0KQnVhdCBlbXBhdCBncmFmaWsgZGFsYW0gc2F0dSB3aW5kb3dzLCBkZW5nYW4gZm9ybWF0IGJlcmlrdXQ6DQoNCiFbXShpbWcvZ3JhcGgtZ3JpZC5qcGVnKQ0KDQoNCg0KIyMgYGdncGxvdDJgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCiMjIyBIaXN0b2dyYW0NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YS5mcmFtZSh2YWx1ZT1ybm9ybSgxMDApKSwgYWVzKHg9dmFsdWUpKSArIA0KICBnZW9tX2hpc3RvZ3JhbSgpDQpgYGANCg0KDQojIyMgU2NhdHRlcnBsb3QNCg0KYGBge3J9DQpnZ3Bsb3QoaXJpcywgYWVzKHg9U2VwYWwuTGVuZ3RoLCB5PVNlcGFsLldpZHRoLCBjb2xvcj1TcGVjaWVzKSkgKyANCiAgICBnZW9tX3BvaW50KHNpemU9NikgDQpgYGANCg0KDQojIyMgQmFycGxvdA0KDQpgYGB7cn0NCmdncGxvdChtdGNhcnMsIGFlcyh4PWFzLmZhY3RvcihjeWwpLCBmaWxsPWFzLmZhY3RvcihjeWwpICkpICsgDQogIGdlb21fYmFyKCApICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikNCmBgYA0KDQoNCiMjIyBQaWUgY2hhcnQNCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KDQojIENyZWF0ZSBEYXRhDQpkYXRhIDwtIGRhdGEuZnJhbWUoDQogIGdyb3VwPUxFVFRFUlNbMTo1XSwNCiAgdmFsdWU9YygxMyw3LDksMjEsMikNCikNCg0KIyBDb21wdXRlIHRoZSBwb3NpdGlvbiBvZiBsYWJlbHMNCmRhdGEgPC0gZGF0YSAlPiUgDQogIGFycmFuZ2UoZGVzYyhncm91cCkpICU+JQ0KICBtdXRhdGUocHJvcCA9IHZhbHVlIC8gc3VtKGRhdGEkdmFsdWUpICoxMDApICU+JQ0KICBtdXRhdGUoeXBvcyA9IGN1bXN1bShwcm9wKS0gMC41KnByb3AgKQ0KDQojIEJhc2ljIHBpZWNoYXJ0DQpnZ3Bsb3QoZGF0YSwgYWVzKHg9IiIsIHk9cHJvcCwgZmlsbD1ncm91cCkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCB3aWR0aD0xLCBjb2xvcj0id2hpdGUiKSArDQogIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQ9MCkgKw0KICB0aGVtZV92b2lkKCkgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKw0KICANCiAgZ2VvbV90ZXh0KGFlcyh5ID0geXBvcywgbGFiZWwgPSBncm91cCksIGNvbG9yID0gIndoaXRlIiwgc2l6ZT02KSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKQ0KYGBgDQoNCg0KIyMjIExpbmUgcGxvdA0KDQpgYGB7cn0NCmdncGxvdChkYXRhLmZyYW1lKHhWYWx1ZT0xOjEwLHlWYWx1ZT1jdW1zdW0ocm5vcm0oMTApKSksIGFlcyh4PXhWYWx1ZSwgeT15VmFsdWUpKSArDQogIGdlb21fbGluZSgpDQpgYGANCg0KDQoNCiMjIGBwbG90bHlgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBodHRwczovL3Bsb3RseS5jb20vci9oZWF0bWFwcy8NCmZpZyA8LSBwbG90X2x5KHogPSB2b2xjYW5vLCB0eXBlID0gImhlYXRtYXAiKQ0KZmlnDQpgYGANCg0KDQojIyBmcm9tIERhdGEgdG8gVml6IA0KDQpgYGB7cn0NCiMgaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8zMjEtaW50cm9kdWN0aW9uLXRvLWludGVyYWN0aXZlLXNhbmtleS1kaWFncmFtLTIuaHRtbA0KbGlicmFyeShuZXR3b3JrRDMpDQpsaWJyYXJ5KGRwbHlyKQ0KIA0KIyBBIGNvbm5lY3Rpb24gZGF0YSBmcmFtZSBpcyBhIGxpc3Qgb2YgZmxvd3Mgd2l0aCBpbnRlbnNpdHkgZm9yIGVhY2ggZmxvdw0KbGlua3MgPC0gZGF0YS5mcmFtZSgNCiAgc291cmNlPWMoImdyb3VwX0EiLCJncm91cF9BIiwgImdyb3VwX0IiLCAiZ3JvdXBfQyIsICJncm91cF9DIiwgImdyb3VwX0UiKSwgDQogIHRhcmdldD1jKCJncm91cF9DIiwiZ3JvdXBfRCIsICJncm91cF9FIiwgImdyb3VwX0YiLCAiZ3JvdXBfRyIsICJncm91cF9IIiksIA0KICB2YWx1ZT1jKDIsMywgMiwgMywgMSwgMykNCiAgKQ0KIA0KIyBGcm9tIHRoZXNlIGZsb3dzIHdlIG5lZWQgdG8gY3JlYXRlIGEgbm9kZSBkYXRhIGZyYW1lOiBpdCBsaXN0cyBldmVyeSBlbnRpdGllcyBpbnZvbHZlZCBpbiB0aGUgZmxvdw0Kbm9kZXMgPC0gZGF0YS5mcmFtZSgNCiAgbmFtZT1jKGFzLmNoYXJhY3RlcihsaW5rcyRzb3VyY2UpLCANCiAgYXMuY2hhcmFjdGVyKGxpbmtzJHRhcmdldCkpICU+JSB1bmlxdWUoKQ0KKQ0KIA0KIyBXaXRoIG5ldHdvcmtEMywgY29ubmVjdGlvbiBtdXN0IGJlIHByb3ZpZGVkIHVzaW5nIGlkLCBub3QgdXNpbmcgcmVhbCBuYW1lIGxpa2UgaW4gdGhlIGxpbmtzIGRhdGFmcmFtZS4uIFNvIHdlIG5lZWQgdG8gcmVmb3JtYXQgaXQuDQpsaW5rcyRJRHNvdXJjZSA8LSBtYXRjaChsaW5rcyRzb3VyY2UsIG5vZGVzJG5hbWUpLTEgDQpsaW5rcyRJRHRhcmdldCA8LSBtYXRjaChsaW5rcyR0YXJnZXQsIG5vZGVzJG5hbWUpLTENCiANCiMgTWFrZSB0aGUgTmV0d29yaw0KcCA8LSBzYW5rZXlOZXR3b3JrKExpbmtzID0gbGlua3MsIE5vZGVzID0gbm9kZXMsDQogICAgICAgICAgICAgIFNvdXJjZSA9ICJJRHNvdXJjZSIsIFRhcmdldCA9ICJJRHRhcmdldCIsDQogICAgICAgICAgICAgIFZhbHVlID0gInZhbHVlIiwgTm9kZUlEID0gIm5hbWUiLCANCiAgICAgICAgICAgICAgc2lua3NSaWdodD1GQUxTRSkNCnANCmBgYA0KDQoNCiMjIFJlbW92ZSB0byBpbXByb3ZlDQoNCiFbUmVtb3ZlIHRvIGltcHJvdmUuIFN1bWJlcjogaHR0cHM6Ly93d3cuZGFya2hvcnNlYW5hbHl0aWNzLmNvbS9ibG9nL2RhdGEtbG9va3MtYmV0dGVyLW5ha2VkXShpbWcvaW1hZ2UuZ2lmKQ0KDQojIyBWaXN1YWxpc2FzaSBkYXRhIHNwYXNpYWwNCg0KDQojIyMgTWVtYmFjYSBkYXRhDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpsaWJyYXJ5KHNmKQ0KbWFwQm9nb3IgPC0gc3RfcmVhZCgiZGF0YS9ib2dvci5zaHAiLCBxdWlldCA9IFRSVUUpDQpgYGANCg0KU2hhcGUgZmlsZSBkaXBlcm9sZWggZGFyaTogW2RhdGEuaHVtZGF0YS5vcmddKGh0dHBzOi8vZGF0YS5odW1kYXRhLm9yZy9kYXRhc2V0L2luZG9uZXNpYS1hZG1pbmlzdHJhdGl2ZS1ib3VuZGFyeS1wb2x5Z29ucy1saW5lcy1hbmQtcGxhY2VzLWxldmVscy0wLTRiKQ0KDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShzZikNCm1hcEJvZ29yIDwtIHN0X3JlYWQoImRhdGEvYm9nb3Iuc2hwIiwgcXVpZXQgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0KZHBseXI6OmdsaW1wc2UobWFwQm9nb3IpDQpgYGANCg0KDQpgYGB7cn0NCmRhdGFCb2dvciA8LSByZWFkUkRTKCJkYXRhL2JvZ29yLnJkcyIpDQpkYXRhQm9nb3INCmBgYA0KDQoNCmBgYHtyfQ0KbWFwQm9nb3I8LSBtYXBCb2dvciAlPiUNCiAgZHBseXI6OmlubmVyX2pvaW4oZGF0YUJvZ29yLCBieSA9IGMoIkFETTNfUENPREUiID0gIktvZGVCUFMiKSkNCm1hcEJvZ29yDQpgYGANCg0KIyMjIFBldGEgU3RhdGlzIA0KDQpQZXRhIHN0YXRpcyBkYXBhdCBkaWJ1YXQgbWVuZ2d1bmFrYW4gYGdncGxvdDJgDQoNCmBgYHtyfQ0KcCA8LSBnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YT1tYXBCb2dvciwgYWVzKGZpbGw9SnVtbGFoUGVuZHVkdWspKQ0KcA0KYGBgDQoNCg0KYGBge3J9DQpjb2xvclBhbGV0dGUgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNSwiWWxHbkJ1IikNCmxlZ2VuZEJyZWFrID0gYygxMjAsMTcwLDIzMCkqMTAwMA0KeUJyZWFrID0gc2VxKDEwNi43MiwgMTA2Ljg2LCBieT0wLjA0KQ0KDQpwICsgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzID0gY29sb3JQYWxldHRlLCANCiAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbGVnZW5kQnJlYWssIA0KICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkp1bWxhaCBQZW5kdWR1ayIpICsNCiAgbGFicyh0aXRsZSA9ICJKdW1sYWggUGVuZHVkdWsgS290YSBCb2dvciIpICArDQogIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9NyksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTcpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwNCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMiwgZmFjZT0nYm9sZCcpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSB5QnJlYWspIA0KYGBgDQoNCiMjIyBQZXRhIEludGVyYWt0aWYNCg0KUGV0YSBzdGF0aXMgZGFwYXQgZGlidWF0IG1lbmdndW5ha2FuIGBsZWFmbGV0YA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgb3BzaW9uYWwsIGppa2EgYmVsdW0gdGVyaW5zdGFsDQppbnN0YWxsLnBhY2thZ2VzKCJsZWFmbGV0IikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkobGVhZmxldCkNCmBgYA0KDQpCZXJpa3V0IHBlcmludGFoLXBlcmludGFoIHVudHVrIG1lbmFtcGlsa2FuIGp1bWxhaCBwZW5kdWR1ayBLb3RhIEJvZ29yIGRlbmdhbiBwZXRhICpsZWFmbGV0Ki4NCg0KMS4gYGxlYWZsZXQoKWA6IGluaXNpYXNpIHBldGEgZGVuZ2FuIG1lbWFuZ2dpbCBmdW5nc2kgYGxlYWZsZXQoKWANCjIuIGBhZGRQcm92aWRlclRpbGVzKClgOiBtZW5hbWJhaGthbiBwZXRhIGRhc2FyICooYmFzZSBtYXApKiBkZW5nYW4gcGVyaW50YWggDQozLiBgYWRkUG9seWdvbnMoKWA6IG1lbmFiYWhrYW4gcG9saWdvbiBkZW5nYW4gZ3JhZGFzaSB3YXJuYSBiZXJkYXNhcmthbiBqdW1sYWggcGVuZHVkdWsuIFBlbmdhdHVyYW4gd2FybmEgZ3JhZGFzaSBtZW5nZ3VuYWthbiBgY29sb3JOdW1lcmljKClgLiBEaXRhbWJhaGthbiBwdWxhIG9wc2kgbGFiZWwgdW50dSBtZW5hbXBpbGthbiAqcG9wdXAqLCB5YW5nIGFrYW4gbXVuY3VsIGtldGlrYSBwZW5nZ3VuYSBtZW55b3JvdCBhcmVhIHRlcnRlbnR1Lg0KNC4gYGFkZExlZ2VuZCgpYDogbWVuYW1iYWhrYW4gbGVnZW5kYQ0KNS4gYGFkZExheWVyc0NvbnRyb2woKWA6IG1lbmFtcGlsa2FuIHRvbWJvbCB1bnR1ayBtZW1pbGloIGxheWVyIHlhbmcgYWthbiBkaXRhbXBpbGthbg0KNi4gYHNldFZpZXcoKWA6IG1lbmdhdHVyIHBvc2lzaSBkYW4gKnpvb21pbmcqICpkZWZhdWx0KiANCg0KDQpgYGB7cn0NCiMgbWVtYnVhdCBjdXN0b20gcGFsZXR0ZSB3YXJuYQ0KcG9wdWxhdGlvblBhbGV0dGUgPC0gY29sb3JOdW1lcmljKA0KICBwYWxldHRlID0gIllsR25CdSIsDQogIGRvbWFpbiA9IG1hcEJvZ29yJEp1bWxhaFBlbmR1ZHVrDQopDQoNCiMgbWVtYnVhdCBjdXN0b20gcG9wdXANCnBvcHVwTGFiZWwgPC0gcGFzdGUwKA0KICAgICI8Yj5LZWNhbWF0YW4gIiwgbWFwQm9nb3IkS2VjYW1hdGFuLCI8L2I+PGJyLz4iLCANCiAgICAiSnVtbGFoIFBlbmR1ZHVrIChqaXdhKTogIiwgbWFwQm9nb3IkSnVtbGFoUGVuZHVkdWssICI8YnIvPiIsIA0KICAgICJMdWFzIFdpbGF5YWggKGttMik6ICIsIG1hcEJvZ29yJEx1YXNXaWxheWFoLCAiPGJyLz4iLCANCiAgICAiS2VwYWRhdGFuIFBlbmR1ZHVrIChqaXdhL2ttMik6ICIsIG1hcEJvZ29yJEtlcGFkYXRhblBlbmR1ZHVrKSAlPiUNCiAgbGFwcGx5KGh0bWx0b29sczo6SFRNTCkNCmBgYA0KDQoNCmBgYHtyLG91dC53aWR0aCA9ICcxMDAlJ30NCg0KIyBtZW1idWF0IHBldGEgbGVhZmxldA0KbGVhZmxldChtYXBCb2dvcikgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uTm9MYWJlbHMsIGdyb3VwID0gIkxpZ2h0IE1vZGUiKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyTm9MYWJlbHMsIGdyb3VwID0gIkRhcmsgTW9kZSIpICU+JQ0KICANCiAgYWRkUG9seWdvbnMod2VpZ2h0ID0gMSwNCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOSwNCiAgICAgICAgICAgICAgbGFiZWwgPSBwb3B1cExhYmVsLA0KICAgICAgICAgICAgICBjb2xvciA9IH5wb3B1bGF0aW9uUGFsZXR0ZShKdW1sYWhQZW5kdWR1ayksDQogICAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKGNvbG9yID0gIndoaXRlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSApICU+JQ0KICBhZGRMZWdlbmQocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiLCANCiAgICAgICAgICAgIHBhbCA9IHBvcHVsYXRpb25QYWxldHRlLCANCiAgICAgICAgICAgIHZhbHVlcyA9IH5KdW1sYWhQZW5kdWR1aywNCiAgICAgICAgICAgIHRpdGxlID0gIkp1bWxhaFxuUGVuZHVkdWsiLA0KICAgICAgICAgICAgb3BhY2l0eSA9IDEpICU+JQ0KICANCiAgYWRkTGF5ZXJzQ29udHJvbChwb3NpdGlvbiA9ICd0b3ByaWdodCcsDQogICAgICAgICAgICAgICAgICAgYmFzZUdyb3VwcyA9IGMoIkxpZ2h0IE1vZGUiLCAiRGFyayBNb2RlIiksDQogICAgICAgICAgICAgICAgICAgb3B0aW9ucyA9IGxheWVyc0NvbnRyb2xPcHRpb25zKGNvbGxhcHNlZCA9IEZBTFNFKSkgJT4lDQogIA0KICBzZXRWaWV3KGxhdCA9IC0gNi41OTUsIGxuZyA9IDEwNi44Nywgem9vbSA9IDEyKQ0KYGBgDQoNCg0K