1 Base R Graphics

1.1 Why Data Visualization?

data("anscombe")
anscombe
stats <- sapply(1:4, function(i) {
  x <- anscombe[[paste0("x", i)]]
  y <- anscombe[[paste0("y", i)]]
  c(mean_x  = mean(x),
    mean_y  = mean(y),
    sd_x    = sd(x),
    sd_y    = sd(y),
    cor_xy  = cor(x, y))
})
colnames(stats) <- paste("Dataset", 1:4)
round(stats, 4)
       Dataset 1 Dataset 2 Dataset 3 Dataset 4
mean_x    9.0000    9.0000    9.0000    9.0000
mean_y    7.5009    7.5009    7.5000    7.5009
sd_x      3.3166    3.3166    3.3166    3.3166
sd_y      2.0316    2.0317    2.0304    2.0306
cor_xy    0.8164    0.8162    0.8163    0.8165
invisible({
  plot_anscombe <- function(x, y, dataset_num) {
    plot(x, y,
         pch = 19, col = "steelblue",
         main = paste("Dataset", dataset_num),
         xlab = paste0("x", dataset_num),
         ylab = paste0("y", dataset_num),
         xlim = c(3, 15), ylim = c(3, 13))
    abline(lm(y ~ x), col = "red", lwd = 2)
  }
  
  par(mfrow = c(2, 2),
      mar = c(4, 4, 2, 1),
      oma = c(0, 0, 2, 0), cex = 1)
  
  plot_anscombe(anscombe$x1, anscombe$y1, 1)
  plot_anscombe(anscombe$x2, anscombe$y2, 2)
  plot_anscombe(anscombe$x3, anscombe$y3, 3)
  plot_anscombe(anscombe$x4, anscombe$y4, 4)
  
  mtext("Anscombe's Quartet", outer = TRUE, cex = 1.5, font = 2)
  par(mfrow = c(1, 1))
})

1.2 Scatter Plot

# Basic scatter plot
plot(mtcars$wt, mtcars$mpg)

# Customized scatter plot
plot(mtcars$wt, mtcars$mpg,
     main = "MPG vs Weight",
     xlab = "Weight (1000 lbs)",
     ylab = "Miles per Gallon",
     pch  = 19,
     col  = "steelblue",
     cex  = 1.5)


# Add regression line
abline(lm(mpg ~ wt, data=mtcars),
       col = "red", lwd = 2)

1.3 Histogram

# Basic histogram
hist(mtcars$mpg)

# Customized histogram
hist(mtcars$mpg,
     breaks = 15,
     col    = "steelblue",
     border = "white",
     main   = "Distribution of MPG",
     xlab   = "Miles per Gallon",
     freq   = FALSE)

# Add density curve
lines(density(mtcars$mpg), col = "red", lwd = 2)

1.4 Bar Plot

counts <- table(mtcars$cyl)
barplot(counts,
  main = "Cars by Cylinders",
  col  = c("#2196F3","#F5A623","#4CAF50"),
  beside = TRUE)

1.5 Box Plot

boxplot(mpg ~ cyl,
  data = mtcars,
  main = "MPG by Cylinders",
  xlab = "Cylinders",
  ylab = "MPG",
  col  = c("#2196F3","#F5A623","#4CAF50"))

1.6 Pie Chart

slices <- c(10, 12, 4, 16, 8)
labels <- c("US","UK","AU","DE","FR")
pie(slices, labels = labels, main = "Country Distribution")

2 Modern Visualization: ggplot2

library(tidyverse)

2.1 geom_point()

ggplot(mtcars, aes(wt, mpg)) +
  geom_point(color="steelblue", size=3)

2.2 geom_line()

ggplot(economics, aes(date, unemploy)) +
  geom_line(color="darkred")

2.3 geom_bar()

ggplot(mtcars, aes(factor(cyl))) +
  geom_bar(fill="steelblue")

2.4 geom_histogram()

ggplot(mtcars, aes(mpg)) +
  geom_histogram(bins=15, fill="coral")

