Data visualization exercise

This notebook uses TidyTuesday week 14 Makeup Shades, data from The Pudding data. And the corresponding article can be found at The Pudding.

# load libraries
library(tidyverse)
library(scales)
library(ggtext)
library(patchwork)

# set theme
theme_set(theme_minimal(10))
# import data
sephora <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-03-30/sephora.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  brand = col_character(),
  product = col_character(),
  url = col_character(),
  description = col_character(),
  imgSrc = col_character(),
  imgAlt = col_character(),
  name = col_character(),
  specific = col_character()
)
ulta <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-03-30/ulta.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  brand = col_character(),
  product = col_character(),
  url = col_character(),
  description = col_character(),
  imgSrc = col_character(),
  imgAlt = col_character(),
  name = col_character(),
  specific = col_character()
)
allCategories <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-03-30/allCategories.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  brand = col_character(),
  product = col_character(),
  url = col_character(),
  imgSrc = col_character(),
  name = col_character(),
  categories = col_character(),
  specific = col_character(),
  hex = col_character(),
  lightness = col_double()
)
allShades <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-03-30/allShades.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  brand = col_character(),
  product = col_character(),
  url = col_character(),
  description = col_character(),
  imgSrc = col_character(),
  imgAlt = col_character(),
  name = col_character(),
  specific = col_character(),
  colorspace = col_character(),
  hex = col_character(),
  hue = col_double(),
  sat = col_double(),
  lightness = col_double()
)
allNumbers <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-03-30/allNumbers.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  brand = col_character(),
  product = col_character(),
  name = col_character(),
  specific = col_character(),
  lightness = col_double(),
  hex = col_character(),
  lightToDark = col_logical(),
  numbers = col_double(),
  id = col_double()
)
head(allShades)

Boxplot: Foundation lightness of brands with more than 5 foundation products

allShades %>% group_by(brand) %>% summarise(product_n=n_distinct(product)) %>% arrange(desc(product_n))
# brands with >5 products
brand_prod = allShades %>% group_by(brand) %>% summarise(product_n=n_distinct(product)) %>% arrange(desc(product_n)) %>% filter(product_n>5)
brand_names = brand_prod$brand 
brand_names
 [1] "Clinique"           "bareMinerals"       "It Cosmetics"       "Tarte"              "SEPHORA COLLECTION" "Dior"              
 [7] "Armani Beauty"      "L'Oréal"            "Laura Mercier"      "Bobbi Brown"        "Lancôme"            "MAKE UP FOR EVER"  
[13] "Maybelline"         "Too Faced"          "ULTA"              
allShades %>% 
  filter(brand %in% brand_names) %>%
  ggplot(aes(y=fct_reorder(brand,lightness,.fun = median, .desc=TRUE), x=lightness)) + 
  geom_boxplot() + 
  theme_minimal(base_size = 10) + 
  theme(panel.grid.minor.x=element_blank(),
        plot.title.position = "plot",
        plot.caption.position = "plot",
        plot.title=element_text(face="bold",size=15),
        axis.title=element_text(face="bold"),
        plot.caption=element_text(hjust=0),
        plot.margin=margin(1,1,1,1,"cm")) + 
  labs(x="Lightness", y="Brand",
       caption="\nTidy Tuesday Week 14 | Data from The Pudding",
       title="Foundation lightness of brands with more than 5 foundation products",
       subtitle = "Lightness represented as a decimal from 0 to 1, where 0 is pure black and 1 is pure white\n") 

# dot plot
allShades %>% 
  filter(brand %in% brand_names) %>%
  ggplot(aes(y=lightness, x=fct_reorder(brand,lightness,.fun = median, .desc=TRUE))) + 
  geom_dotplot(fill="slategrey",
               binaxis = "y",
               binwidth = 0.005,
               stackdir="center", show.legend = F, size=1, color=NA, alpha=0.4) +
  stat_summary(fun.y = median, fun.ymin = median, fun.ymax = median,
                geom = "pointrange", width = 0.5, size=0.2, alpha=0.9, color="#f77f00") +
  coord_flip() + 
  theme_minimal(base_size = 10) + 
  theme(
    #plot.margin=margin(1,1,1,1,"cm"),
    axis.title=element_text(face="bold"),
    plot.title.position = "plot",
    plot.subtitle=element_markdown()) +
  labs(y="",x="", title="Foundation lightness by brand",
       subtitle = "Foundation lightness by brand, from lowest to highest <span style = 'color:#f77f00'><b>Median</b></span> lightness\n")

# products
# lightToDark: Whether this product line organizes their colors from light to dark (Note: a value of NA indicates that a product uses a number-based naming system, but not a sequential numbering system)