2.5 geom_boxplot()

ggplot(mtcars, aes(factor(cyl), mpg)) +
  geom_boxplot(fill="lightblue")

2.6 geom_smooth()

ggplot(mtcars, aes(wt, mpg)) +
  geom_smooth(method="lm")

2.7 ggplot2 example

ggplot(data = mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
  geom_point(size = 3, alpha = 0.8) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title    = "Fuel Efficiency vs Vehicle Weight",
    subtitle = "Grouped by number of cylinders",
    x        = "Weight (1000 lbs)",
    y        = "Miles per Gallon",
    color    = "Cylinders"
  ) +
  scale_color_manual(values = c("#2196F3", "#F5A623", "#E91E63")) +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold"))

3 Statistical Plots

3.1 Density Plot

ggplot(mtcars, aes(mpg, fill = factor(cyl))) +
  geom_density(alpha = 0.5) +
  labs(title = "MPG Density by Cylinders",
       fill = 
"Cylinders") +
  theme_minimal()

3.2 Correlation Heatmap

library(corrplot)

cor_matrix <- cor(mtcars[,1:7])
corrplot(cor_matrix,
  method  = "color",
  type    = "upper",
  addCoef.col = "black")

3.3 QQ Plot

# Base R
qqnorm(mtcars$mpg)
qqline(mtcars$mpg, col = "red")

# ggplot2
ggplot(mtcars, aes(sample = mpg)) +
  stat_qq() + stat_qq_line()

3.4 Pair Plot

# Base R
pairs(mtcars[,1:4])

# Enhanced with GGally
library(GGally)
ggpairs(mtcars[,1:4],
  aes(color = factor(mtcars$cyl)))

4 Time Series Plots

4.1 TS Plot with Base R

# Create a ts object
data <- AirPassengers
class(data)
[1] "ts"
# Basic time series plot
plot.ts(data,
  main = "Monthly Air Passengers",
  ylab = "Passengers (thousands)",
  xlab = "Year",
  col  = "steelblue", lwd = 2)

4.2 TS Plot with ggplot2

# Convert ts to data.frame
df <- data.frame(
  date  = seq(as.Date("1949-01-01"),
              by = "month", length.out = 144),
  value = as.numeric(AirPassengers))

ggplot(df, aes(date, value)) +
  geom_line(color = "steelblue") +
  ylab("Passengers (thousands)") + xlab("") +
  scale_x_date(date_labels = "%b %Y", date_breaks = "2 year")

4.3 ACF and PACF Plots

par(mfrow = c(1, 2))
acf(AirPassengers, main = "ACF")
pacf(AirPassengers, main = "PACF")

4.4 Time Series Decomposition

# Decompose into components
decomp <- decompose(AirPassengers)
plot(decomp)


# Components:
# $trend    - long-term direction
# $seasonal - repeating pattern
# $random   - residual noise
# STL decomposition (more robust)
stl_result <- stl(AirPassengers,
                  s.window = "periodic")
plot(stl_result)

library(forecast)
fit <- auto.arima(AirPassengers)
autoplot(forecast(fit, h = 24))

4.5 Seasonal Plot & Testing

library(tsutils)

# Seasonal diagram (default)
seasplot(AirPassengers)
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

# Seasonal boxplots
seasplot(AirPassengers, outplot = 2)
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

# Seasonal subseries
seasplot(AirPassengers, outplot = 3)
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

# Seasonal density
seasplot(AirPassengers, outplot = 5)
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

# With custom decomposition
seasplot(AirPassengers, decomposition = "multiplicative")
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