# number of distinct brands
n_distinct(allNumbers$brand)
[1] 64
# lightToDark table
allNumbers %>% group_by(lightToDark) %>% tally() %>% mutate("%"=round(n/sum(n)*100,2)) #table
# histogram: lightness
hist = allCategories %>%
  ggplot(aes(lightness)) +
  geom_histogram(alpha = 0.7, bins = 100, fill="slategrey") +
  xlim(0, 1) + 
  labs(title="Histogram: lightness") + 
  theme(plot.title.position = "plot")

# density plot: lightness and lightToDark
dens = allNumbers %>%
  ggplot(aes(lightness, color=lightToDark)) +
  geom_density(alpha = 0.6, fill=NA) +
  xlim(0, 1) + 
  scale_color_manual(values=c("#577590","#90be6d"), na.value="#f94144") + 
  labs(title="Density plot: lightness and lightToDark") + 
  theme(plot.title.position = "plot")

# plot
(hist/dens)

# brands that uses sequential numbering system (SNS)
seq = allNumbers %>% 
  filter(!is.na(lightToDark)) %>% 
  group_by(brand,product,lightToDark) %>%
  tally() %>%
  ungroup() %>%
  group_by(brand,lightToDark) %>% 
  summarise(product_n=n_distinct(product))
`summarise()` has grouped output by 'brand'. You can override using the `.groups` argument.
# brands that uses SNS that have both classes of lightToDark
seq %>% group_by(brand) %>% summarise(class_n=n_distinct(lightToDark)) %>% filter(class_n==2) %>% count() %>% unlist() #no brands uses both classes
n 
0 
# brand and lightToDark
seq %>% group_by(lightToDark) %>% tally()

Average product shades by brand

# number of shades by product
shades = allShades %>% 
  group_by(brand, product) %>% 
  summarise(shades_n = n_distinct(hex))
`summarise()` has grouped output by 'brand'. You can override using the `.groups` argument.
summary(shades$shades_n)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    8.00   16.00   20.55   30.00   62.00 
# products with more than 50 shades
shades %>% filter(shades_n>=50) %>% arrange(desc(shades_n))
# brands, product count and average number of shades
shades2 = shades %>% 
  group_by(brand) %>%
  summarise(product_count=n_distinct(product),
            avg_shades = (mean(shades_n))) %>%
  arrange(desc(avg_shades))
shades2
hist(shades2$product_count)

shades2 %>% mutate(cat = ifelse(product_count==1,"1 product",">1 product")) %>% 
  group_by(cat) %>%
  summarise(avg1 = mean(avg_shades))
# Brands with 1 product versus Brands with >1 product
shades2 %>% mutate(cat = ifelse(product_count==1,"1 product",">1 product")) %>%
  ggplot(aes(x=avg_shades, y= cat)) + 
  geom_bar(stat="summary",fun.x="mean", width=0.5, alpha=0.5, fill="#b8b8d1") +
  geom_boxplot(fill=NA, width=0.2, outlier.size=-1, color="#f28f3b") +
  geom_jitter(height=0.2, size=1.5, alpha=0.8, color="#255f85") + 
  theme_minimal(base_size = 10) + 
  theme(panel.grid.minor.x=element_blank(),
        panel.grid.major.y=element_blank(),
        plot.margin=margin(1,1,1,1,"cm"),
        plot.title.position = "plot") + 
  labs(x="Average product shades (brand)",
       y="",
       title="Average product shades",
       subtitle="Brands with 1 product vs. Brands with >1 product")

10 brands with the most shades

# 10 brands with the most shades, with their palettes
# code reference: [Richard Vogg](https://t.co/y1IBB3fjMJ?amp=1)

brands_n = allCategories %>% 
  group_by(brand) %>%
  tally(sort=T) %>%
  slice(1:10) %>%
  .$brand # get brand names

brands_n
 [1] "bareMinerals"       "Tarte"              "Clinique"           "MAKE UP FOR EVER"   "Laura Mercier"      "Too Faced"         
 [7] "Estée Lauder"       "SEPHORA COLLECTION" "Dior"               "L'Oréal"           
# sort 
sorted <- allCategories %>%
  group_by(brand) %>%
  arrange(brand,lightness) %>%
  mutate(rank=rank(lightness,ties.method = "first"))
# function to create plot
get_brand_colors <- function(brand_name) {
  sorted %>%
    filter(brand==brand_name)
}

plot_brand_colors <- function(brand_data) {
  title <- brand_data[[1,1]]
  
  ggplot(brand_data,aes(x=rank,y=brand,fill=hex)) + geom_tile() +
    scale_fill_manual(values=brand_data$hex) +
    scale_x_continuous(limits=c(0,370)) +
    theme_minimal(base_size=10)+
    theme(legend.position = "none",
          axis.title = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks = element_blank(),
          plot.subtitle = element_text(),
          panel.grid=element_blank()) + 
    coord_cartesian(expand=FALSE)
}

plots <- lapply(brands_n,function(x) {
  x %>% 
     get_brand_colors() %>% 
     plot_brand_colors()
  })
# plot
(plots[[1]]) / (plots[[2]]) / (plots[[3]]) / (plots[[4]]) / (plots[[5]]) / (plots[[6]]) / (plots[[7]]) / (plots[[8]]) / (plots[[9]]) / (plots[[10]]) + 
  plot_annotation(title = "10 brands with the most foundation shades")

# get 10 most frequent name (programmatically extracted word-based name of this particular shade) 
names1 = 
  allShades %>% 
  mutate(name=tolower(name)) %>%
  count(name) %>%
  drop_na() %>%
  arrange(desc(n)) %>%
  mutate(rank=rank(desc(n), ties.method = "first")) #code from [Cal Webb](https://t.co/KrE6NtQPaw?amp=1)

# join rank to data 
names2 = left_join(allShades %>% mutate(name = tolower(name)), names1, by = c("name")) %>% drop_na()

# shade colors
shade_col <- names2 %>% 
  filter(rank<=10) %>% pull(hex, hex) #pull hex code from [Jamie Avendano](https://t.co/hxmx55bxWC?amp=1)

# plot
names2 %>% filter(rank<=10) %>%
  ggplot(aes(y=fct_reorder(name,lightness,.fun = median, .desc=TRUE), x= lightness)) + 
  geom_boxplot(outlier.shape=NA, color="slategrey",width=0.7) + 
  geom_jitter(aes(color=hex),alpha=0.6, show.legend = F, height=0.2, size=1.5) + 
  scale_color_manual(values=shade_col) + 
  theme(plot.margin=margin(1,1,1,1,"cm"),
        plot.title.position = "plot"
        #plot.title=element_text(face="bold"),
        #axis.title=element_text(face="bold")
        ) + 
  labs(x="Name", y= "Lightness",
       title="Lightness of 10 Most Frequent Word-based Shade Name",
       subtitle="Where Lightness 0 is pure black and 1 is pure white\n")