LS0tCnRpdGxlOiAiQ29tcHV0YXRpb25hbCBTdGF0aXN0aWNzIFdlZWsgMyIKCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgbWF0aF9tZXRob2Q6IGthdGV4CiAgICB0aGVtZTogeWV0aQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKIyBCYXNlIFIgR3JhcGhpY3MKCiMjIFdoeSBEYXRhIFZpc3VhbGl6YXRpb24/CgpgYGB7cn0KZGF0YSgiYW5zY29tYmUiKQphbnNjb21iZQpgYGAKCmBgYHtyfQpzdGF0cyA8LSBzYXBwbHkoMTo0LCBmdW5jdGlvbihpKSB7CiAgeCA8LSBhbnNjb21iZVtbcGFzdGUwKCJ4IiwgaSldXQogIHkgPC0gYW5zY29tYmVbW3Bhc3RlMCgieSIsIGkpXV0KICBjKG1lYW5feCAgPSBtZWFuKHgpLAogICAgbWVhbl95ICA9IG1lYW4oeSksCiAgICBzZF94ICAgID0gc2QoeCksCiAgICBzZF95ICAgID0gc2QoeSksCiAgICBjb3JfeHkgID0gY29yKHgsIHkpKQp9KQpjb2xuYW1lcyhzdGF0cykgPC0gcGFzdGUoIkRhdGFzZXQiLCAxOjQpCnJvdW5kKHN0YXRzLCA0KQpgYGAKCgpgYGB7ciwgb3V0LndpZHRoPSIxMDAlIn0KaW52aXNpYmxlKHsKICBwbG90X2Fuc2NvbWJlIDwtIGZ1bmN0aW9uKHgsIHksIGRhdGFzZXRfbnVtKSB7CiAgICBwbG90KHgsIHksCiAgICAgICAgIHBjaCA9IDE5LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICAgICAgbWFpbiA9IHBhc3RlKCJEYXRhc2V0IiwgZGF0YXNldF9udW0pLAogICAgICAgICB4bGFiID0gcGFzdGUwKCJ4IiwgZGF0YXNldF9udW0pLAogICAgICAgICB5bGFiID0gcGFzdGUwKCJ5IiwgZGF0YXNldF9udW0pLAogICAgICAgICB4bGltID0gYygzLCAxNSksIHlsaW0gPSBjKDMsIDEzKSkKICAgIGFibGluZShsbSh5IH4geCksIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQogIH0KICAKICBwYXIobWZyb3cgPSBjKDIsIDIpLAogICAgICBtYXIgPSBjKDQsIDQsIDIsIDEpLAogICAgICBvbWEgPSBjKDAsIDAsIDIsIDApLCBjZXggPSAxKQogIAogIHBsb3RfYW5zY29tYmUoYW5zY29tYmUkeDEsIGFuc2NvbWJlJHkxLCAxKQogIHBsb3RfYW5zY29tYmUoYW5zY29tYmUkeDIsIGFuc2NvbWJlJHkyLCAyKQogIHBsb3RfYW5zY29tYmUoYW5zY29tYmUkeDMsIGFuc2NvbWJlJHkzLCAzKQogIHBsb3RfYW5zY29tYmUoYW5zY29tYmUkeDQsIGFuc2NvbWJlJHk0LCA0KQogIAogIG10ZXh0KCJBbnNjb21iZSdzIFF1YXJ0ZXQiLCBvdXRlciA9IFRSVUUsIGNleCA9IDEuNSwgZm9udCA9IDIpCiAgcGFyKG1mcm93ID0gYygxLCAxKSkKfSkKYGBgCgojIyBTY2F0dGVyIFBsb3QKCmBgYHtyfQojIEJhc2ljIHNjYXR0ZXIgcGxvdApwbG90KG10Y2FycyR3dCwgbXRjYXJzJG1wZykKYGBgCgpgYGB7cn0KIyBDdXN0b21pemVkIHNjYXR0ZXIgcGxvdApwbG90KG10Y2FycyR3dCwgbXRjYXJzJG1wZywKICAgICBtYWluID0gIk1QRyB2cyBXZWlnaHQiLAogICAgIHhsYWIgPSAiV2VpZ2h0ICgxMDAwIGxicykiLAogICAgIHlsYWIgPSAiTWlsZXMgcGVyIEdhbGxvbiIsCiAgICAgcGNoICA9IDE5LAogICAgIGNvbCAgPSAic3RlZWxibHVlIiwKICAgICBjZXggID0gMS41KQoKCiMgQWRkIHJlZ3Jlc3Npb24gbGluZQphYmxpbmUobG0obXBnIH4gd3QsIGRhdGE9bXRjYXJzKSwKICAgICAgIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQpgYGAKCiMjIEhpc3RvZ3JhbQoKYGBge3J9CiMgQmFzaWMgaGlzdG9ncmFtCmhpc3QobXRjYXJzJG1wZykKYGBgCgpgYGB7cn0KIyBDdXN0b21pemVkIGhpc3RvZ3JhbQpoaXN0KG10Y2FycyRtcGcsCiAgICAgYnJlYWtzID0gMTUsCiAgICAgY29sICAgID0gInN0ZWVsYmx1ZSIsCiAgICAgYm9yZGVyID0gIndoaXRlIiwKICAgICBtYWluICAgPSAiRGlzdHJpYnV0aW9uIG9mIE1QRyIsCiAgICAgeGxhYiAgID0gIk1pbGVzIHBlciBHYWxsb24iLAogICAgIGZyZXEgICA9IEZBTFNFKQoKIyBBZGQgZGVuc2l0eSBjdXJ2ZQpsaW5lcyhkZW5zaXR5KG10Y2FycyRtcGcpLCBjb2wgPSAicmVkIiwgbHdkID0gMikKYGBgCgojIyBCYXIgUGxvdAoKYGBge3J9CmNvdW50cyA8LSB0YWJsZShtdGNhcnMkY3lsKQpiYXJwbG90KGNvdW50cywKICBtYWluID0gIkNhcnMgYnkgQ3lsaW5kZXJzIiwKICBjb2wgID0gYygiIzIxOTZGMyIsIiNGNUE2MjMiLCIjNENBRjUwIiksCiAgYmVzaWRlID0gVFJVRSkKYGBgCgojIyBCb3ggUGxvdAoKYGBge3J9CmJveHBsb3QobXBnIH4gY3lsLAogIGRhdGEgPSBtdGNhcnMsCiAgbWFpbiA9ICJNUEcgYnkgQ3lsaW5kZXJzIiwKICB4bGFiID0gIkN5bGluZGVycyIsCiAgeWxhYiA9ICJNUEciLAogIGNvbCAgPSBjKCIjMjE5NkYzIiwiI0Y1QTYyMyIsIiM0Q0FGNTAiKSkKCmBgYAoKIyMgUGllIENoYXJ0CgpgYGB7cn0Kc2xpY2VzIDwtIGMoMTAsIDEyLCA0LCAxNiwgOCkKbGFiZWxzIDwtIGMoIlVTIiwiVUsiLCJBVSIsIkRFIiwiRlIiKQpwaWUoc2xpY2VzLCBsYWJlbHMgPSBsYWJlbHMsIG1haW4gPSAiQ291bnRyeSBEaXN0cmlidXRpb24iKQpgYGAKCiMgTW9kZXJuIFZpc3VhbGl6YXRpb246IGBnZ3Bsb3QyYAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCgojIyBgZ2VvbV9wb2ludCgpYAoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyh3dCwgbXBnKSkgKwogIGdlb21fcG9pbnQoY29sb3I9InN0ZWVsYmx1ZSIsIHNpemU9MykKYGBgCgojIyBgZ2VvbV9saW5lKClgCgpgYGB7cn0KZ2dwbG90KGVjb25vbWljcywgYWVzKGRhdGUsIHVuZW1wbG95KSkgKwogIGdlb21fbGluZShjb2xvcj0iZGFya3JlZCIpCmBgYAoKIyMgYGdlb21fYmFyKClgCgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpKSkgKwogIGdlb21fYmFyKGZpbGw9InN0ZWVsYmx1ZSIpCmBgYAoKIyMgYGdlb21faGlzdG9ncmFtKClgCgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKG1wZykpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zPTE1LCBmaWxsPSJjb3JhbCIpCmBgYAoKIyMgYGdlb21fYm94cGxvdCgpYAoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyhmYWN0b3IoY3lsKSwgbXBnKSkgKwogIGdlb21fYm94cGxvdChmaWxsPSJsaWdodGJsdWUiKQpgYGAKCiMjIGBnZW9tX3Ntb290aCgpYAoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyh3dCwgbXBnKSkgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKQpgYGAKCiMjIGBnZ3Bsb3QyYCBleGFtcGxlCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcsIGNvbG9yID0gZmFjdG9yKGN5bCkpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGEgPSAwLjgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArCiAgbGFicygKICAgIHRpdGxlICAgID0gIkZ1ZWwgRWZmaWNpZW5jeSB2cyBWZWhpY2xlIFdlaWdodCIsCiAgICBzdWJ0aXRsZSA9ICJHcm91cGVkIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMiLAogICAgeCAgICAgICAgPSAiV2VpZ2h0ICgxMDAwIGxicykiLAogICAgeSAgICAgICAgPSAiTWlsZXMgcGVyIEdhbGxvbiIsCiAgICBjb2xvciAgICA9ICJDeWxpbmRlcnMiCiAgKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiMyMTk2RjMiLCAiI0Y1QTYyMyIsICIjRTkxRTYzIikpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpCmBgYAoKIyBTdGF0aXN0aWNhbCBQbG90cwoKIyMgRGVuc2l0eSBQbG90CgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKG1wZywgZmlsbCA9IGZhY3RvcihjeWwpKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiTVBHIERlbnNpdHkgYnkgQ3lsaW5kZXJzIiwKICAgICAgIGZpbGwgPSAKIkN5bGluZGVycyIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyBDb3JyZWxhdGlvbiBIZWF0bWFwCgpgYGB7cn0KbGlicmFyeShjb3JycGxvdCkKCmNvcl9tYXRyaXggPC0gY29yKG10Y2Fyc1ssMTo3XSkKY29ycnBsb3QoY29yX21hdHJpeCwKICBtZXRob2QgID0gImNvbG9yIiwKICB0eXBlICAgID0gInVwcGVyIiwKICBhZGRDb2VmLmNvbCA9ICJibGFjayIpCmBgYAoKIyMgUVEgUGxvdAoKYGBge3J9CiMgQmFzZSBSCnFxbm9ybShtdGNhcnMkbXBnKQpxcWxpbmUobXRjYXJzJG1wZywgY29sID0gInJlZCIpCmBgYAoKYGBge3J9CiMgZ2dwbG90MgpnZ3Bsb3QobXRjYXJzLCBhZXMoc2FtcGxlID0gbXBnKSkgKwogIHN0YXRfcXEoKSArIHN0YXRfcXFfbGluZSgpCmBgYAoKIyMgUGFpciBQbG90CgpgYGB7cn0KIyBCYXNlIFIKcGFpcnMobXRjYXJzWywxOjRdKQpgYGAKCmBgYHtyfQojIEVuaGFuY2VkIHdpdGggR0dhbGx5CmxpYnJhcnkoR0dhbGx5KQpnZ3BhaXJzKG10Y2Fyc1ssMTo0XSwKICBhZXMoY29sb3IgPSBmYWN0b3IobXRjYXJzJGN5bCkpKQpgYGAKCiMgVGltZSBTZXJpZXMgUGxvdHMKCiMjIFRTIFBsb3Qgd2l0aCBCYXNlIFIKCmBgYHtyfQojIENyZWF0ZSBhIHRzIG9iamVjdApkYXRhIDwtIEFpclBhc3NlbmdlcnMKY2xhc3MoZGF0YSkKYGBgCgpgYGB7cn0KIyBCYXNpYyB0aW1lIHNlcmllcyBwbG90CnBsb3QudHMoZGF0YSwKICBtYWluID0gIk1vbnRobHkgQWlyIFBhc3NlbmdlcnMiLAogIHlsYWIgPSAiUGFzc2VuZ2VycyAodGhvdXNhbmRzKSIsCiAgeGxhYiA9ICJZZWFyIiwKICBjb2wgID0gInN0ZWVsYmx1ZSIsIGx3ZCA9IDIpCmBgYAoKIyMgVFMgUGxvdCB3aXRoIGBnZ3Bsb3QyYAoKYGBge3J9CiMgQ29udmVydCB0cyB0byBkYXRhLmZyYW1lCmRmIDwtIGRhdGEuZnJhbWUoCiAgZGF0ZSAgPSBzZXEoYXMuRGF0ZSgiMTk0OS0wMS0wMSIpLAogICAgICAgICAgICAgIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDE0NCksCiAgdmFsdWUgPSBhcy5udW1lcmljKEFpclBhc3NlbmdlcnMpKQoKZ2dwbG90KGRmLCBhZXMoZGF0ZSwgdmFsdWUpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gInN0ZWVsYmx1ZSIpICsKICB5bGFiKCJQYXNzZW5nZXJzICh0aG91c2FuZHMpIikgKyB4bGFiKCIiKSArCiAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiViICVZIiwgZGF0ZV9icmVha3MgPSAiMiB5ZWFyIikKYGBgCgojIyBBQ0YgYW5kIFBBQ0YgUGxvdHMKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDIpKQphY2YoQWlyUGFzc2VuZ2VycywgbWFpbiA9ICJBQ0YiKQpwYWNmKEFpclBhc3NlbmdlcnMsIG1haW4gPSAiUEFDRiIpCmBgYAoKIyMgVGltZSBTZXJpZXMgRGVjb21wb3NpdGlvbgoKYGBge3J9CiMgRGVjb21wb3NlIGludG8gY29tcG9uZW50cwpkZWNvbXAgPC0gZGVjb21wb3NlKEFpclBhc3NlbmdlcnMpCnBsb3QoZGVjb21wKQoKIyBDb21wb25lbnRzOgojICR0cmVuZCAgICAtIGxvbmctdGVybSBkaXJlY3Rpb24KIyAkc2Vhc29uYWwgLSByZXBlYXRpbmcgcGF0dGVybgojICRyYW5kb20gICAtIHJlc2lkdWFsIG5vaXNlCmBgYAoKYGBge3J9CiMgU1RMIGRlY29tcG9zaXRpb24gKG1vcmUgcm9idXN0KQpzdGxfcmVzdWx0IDwtIHN0bChBaXJQYXNzZW5nZXJzLAogICAgICAgICAgICAgICAgICBzLndpbmRvdyA9ICJwZXJpb2RpYyIpCnBsb3Qoc3RsX3Jlc3VsdCkKYGBgCgpgYGB7cn0KbGlicmFyeShmb3JlY2FzdCkKZml0IDwtIGF1dG8uYXJpbWEoQWlyUGFzc2VuZ2VycykKYXV0b3Bsb3QoZm9yZWNhc3QoZml0LCBoID0gMjQpKQpgYGAKCiMjIFNlYXNvbmFsIFBsb3QgJiBUZXN0aW5nCgpgYGB7cn0KbGlicmFyeSh0c3V0aWxzKQoKIyBTZWFzb25hbCBkaWFncmFtIChkZWZhdWx0KQpzZWFzcGxvdChBaXJQYXNzZW5nZXJzKQoKIyBTZWFzb25hbCBib3hwbG90cwpzZWFzcGxvdChBaXJQYXNzZW5nZXJzLCBvdXRwbG90ID0gMikKCiMgU2Vhc29uYWwgc3Vic2VyaWVzCnNlYXNwbG90KEFpclBhc3NlbmdlcnMsIG91dHBsb3QgPSAzKQoKIyBTZWFzb25hbCBkZW5zaXR5CnNlYXNwbG90KEFpclBhc3NlbmdlcnMsIG91dHBsb3QgPSA1KQoKIyBXaXRoIGN1c3RvbSBkZWNvbXBvc2l0aW9uCnNlYXNwbG90KEFpclBhc3NlbmdlcnMsIGRlY29tcG9zaXRpb24gPSAibXVsdGlwbGljYXRpdmUiKQpgYGAKCgo=