LS0tCnRpdGxlOiAiTWFrZXVwIFNoYWRlcyIKZGF0ZTogIjIwMjEtMDMtMzAiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyMgRGF0YSB2aXN1YWxpemF0aW9uIGV4ZXJjaXNlCgpUaGlzIG5vdGVib29rIHVzZXMgW1RpZHlUdWVzZGF5XShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5Lykgd2VlayAxNCBbTWFrZXVwIFNoYWRlc10oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9ibG9iL21hc3Rlci9kYXRhLzIwMjEvMjAyMS0wMy0zMC9yZWFkbWUubWQpLCBkYXRhIGZyb20gW1RoZSBQdWRkaW5nIGRhdGFdKGh0dHBzOi8vZ2l0aHViLmNvbS90aGUtcHVkZGluZy9kYXRhL3RyZWUvbWFzdGVyL2ZvdW5kYXRpb24tbmFtZXMpLiBBbmQgdGhlIGNvcnJlc3BvbmRpbmcgYXJ0aWNsZSBjYW4gYmUgZm91bmQgYXQgCltUaGUgUHVkZGluZ10oaHR0cHM6Ly9wdWRkaW5nLmNvb2wvMjAyMS8wMy9mb3VuZGF0aW9uLW5hbWVzLykuIAoKCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBsb2FkIGxpYnJhcmllcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZ2d0ZXh0KQpsaWJyYXJ5KHBhdGNod29yaykKCiMgc2V0IHRoZW1lCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKDEwKSkKYGBgCgpgYGB7cn0KIyBpbXBvcnQgZGF0YQpzZXBob3JhIDwtIHJlYWRyOjpyZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDIxLzIwMjEtMDMtMzAvc2VwaG9yYS5jc3YnKQp1bHRhIDwtIHJlYWRyOjpyZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDIxLzIwMjEtMDMtMzAvdWx0YS5jc3YnKQphbGxDYXRlZ29yaWVzIDwtIHJlYWRyOjpyZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDIxLzIwMjEtMDMtMzAvYWxsQ2F0ZWdvcmllcy5jc3YnKQphbGxTaGFkZXMgPC0gcmVhZHI6OnJlYWRfY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L21hc3Rlci9kYXRhLzIwMjEvMjAyMS0wMy0zMC9hbGxTaGFkZXMuY3N2JykKYWxsTnVtYmVycyA8LSByZWFkcjo6cmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAyMS8yMDIxLTAzLTMwL2FsbE51bWJlcnMuY3N2JykKYGBgCgpgYGB7cn0KaGVhZChhbGxTaGFkZXMpCmBgYAoKIyMjIyBCb3hwbG90OiBGb3VuZGF0aW9uIGxpZ2h0bmVzcyBvZiBicmFuZHMgd2l0aCBtb3JlIHRoYW4gNSBmb3VuZGF0aW9uIHByb2R1Y3RzCiogc2hhcmVkIG9uIFtUd2l0dGVyXShodHRwczovL3R3aXR0ZXIuY29tL2xlZW9sbmV5My9zdGF0dXMvMTM3NjY0NDMyMzk5MTgxODI0Ny9waG90by8xKQoqIGFsdCB0ZXh0OiBCb3hwbG90IHNob3dpbmcgdGhlIG1ha2V1cCBmb3VuZGF0aW9uIHNoYWRlcyBvZiBmaWZ0ZWVuIGJyYW5kcyB0aGF0IGhhdmUgbW9yZSB0aGFuIDUgZm91bmRhdGlvbiBwcm9kdWN0cywgd2hlcmUgdGhlIGJyYW5kIFRvbyBGYWNlZCBoYXMgdGhlIGRhcmtlc3QgZm91bmRhdGlvbiBzaGFkZS4KCmBgYHtyfQphbGxTaGFkZXMgJT4lIGdyb3VwX2J5KGJyYW5kKSAlPiUgc3VtbWFyaXNlKHByb2R1Y3Rfbj1uX2Rpc3RpbmN0KHByb2R1Y3QpKSAlPiUgYXJyYW5nZShkZXNjKHByb2R1Y3RfbikpCmBgYAoKYGBge3J9CiMgYnJhbmRzIHdpdGggPjUgcHJvZHVjdHMKYnJhbmRfcHJvZCA9IGFsbFNoYWRlcyAlPiUgZ3JvdXBfYnkoYnJhbmQpICU+JSBzdW1tYXJpc2UocHJvZHVjdF9uPW5fZGlzdGluY3QocHJvZHVjdCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZHVjdF9uKSkgJT4lIGZpbHRlcihwcm9kdWN0X24+NSkKYnJhbmRfbmFtZXMgPSBicmFuZF9wcm9kJGJyYW5kIApicmFuZF9uYW1lcwpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD0zLjIsIGZpZy53aWR0aD00fQphbGxTaGFkZXMgJT4lIAogIGZpbHRlcihicmFuZCAlaW4lIGJyYW5kX25hbWVzKSAlPiUKICBnZ3Bsb3QoYWVzKHk9ZmN0X3Jlb3JkZXIoYnJhbmQsbGlnaHRuZXNzLC5mdW4gPSBtZWRpYW4sIC5kZXNjPVRSVUUpLCB4PWxpZ2h0bmVzcykpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEwKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgICAgICBwbG90LmNhcHRpb24ucG9zaXRpb24gPSAicGxvdCIsCiAgICAgICAgcGxvdC50aXRsZT1lbGVtZW50X3RleHQoZmFjZT0iYm9sZCIsc2l6ZT0xNSksCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpLAogICAgICAgIHBsb3QuY2FwdGlvbj1lbGVtZW50X3RleHQoaGp1c3Q9MCksCiAgICAgICAgcGxvdC5tYXJnaW49bWFyZ2luKDEsMSwxLDEsImNtIikpICsgCiAgbGFicyh4PSJMaWdodG5lc3MiLCB5PSJCcmFuZCIsCiAgICAgICBjYXB0aW9uPSJcblRpZHkgVHVlc2RheSBXZWVrIDE0IHwgRGF0YSBmcm9tIFRoZSBQdWRkaW5nIiwKICAgICAgIHRpdGxlPSJGb3VuZGF0aW9uIGxpZ2h0bmVzcyBvZiBicmFuZHMgd2l0aCBtb3JlIHRoYW4gNSBmb3VuZGF0aW9uIHByb2R1Y3RzIiwKICAgICAgIHN1YnRpdGxlID0gIkxpZ2h0bmVzcyByZXByZXNlbnRlZCBhcyBhIGRlY2ltYWwgZnJvbSAwIHRvIDEsIHdoZXJlIDAgaXMgcHVyZSBibGFjayBhbmQgMSBpcyBwdXJlIHdoaXRlXG4iKSAKYGBgCmBgYHtyLCBmaWcuaGVpZ2h0PTMuMiwgZmlnLndpZHRoPTQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgZG90IHBsb3QKYWxsU2hhZGVzICU+JSAKICBmaWx0ZXIoYnJhbmQgJWluJSBicmFuZF9uYW1lcykgJT4lCiAgZ2dwbG90KGFlcyh5PWxpZ2h0bmVzcywgeD1mY3RfcmVvcmRlcihicmFuZCxsaWdodG5lc3MsLmZ1biA9IG1lZGlhbiwgLmRlc2M9VFJVRSkpKSArIAogIGdlb21fZG90cGxvdChmaWxsPSJzbGF0ZWdyZXkiLAogICAgICAgICAgICAgICBiaW5heGlzID0gInkiLAogICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMDA1LAogICAgICAgICAgICAgICBzdGFja2Rpcj0iY2VudGVyIiwgc2hvdy5sZWdlbmQgPSBGLCBzaXplPTEsIGNvbG9yPU5BLCBhbHBoYT0wLjQpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWRpYW4sIGZ1bi55bWluID0gbWVkaWFuLCBmdW4ueW1heCA9IG1lZGlhbiwKICAgICAgICAgICAgICAgIGdlb20gPSAicG9pbnRyYW5nZSIsIHdpZHRoID0gMC41LCBzaXplPTAuMiwgYWxwaGE9MC45LCBjb2xvcj0iI2Y3N2YwMCIpICsKICBjb29yZF9mbGlwKCkgKyAKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEwKSArIAogIHRoZW1lKAogICAgI3Bsb3QubWFyZ2luPW1hcmdpbigxLDEsMSwxLCJjbSIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpLAogICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgIHBsb3Quc3VidGl0bGU9ZWxlbWVudF9tYXJrZG93bigpKSArCiAgbGFicyh5PSIiLHg9IiIsIHRpdGxlPSJGb3VuZGF0aW9uIGxpZ2h0bmVzcyBieSBicmFuZCIsCiAgICAgICBzdWJ0aXRsZSA9ICJGb3VuZGF0aW9uIGxpZ2h0bmVzcyBieSBicmFuZCwgZnJvbSBsb3dlc3QgdG8gaGlnaGVzdCA8c3BhbiBzdHlsZSA9ICdjb2xvcjojZjc3ZjAwJz48Yj5NZWRpYW48L2I+PC9zcGFuPiBsaWdodG5lc3NcbiIpCgpgYGAKCgpgYGB7cn0KIyBwcm9kdWN0cwojIGxpZ2h0VG9EYXJrOiBXaGV0aGVyIHRoaXMgcHJvZHVjdCBsaW5lIG9yZ2FuaXplcyB0aGVpciBjb2xvcnMgZnJvbSBsaWdodCB0byBkYXJrIChOb3RlOiBhIHZhbHVlIG9mIE5BIGluZGljYXRlcyB0aGF0IGEgcHJvZHVjdCB1c2VzIGEgbnVtYmVyLWJhc2VkIG5hbWluZyBzeXN0ZW0sIGJ1dCBub3QgYSBzZXF1ZW50aWFsIG51bWJlcmluZyBzeXN0ZW0pCgojIG51bWJlciBvZiBkaXN0aW5jdCBicmFuZHMKbl9kaXN0aW5jdChhbGxOdW1iZXJzJGJyYW5kKQojIGxpZ2h0VG9EYXJrIHRhYmxlCmFsbE51bWJlcnMgJT4lIGdyb3VwX2J5KGxpZ2h0VG9EYXJrKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKCIlIj1yb3VuZChuL3N1bShuKSoxMDAsMikpICN0YWJsZQpgYGAKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NzYWdlPUZBTFNFfQojIGhpc3RvZ3JhbTogbGlnaHRuZXNzCmhpc3QgPSBhbGxDYXRlZ29yaWVzICU+JQogIGdncGxvdChhZXMobGlnaHRuZXNzKSkgKwogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC43LCBiaW5zID0gMTAwLCBmaWxsPSJzbGF0ZWdyZXkiKSArCiAgeGxpbSgwLCAxKSArIAogIGxhYnModGl0bGU9Ikhpc3RvZ3JhbTogbGlnaHRuZXNzIikgKyAKICB0aGVtZShwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiKQoKIyBkZW5zaXR5IHBsb3Q6IGxpZ2h0bmVzcyBhbmQgbGlnaHRUb0RhcmsKZGVucyA9IGFsbE51bWJlcnMgJT4lCiAgZ2dwbG90KGFlcyhsaWdodG5lc3MsIGNvbG9yPWxpZ2h0VG9EYXJrKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNiwgZmlsbD1OQSkgKwogIHhsaW0oMCwgMSkgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiM1Nzc1OTAiLCIjOTBiZTZkIiksIG5hLnZhbHVlPSIjZjk0MTQ0IikgKyAKICBsYWJzKHRpdGxlPSJEZW5zaXR5IHBsb3Q6IGxpZ2h0bmVzcyBhbmQgbGlnaHRUb0RhcmsiKSArIAogIHRoZW1lKHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpCgojIHBsb3QKKGhpc3QvZGVucykKYGBgCgpgYGB7cn0KIyBicmFuZHMgdGhhdCB1c2VzIHNlcXVlbnRpYWwgbnVtYmVyaW5nIHN5c3RlbSAoU05TKQpzZXEgPSBhbGxOdW1iZXJzICU+JSAKICBmaWx0ZXIoIWlzLm5hKGxpZ2h0VG9EYXJrKSkgJT4lIAogIGdyb3VwX2J5KGJyYW5kLHByb2R1Y3QsbGlnaHRUb0RhcmspICU+JQogIHRhbGx5KCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KGJyYW5kLGxpZ2h0VG9EYXJrKSAlPiUgCiAgc3VtbWFyaXNlKHByb2R1Y3Rfbj1uX2Rpc3RpbmN0KHByb2R1Y3QpKQoKIyBicmFuZHMgdGhhdCB1c2VzIFNOUyB0aGF0IGhhdmUgYm90aCBjbGFzc2VzIG9mIGxpZ2h0VG9EYXJrCnNlcSAlPiUgZ3JvdXBfYnkoYnJhbmQpICU+JSBzdW1tYXJpc2UoY2xhc3Nfbj1uX2Rpc3RpbmN0KGxpZ2h0VG9EYXJrKSkgJT4lIGZpbHRlcihjbGFzc19uPT0yKSAlPiUgY291bnQoKSAlPiUgdW5saXN0KCkgI25vIGJyYW5kcyB1c2VzIGJvdGggY2xhc3NlcwoKIyBicmFuZCBhbmQgbGlnaHRUb0RhcmsKc2VxICU+JSBncm91cF9ieShsaWdodFRvRGFyaykgJT4lIHRhbGx5KCkKYGBgCgoqIDYxIG91dCBvZiA2NCBtYWtldXAgYnJhbmRzIGluIHRoZSBkYXRhc2V0IHVzZXMgc2VxdWVudGlhbCBudW1iZXJpbmcgc3lzdGVtLCBvZiB3aGljaCA1NyBvcmdhbml6ZXMgdGhlaXIgY29sb3JzIGZyb20gbGlnaHQgdG8gZGFyawoKCgoKIyMjIyBBdmVyYWdlIHByb2R1Y3Qgc2hhZGVzIGJ5IGJyYW5kCmBgYHtyfQojIG51bWJlciBvZiBzaGFkZXMgYnkgcHJvZHVjdApzaGFkZXMgPSBhbGxTaGFkZXMgJT4lIAogIGdyb3VwX2J5KGJyYW5kLCBwcm9kdWN0KSAlPiUgCiAgc3VtbWFyaXNlKHNoYWRlc19uID0gbl9kaXN0aW5jdChoZXgpKQoKc3VtbWFyeShzaGFkZXMkc2hhZGVzX24pCgojIHByb2R1Y3RzIHdpdGggbW9yZSB0aGFuIDUwIHNoYWRlcwpzaGFkZXMgJT4lIGZpbHRlcihzaGFkZXNfbj49NTApICU+JSBhcnJhbmdlKGRlc2Moc2hhZGVzX24pKQpgYGAKCgpgYGB7cn0KIyBicmFuZHMsIHByb2R1Y3QgY291bnQgYW5kIGF2ZXJhZ2UgbnVtYmVyIG9mIHNoYWRlcwpzaGFkZXMyID0gc2hhZGVzICU+JSAKICBncm91cF9ieShicmFuZCkgJT4lCiAgc3VtbWFyaXNlKHByb2R1Y3RfY291bnQ9bl9kaXN0aW5jdChwcm9kdWN0KSwKICAgICAgICAgICAgYXZnX3NoYWRlcyA9IChtZWFuKHNoYWRlc19uKSkpICU+JQogIGFycmFuZ2UoZGVzYyhhdmdfc2hhZGVzKSkKc2hhZGVzMgpgYGAKCmBgYHtyfQpoaXN0KHNoYWRlczIkcHJvZHVjdF9jb3VudCkKYGBgCgoKYGBge3J9CnNoYWRlczIgJT4lIG11dGF0ZShjYXQgPSBpZmVsc2UocHJvZHVjdF9jb3VudD09MSwiMSBwcm9kdWN0IiwiPjEgcHJvZHVjdCIpKSAlPiUgCiAgZ3JvdXBfYnkoY2F0KSAlPiUKICBzdW1tYXJpc2UoYXZnMSA9IG1lYW4oYXZnX3NoYWRlcykpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgQnJhbmRzIHdpdGggMSBwcm9kdWN0IHZlcnN1cyBCcmFuZHMgd2l0aCA+MSBwcm9kdWN0CnNoYWRlczIgJT4lIG11dGF0ZShjYXQgPSBpZmVsc2UocHJvZHVjdF9jb3VudD09MSwiMSBwcm9kdWN0IiwiPjEgcHJvZHVjdCIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YXZnX3NoYWRlcywgeT0gY2F0KSkgKyAKICBnZW9tX2JhcihzdGF0PSJzdW1tYXJ5IixmdW4ueD0ibWVhbiIsIHdpZHRoPTAuNSwgYWxwaGE9MC41LCBmaWxsPSIjYjhiOGQxIikgKwogIGdlb21fYm94cGxvdChmaWxsPU5BLCB3aWR0aD0wLjIsIG91dGxpZXIuc2l6ZT0tMSwgY29sb3I9IiNmMjhmM2IiKSArCiAgZ2VvbV9qaXR0ZXIoaGVpZ2h0PTAuMiwgc2l6ZT0xLjUsIGFscGhhPTAuOCwgY29sb3I9IiMyNTVmODUiKSArIAogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTApICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QubWFyZ2luPW1hcmdpbigxLDEsMSwxLCJjbSIpLAogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpICsgCiAgbGFicyh4PSJBdmVyYWdlIHByb2R1Y3Qgc2hhZGVzIChicmFuZCkiLAogICAgICAgeT0iIiwKICAgICAgIHRpdGxlPSJBdmVyYWdlIHByb2R1Y3Qgc2hhZGVzIiwKICAgICAgIHN1YnRpdGxlPSJCcmFuZHMgd2l0aCAxIHByb2R1Y3QgdnMuIEJyYW5kcyB3aXRoID4xIHByb2R1Y3QiKQpgYGAKCiMjIyMgMTAgYnJhbmRzIHdpdGggdGhlIG1vc3Qgc2hhZGVzCgpgYGB7cn0KIyBiYXJwbG90IG9mIDEwIGJyYW5kcyB3aXRoIHRoZSBtb3N0IHNoYWRlcywgd2l0aCB0aGVpciBwYWxldHRlcwojIGNvZGUgcmVmZXJlbmNlOiBbUmljaGFyZCBWb2dnXShodHRwczovL3QuY28veTFJQkIzZmpNSj9hbXA9MSkKCmJyYW5kc19uID0gYWxsQ2F0ZWdvcmllcyAlPiUgCiAgZ3JvdXBfYnkoYnJhbmQpICU+JQogIHRhbGx5KHNvcnQ9VCkgJT4lCiAgc2xpY2UoMToxMCkgJT4lCiAgLiRicmFuZCAjIGdldCBicmFuZCBuYW1lcwoKYnJhbmRzX24KCiMgc29ydCAKc29ydGVkIDwtIGFsbENhdGVnb3JpZXMgJT4lCiAgZ3JvdXBfYnkoYnJhbmQpICU+JQogIGFycmFuZ2UoYnJhbmQsbGlnaHRuZXNzKSAlPiUKICBtdXRhdGUocmFuaz1yYW5rKGxpZ2h0bmVzcyx0aWVzLm1ldGhvZCA9ICJmaXJzdCIpKQpgYGAKCgpgYGB7cn0KIyBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdApnZXRfYnJhbmRfY29sb3JzIDwtIGZ1bmN0aW9uKGJyYW5kX25hbWUpIHsKICBzb3J0ZWQgJT4lCiAgICBmaWx0ZXIoYnJhbmQ9PWJyYW5kX25hbWUpCn0KCnBsb3RfYnJhbmRfY29sb3JzIDwtIGZ1bmN0aW9uKGJyYW5kX2RhdGEpIHsKICB0aXRsZSA8LSBicmFuZF9kYXRhW1sxLDFdXQogIAogIGdncGxvdChicmFuZF9kYXRhLGFlcyh4PXJhbmsseT1icmFuZCxmaWxsPWhleCkpICsgZ2VvbV90aWxlKCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWJyYW5kX2RhdGEkaGV4KSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzPWMoMCwzNzApKSArCiAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZT0xMCkrCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dCgpLAogICAgICAgICAgcGFuZWwuZ3JpZD1lbGVtZW50X2JsYW5rKCkpICsgCiAgICBjb29yZF9jYXJ0ZXNpYW4oZXhwYW5kPUZBTFNFKQp9CgpwbG90cyA8LSBsYXBwbHkoYnJhbmRzX24sZnVuY3Rpb24oeCkgewogIHggJT4lIAogICAgIGdldF9icmFuZF9jb2xvcnMoKSAlPiUgCiAgICAgcGxvdF9icmFuZF9jb2xvcnMoKQogIH0pCmBgYAoKCmBgYHtyfQojIHBsb3QKKHBsb3RzW1sxXV0pIC8gKHBsb3RzW1syXV0pIC8gKHBsb3RzW1szXV0pIC8gKHBsb3RzW1s0XV0pIC8gKHBsb3RzW1s1XV0pIC8gKHBsb3RzW1s2XV0pIC8gKHBsb3RzW1s3XV0pIC8gKHBsb3RzW1s4XV0pIC8gKHBsb3RzW1s5XV0pIC8gKHBsb3RzW1sxMF1dKSArIAogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICIxMCBicmFuZHMgd2l0aCB0aGUgbW9zdCBmb3VuZGF0aW9uIHNoYWRlcyIpCmBgYAoKCgpgYGB7cn0KIyBnZXQgMTAgbW9zdCBmcmVxdWVudCBuYW1lIChwcm9ncmFtbWF0aWNhbGx5IGV4dHJhY3RlZCB3b3JkLWJhc2VkIG5hbWUgb2YgdGhpcyBwYXJ0aWN1bGFyIHNoYWRlKSAKbmFtZXMxID0gCiAgYWxsU2hhZGVzICU+JSAKICBtdXRhdGUobmFtZT10b2xvd2VyKG5hbWUpKSAlPiUKICBjb3VudChuYW1lKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBtdXRhdGUocmFuaz1yYW5rKGRlc2MobiksIHRpZXMubWV0aG9kID0gImZpcnN0IikpICNjb2RlIGZyb20gW0NhbCBXZWJiXShodHRwczovL3QuY28vS3JFNk50UVBhdz9hbXA9MSkKCiMgam9pbiByYW5rIHRvIGRhdGEgCm5hbWVzMiA9IGxlZnRfam9pbihhbGxTaGFkZXMgJT4lIG11dGF0ZShuYW1lID0gdG9sb3dlcihuYW1lKSksIG5hbWVzMSwgYnkgPSBjKCJuYW1lIikpICU+JSBkcm9wX25hKCkKCiMgc2hhZGUgY29sb3JzCnNoYWRlX2NvbCA8LSBuYW1lczIgJT4lIAogIGZpbHRlcihyYW5rPD0xMCkgJT4lIHB1bGwoaGV4LCBoZXgpICNwdWxsIGhleCBjb2RlIGZyb20gW0phbWllIEF2ZW5kYW5vXShodHRwczovL3QuY28vaHhteDU1YnhXQz9hbXA9MSkKCiMgcGxvdApuYW1lczIgJT4lIGZpbHRlcihyYW5rPD0xMCkgJT4lCiAgZ2dwbG90KGFlcyh5PWZjdF9yZW9yZGVyKG5hbWUsbGlnaHRuZXNzLC5mdW4gPSBtZWRpYW4sIC5kZXNjPVRSVUUpLCB4PSBsaWdodG5lc3MpKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BLCBjb2xvcj0ic2xhdGVncmV5Iix3aWR0aD0wLjcpICsgCiAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yPWhleCksYWxwaGE9MC42LCBzaG93LmxlZ2VuZCA9IEYsIGhlaWdodD0wLjIsIHNpemU9MS41KSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9c2hhZGVfY29sKSArIAogIHRoZW1lKHBsb3QubWFyZ2luPW1hcmdpbigxLDEsMSwxLCJjbSIpLAogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIKICAgICAgICAjcGxvdC50aXRsZT1lbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpLAogICAgICAgICNheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChmYWNlPSJib2xkIikKICAgICAgICApICsgCiAgbGFicyh4PSJOYW1lIiwgeT0gIkxpZ2h0bmVzcyIsCiAgICAgICB0aXRsZT0iTGlnaHRuZXNzIG9mIDEwIE1vc3QgRnJlcXVlbnQgV29yZC1iYXNlZCBTaGFkZSBOYW1lIiwKICAgICAgIHN1YnRpdGxlPSJXaGVyZSBMaWdodG5lc3MgMCBpcyBwdXJlIGJsYWNrIGFuZCAxIGlzIHB1cmUgd2hpdGVcbiIpCgpgYGAKCg==