About


This tutorial presents an original data visualization developed by Morgan Attannon. I am a data scientist with an engineering background in applied mathematics and mathematical modelling. I use R and its visualization ecosystem to explore datasets and create clear, reproducible, and visually engaging graphics.

You can find more of my projects and source code on GitHub.

As a teaser, here is the plot we’re gonna try building:

knitr::include_graphics("/cloud/project/plot_21.png")

Libraries


We’re going to load the packages we need for our work, just a few tips to help us better structure our code. So we need the essentials:

  1. Tidyverse, which needs no introduction;
  2. janitor, which will be used to apply uniform formatting to all the column names in our database;
  3. ggrepel, the ultimate package for automatic annotation without overlapping text;
  4. ggnewscale, which I also discovered. It allows you to use different color scales on the same graph. More on that later!
library(tidyverse)
library(janitor)
library(ggrepel)
library(ggnewscale)

Dataset


The database used in this tutorial comes from the Los Angeles Police Department (LAPD) and covers the period from January 2020 to September 2023. It has already been processed and only the columns needed for this tutorial are available, to make it less voluminous.

#df <- read_csv("los_angeles_crime_data.csv") 
df <- read_csv("https://drive.google.com/uc?export=download&id=15GVDzPx6AMSUPZk5gBtg6ETNyRRD-aBR")

Basic circular bar chart with hours


First we’ll define a theme and also a set of colors to be used for the graphic.

 theme<-theme_minimal()+
  theme(
    axis.text = element_blank(),
    axis.title = element_blank(),
    panel.grid = element_blank(),
    plot.margin = unit(rep(-2,4), "cm"),
    legend.position = "none",
    plot.background = element_blank()
  )

bg = "#FDF6E3"
text_color = "#9C2949"
primary_color = "#6B0F1A"
bar_color = "#0C1821"
hour_class_color = c("#256EFF", "#FF2E00", "#BF6900", "#3E8914")

Let’s create a simple circular bar chart, adding watch elements such as hour markers and two hands pointing to the bars representing the two highest crime numbers.

df %>%
  count(hour_occ, sort = TRUE) %>%
  ggplot(aes(x=hour_occ,y=n))+
  geom_col()+
  geom_text(aes(x=hour_occ, y=-14000, label=hour_occ, hjust=0.5), color=primary_color, fontface="bold", size=2.5)+
  geom_point(aes(x=hour_occ, y=-7000),size=2)+
  labs(
    x=NULL,
    y=NULL
    )+
  ylim(-60000,70000)+
  coord_polar(start = -0.13)+
  theme+
  annotate("segment", x=13, xend=13, y=-27000, yend=-60000, linewidth=0.4, color= primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) +
  annotate("segment", x=19, xend=19, y=-27000, yend=-60000, linewidth=0.4, color=primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) 

The problem with this graph is that it doesn’t really look like a watch, at least not very much, and the bars representing 11 p.m.(23) and 00 a.m.(0) are stuck together. So how do you do it?

In fact, a watch is marked with hours and minutes. What we need to do is set up dots to represent hours separated by minutes. The problem is that our graph is represented over 24 hours instead of 12 for a normal watch. However, separating the hour dots from 4 minute dots wouldn’t be very aesthetic, so we’ll do it with two (what we call gap=3 in the following).

Especially as it would be impossible to do this with the current state of the database. You’re probably wondering why this is impossible. In fact, the x-axis of the current graph has 24 discrete (categorical) values, so increasing the number of points to gap*24=72 in the geom_point function would create a shifted graph that’s completely different from what we want the result to be. So we’re going to have to get crafty!

First circular level of the graph, with the watch.


Firstly, we’re going to reformat our database so as to obtain a hollow matrix, i.e. integrate two empty rows supposed to represent the minutes after each hour.

gap = 3
width = gap*2.75/3

Why this width expression? By replacing the gap value=3 in the expression, we get width=2.75. In fact, we want to represent 24 bars on an axis of 3*24=72 values. There will therefore be a lot of empty space between each bar. To avoid this, width must be proportional to the available space, which is 3, reduced by .25 to give a little space. We then took the liberty of making the expression compatible with any gap value.

Secondly, we’re going to create the value_data database, which, as you may have guessed, contains counts of the number of crimes committed over 24 hours.

value_data <- df %>%
  count(hour_occ, sort = TRUE) %>%
  mutate(
    id = as.factor(as.numeric(hour_occ)*gap)
  ) 

which we’ll then join with a 72-line empty_data database to obtain mixed_data.

empty_data <- data.frame(id=as.factor(seq(0, nrow(value_data)*gap-1)))

mixed_data <- left_join(empty_data, value_data, by = join_by(id)) 

We are now ready to plot the result.

mixed_data %>%
  slice(1:(gap*24-1)) %>%
  ggplot(aes(x=id, y=n))+
  geom_col(width=width)+
  geom_text(aes(x=id, y=-14000, label=hour_occ, hjust=0.5), color=primary_color, fontface="bold", size=1.75)+
  annotate("segment", x=37, xend=37, y=-27000, yend=-60000, linewidth=0.4, color= primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) +
  annotate("segment", x=55, xend=55, y=-27000, yend=-60000, linewidth=0.4, color=primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) +
  geom_point(aes(x=id, y=-7000))+
  geom_point(aes(x=0, y=-60000), color=primary_color, size=gap/1.5)+
  labs(
    x=NULL,
    y=NULL
    )+
  ylim(-60000,70000) +
  coord_polar(start = -0.13)+
  theme

Now that’s much better!

We’re now going to integrate:

  • A color scale on the graph using the highlight variable, using a more accentuated color on the two largest bars.
  • A different size for each point used to mark hours and minutes,
  • An annotation of the representative number of each bar, but only for the two largest.
  • colors representative of each hour class, which we’ll use later.
value_data <- df %>%
  count(hour_occ) %>%
  mutate(
    id = as.factor(as.numeric(hour_occ)*gap),
    highlight = if_else(hour_occ %in% c("12","18"), "1","0"),
    value = if_else(hour_occ %in% c("12","18"), format(n, big.mark = ","), NA)
  ) 

mixed_data <- 
  left_join(empty_data, value_data, by = join_by(id)) %>%
  mutate(
    size = if_else(is.na(hour_occ),gap/6,gap/3),
    angle = 5-(360 * (as.integer(id))/nrow(.)),
    angle = ifelse(angle <= -90 & angle > -270, angle+180, angle),
    hour_class = cut(as.numeric(id), breaks = c(-1,6,12,18,24)*gap, labels = c("00-06H", "06-12H", "12-18H", "18-00H"))
  )


plot<-mixed_data %>%
  slice(1:(gap*24-1)) %>%
  ggplot(aes(x=id, y=n))+
  geom_col(aes(fill=highlight), width=width)+
  geom_text(aes(x=id, y=-14000, label=hour_occ, hjust=0.5), color=primary_color, fontface="bold", size=1.75)+
  geom_text(aes(x=id, y=n+4000, label=value, hjust=0.5,), color=primary_color, alpha=1, size=1.75, angle=mixed_data$angle[1:71], fontface="bold")+
  annotate("segment", x=37, xend=37, y=-27000, yend=-60000, linewidth=0.4, color= primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) +
  annotate("segment", x=55, xend=55, y=-27000, yend=-60000, linewidth=0.4, color=primary_color, arrow = arrow(ends="first", type="closed", length = unit(0.15, "cm"))) +
  annotate("text", x=0, y=-30000, label="Hours", hjust=0.4, color=primary_color, fontface="bold", size=2) +
  geom_point(aes(x=0, y=-60000), color=primary_color, size=gap/1.5)+
  scale_fill_manual(values = c(alpha(bar_color,0.6), alpha(bar_color,1)))+
  geom_point(aes(x=id, y=-7000, color=hour_class), size=mixed_data$size[1:71])+
  labs(
    x=NULL,
    y=NULL
    )+
  ylim(-60000,70000) +
  theme+
  coord_polar(start = -0.13)


plot

You’ll notice that both annotations are subject to a certain angle. To find out more about how to determine the angle, you can read the tutorial (Circular barplot with groups) [https://r-graph-gallery.com/297-circular-barplot-with-groups.html] in the gallery.

Now that the first circular level of the graphic has been completed, we can move on to the second level, where another challenge awaits!

Second circular chart level

The second circular level of the graph represents the most frequent crimes over a period of time corresponding to the hour class we described earlier.

Let’s start by adding to our main graph, the bar graph of the frequency of occurrence of a crime over a class of time.

Note that our x-axis can only carry 24*gap=72 bars, so we can only represent 72/4=18 per hour class. But we’ll limit ourselves to the top 16 frequencies of occurrence for each hour class.

crm_by_hour_class <- df %>%
  count(hour_class, crm_cd) %>%
  group_by(hour_class) %>%
  mutate(percentage = n*100/sum(n), lim=sum(n)) %>%
  top_n(percentage,n=(24*gap/4)-2) %>%
  ungroup()%>%
  group_split(hour_class) %>%
  map_dfr(~ .x[1:(nrow(.x)+2),]) %>%
  mutate(id = mixed_data$id)
plot+
  geom_col(data = filter(crm_by_hour_class, !(id==71)), aes(x=id, y=percentage*2500), show.legend = FALSE, inherit.aes = FALSE, fill=primary_color)

Why y=percentage*2500? In fact, to represent the frequencies of values between 0 and 100, there’s a problem of scale: the bars are then invisible compared to those of the first circular level, which represents values 1000 times higher. But we can modify the scale of all the values so that the bars are visible, since we’re just trying to visualize the difference in frequency between the different crimes.

But as you can see, the main flaw in this graph is its legibility, due to the fact that all the bars in the second circular level start at x=0. Once again, we’re going to have to work out how to change the origin of the bars.

How do we do it? We’re going to use a stacked bar chart, then assign zero opacity to the first stacking level.

duplicated_data<-rbind(
  crm_by_hour_class %>% mutate(lab=1, percentage=2500*percentage),
  crm_by_hour_class %>% mutate(
    lab=0,
    percentage=if_else(is.na(percentage),percentage, lim*0.29))
  )
plot+
  geom_col(data = filter(duplicated_data, !(id==71)),aes(x=id, y=percentage, fill=as.factor(lab)), position = position_stack(reverse = TRUE), show.legend = FALSE, inherit.aes = FALSE)+
  ylim(-60000,150000) 

The second circular level retains the same fill scale defined for the first circular level. Fortunately, the new_scale_fill function allows us to adjust this.

plot+
  new_scale_fill()+
  geom_col(data = filter(duplicated_data, !(id==71)),aes(x=id, y=percentage, fill=as.factor(lab)), position = position_stack(reverse = TRUE), show.legend = FALSE, inherit.aes = FALSE)+
  scale_fill_manual(values = c(alpha("skyblue",0), alpha(primary_color,1)))+
  ylim(-60000,150000)

Final plot


top3_label_crm_by_class_hour <- df %>%
  count(hour_class, crm_cd) %>%
  group_by(hour_class) %>%
  mutate(per = n*100/sum(n), label=paste0(round(per),"%")) %>%
  top_n(per,n=3) %>%
  ungroup() %>%
  left_join(.,distinct(select(df,crm_cd,crm_cd_desc)), by=join_by(crm_cd)) %>%
  select(-c(n,per)) %>%
  mutate(
    hjust = c(rep("left",6),rep("right",6)),
    nudge_x = c(-1,-.5,-0.8,1,-1,2,rep(-.2,3),0.5,-1,2),
    crm_cd_desc = stringr::str_replace(crm_cd_desc, pattern = " ", replacement = "\n"),
    crm_cd_desc = stringr::str_remove(crm_cd_desc, pattern = "-")
  )

crm_by_hour_class <- crm_by_hour_class %>%
  left_join(top3_label_crm_by_class_hour, by=join_by(hour_class, crm_cd))

duplicated_data<-rbind(
  crm_by_hour_class %>% mutate(lab=1, percentage=2500*percentage),
  crm_by_hour_class %>% mutate(
    lab=0,
    percentage=if_else(is.na(percentage),percentage, lim*0.29))
  )



plot+
  ylim(-60000,150000)+
  scale_fill_manual(values = c(alpha(bar_color,0.6), alpha(bar_color,1)))+
  new_scale_fill()+
  geom_point(aes(x=id, y=-7000, color=hour_class), size=mixed_data$size[1:71])+
  geom_line(data = filter(crm_by_hour_class, !(id==71)), aes(x = as.numeric(id), y=lim*0.27, color=hour_class), linewidth=1.5)+
  scale_color_manual(values = hour_class_color)+
  new_scale_color()+
  #scale_fill_viridis_d(option = "B")+
  geom_col(data = filter(duplicated_data, !(id==71)),aes(x=id, y=percentage, fill=as.factor(lab)), position = position_stack(reverse = TRUE), show.legend = FALSE, inherit.aes = FALSE)+
  scale_fill_manual(values = c(alpha("skyblue",0), alpha(primary_color,1)))+
  geom_text_repel(
    data = filter(duplicated_data, !(id==71)),
    aes(y=percentage+lim*0.29, label = crm_cd_desc, color=as.factor(lab)),
    #family = "Poppins",
    seed=1234,
    fontface="bold",
    size = 1.75,
    hjust=filter(duplicated_data, !(id==71))$hjust,
    #vjust=1,
    min.segment.length = 0, 
    arrow = arrow(length = unit(0.002, "npc")),
    nudge_x = filter(duplicated_data, !(id==71))$nudge_x,
    #nudge_y = .5,
    na.rm = TRUE,
    direction = "y",
  )+
  scale_color_manual(values = c(alpha("black",0), alpha(primary_color,1)))

LS0tCnRpdGxlOiAiTXVsdGktbGV2ZWwgY2lyY3VsYXIgYmFyIGNoYXJ0IHdpdGggaG91cmx5IGNyaW1lIHBhdHRlcm5zIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBmbGF0bHkKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBmaWdfd2lkdGg6IDkKICAgIGZpZ19oZWlnaHQ6IDYKICAgIGZpZ19yZXRpbmE6IDIKICAgIGNzczogc3R5bGUuY3NzCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQotLS0KCgpgYGB7ciBnbG9iYWwgb3B0aW9ucywgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgd2FybmluZyA9IEZBTFNFLAogIG1lc3NhZ2UgPSBGQUxTRSwKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICBvdXQud2lkdGggPSAiODglIiwKICBmaWcuaGVpZ2h0ID0gNSwKICBmaWcud2lkdGggPSA3LAogIGRwaSA9IDE2MAopCmBgYAoKPGRpdiBjbGFzcz0iY29udGFpbmVyIj4KCgojIEFib3V0CioqKgpUaGlzIHR1dG9yaWFsIHByZXNlbnRzIGFuIG9yaWdpbmFsIGRhdGEgdmlzdWFsaXphdGlvbiBkZXZlbG9wZWQgYnkgW01vcmdhbiBBdHRhbm5vbl0oaHR0cHM6Ly9naXRodWIuY29tL2F0bi1tcmduKS4gSSBhbSBhIGRhdGEgc2NpZW50aXN0IHdpdGggYW4gZW5naW5lZXJpbmcgYmFja2dyb3VuZCBpbiBhcHBsaWVkIG1hdGhlbWF0aWNzIGFuZCBtYXRoZW1hdGljYWwgbW9kZWxsaW5nLiBJIHVzZSBSIGFuZCBpdHMgdmlzdWFsaXphdGlvbiBlY29zeXN0ZW0gdG8gZXhwbG9yZSBkYXRhc2V0cyBhbmQgY3JlYXRlIGNsZWFyLCByZXByb2R1Y2libGUsIGFuZCB2aXN1YWxseSBlbmdhZ2luZyBncmFwaGljcy4KCllvdSBjYW4gZmluZCBtb3JlIG9mIG15IHByb2plY3RzIGFuZCBzb3VyY2UgY29kZSBvbiBbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vYXRuLW1yZ24pLgoKQXMgYSB0ZWFzZXIsIGhlcmUgaXMgdGhlIHBsb3Qgd2XigJlyZSBnb25uYSB0cnkgYnVpbGRpbmc6CgoKYGBge3IsIG91dC53aWR0aD0iNTAlIiwgZmlnLmFsaWduPSdjZW50ZXInfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiL2Nsb3VkL3Byb2plY3QvcGxvdF8yMS5wbmciKQpgYGAKCgoKIyBMaWJyYXJpZXMKKioqCldlJ3JlIGdvaW5nIHRvIGxvYWQgdGhlIHBhY2thZ2VzIHdlIG5lZWQgZm9yIG91ciB3b3JrLCBqdXN0IGEgZmV3IHRpcHMgdG8gaGVscCB1cyBiZXR0ZXIgc3RydWN0dXJlIG91ciBjb2RlLiBTbyB3ZSBuZWVkIHRoZSBlc3NlbnRpYWxzOgoKMS4gW1RpZHl2ZXJzZV0oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheSksIHdoaWNoIG5lZWRzIG5vIGludHJvZHVjdGlvbjsKMi4gW2phbml0b3JdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9qYW5pdG9yL3ZpZ25ldHRlcy9qYW5pdG9yLmh0bWwpLCB3aGljaCB3aWxsIGJlIHVzZWQgdG8gYXBwbHkgdW5pZm9ybSBmb3JtYXR0aW5nIHRvIGFsbCB0aGUgY29sdW1uIG5hbWVzIGluIG91ciBkYXRhYmFzZTsgCjMuIFtnZ3JlcGVsXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dyZXBlbC92aWduZXR0ZXMvZ2dyZXBlbC5odG1sKSwgdGhlIHVsdGltYXRlIHBhY2thZ2UgZm9yIGF1dG9tYXRpYyBhbm5vdGF0aW9uIHdpdGhvdXQgb3ZlcmxhcHBpbmcgdGV4dDsgCjQuIFtnZ25ld3NjYWxlXShodHRwczovL2dpdGh1Yi5jb20vZWxpb2NhbXAvZ2duZXdzY2FsZS90cmVlL21hc3RlciksIHdoaWNoIEkgYWxzbyBkaXNjb3ZlcmVkLiBJdCBhbGxvd3MgeW91IHRvIHVzZSBkaWZmZXJlbnQgY29sb3Igc2NhbGVzIG9uIHRoZSBzYW1lIGdyYXBoLiBNb3JlIG9uIHRoYXQgbGF0ZXIhCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KGdnbmV3c2NhbGUpCmBgYAoKCiMgRGF0YXNldAoqKioKVGhlIGRhdGFiYXNlIHVzZWQgaW4gdGhpcyB0dXRvcmlhbCBjb21lcyBmcm9tIHRoZSBMb3MgQW5nZWxlcyBQb2xpY2UgRGVwYXJ0bWVudCAoW0xBUERdKGh0dHBzOi8vd3d3LmxhcGRvbmxpbmUub3JnLykpIGFuZCBjb3ZlcnMgdGhlIHBlcmlvZCBmcm9tIEphbnVhcnkgMjAyMCB0byBTZXB0ZW1iZXIgMjAyMy4gSXQgaGFzIGFscmVhZHkgYmVlbiBwcm9jZXNzZWQgYW5kIG9ubHkgdGhlIGNvbHVtbnMgbmVlZGVkIGZvciB0aGlzIHR1dG9yaWFsIGFyZSBhdmFpbGFibGUsIHRvIG1ha2UgaXQgbGVzcyB2b2x1bWlub3VzLgoKCmBgYHtyfQojZGYgPC0gcmVhZF9jc3YoImxvc19hbmdlbGVzX2NyaW1lX2RhdGEuY3N2IikgCmRmIDwtIHJlYWRfY3N2KCJodHRwczovL2RyaXZlLmdvb2dsZS5jb20vdWM/ZXhwb3J0PWRvd25sb2FkJmlkPTE1R1ZEelB4NkFNU1VQWms1Z0J0ZzZFVE55UlJELWFCUiIpCmBgYAoKIyBCYXNpYyBjaXJjdWxhciBiYXIgY2hhcnQgd2l0aCBob3VycwoqKioKRmlyc3Qgd2UnbGwgZGVmaW5lIGEgdGhlbWUgYW5kIGFsc28gYSBzZXQgb2YgY29sb3JzIHRvIGJlIHVzZWQgZm9yIHRoZSBncmFwaGljLgoKYGBge3J9CiB0aGVtZTwtdGhlbWVfbWluaW1hbCgpKwogIHRoZW1lKAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90Lm1hcmdpbiA9IHVuaXQocmVwKC0yLDQpLCAiY20iKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKQogICkKCmJnID0gIiNGREY2RTMiCnRleHRfY29sb3IgPSAiIzlDMjk0OSIKcHJpbWFyeV9jb2xvciA9ICIjNkIwRjFBIgpiYXJfY29sb3IgPSAiIzBDMTgyMSIKaG91cl9jbGFzc19jb2xvciA9IGMoIiMyNTZFRkYiLCAiI0ZGMkUwMCIsICIjQkY2OTAwIiwgIiMzRTg5MTQiKQpgYGAKCkxldCdzIGNyZWF0ZSBhIHNpbXBsZSBjaXJjdWxhciBiYXIgY2hhcnQsIGFkZGluZyB3YXRjaCBlbGVtZW50cyBzdWNoIGFzIGhvdXIgbWFya2VycyBhbmQgdHdvIGhhbmRzIHBvaW50aW5nIHRvIHRoZSBiYXJzIHJlcHJlc2VudGluZyB0aGUgdHdvIGhpZ2hlc3QgY3JpbWUgbnVtYmVycy4KCmBgYHtyfQpkZiAlPiUKICBjb3VudChob3VyX29jYywgc29ydCA9IFRSVUUpICU+JQogIGdncGxvdChhZXMoeD1ob3VyX29jYyx5PW4pKSsKICBnZW9tX2NvbCgpKwogIGdlb21fdGV4dChhZXMoeD1ob3VyX29jYywgeT0tMTQwMDAsIGxhYmVsPWhvdXJfb2NjLCBoanVzdD0wLjUpLCBjb2xvcj1wcmltYXJ5X2NvbG9yLCBmb250ZmFjZT0iYm9sZCIsIHNpemU9Mi41KSsKICBnZW9tX3BvaW50KGFlcyh4PWhvdXJfb2NjLCB5PS03MDAwKSxzaXplPTIpKwogIGxhYnMoCiAgICB4PU5VTEwsCiAgICB5PU5VTEwKICAgICkrCiAgeWxpbSgtNjAwMDAsNzAwMDApKwogIGNvb3JkX3BvbGFyKHN0YXJ0ID0gLTAuMTMpKwogIHRoZW1lKwogIGFubm90YXRlKCJzZWdtZW50IiwgeD0xMywgeGVuZD0xMywgeT0tMjcwMDAsIHllbmQ9LTYwMDAwLCBsaW5ld2lkdGg9MC40LCBjb2xvcj0gcHJpbWFyeV9jb2xvciwgYXJyb3cgPSBhcnJvdyhlbmRzPSJmaXJzdCIsIHR5cGU9ImNsb3NlZCIsIGxlbmd0aCA9IHVuaXQoMC4xNSwgImNtIikpKSArCiAgYW5ub3RhdGUoInNlZ21lbnQiLCB4PTE5LCB4ZW5kPTE5LCB5PS0yNzAwMCwgeWVuZD0tNjAwMDAsIGxpbmV3aWR0aD0wLjQsIGNvbG9yPXByaW1hcnlfY29sb3IsIGFycm93ID0gYXJyb3coZW5kcz0iZmlyc3QiLCB0eXBlPSJjbG9zZWQiLCBsZW5ndGggPSB1bml0KDAuMTUsICJjbSIpKSkgCmBgYAoKVGhlIHByb2JsZW0gd2l0aCB0aGlzIGdyYXBoIGlzIHRoYXQgaXQgZG9lc24ndCByZWFsbHkgbG9vayBsaWtlIGEgd2F0Y2gsIGF0IGxlYXN0IG5vdCB2ZXJ5IG11Y2gsIGFuZCB0aGUgYmFycyByZXByZXNlbnRpbmcgMTEgcC5tLigyMykgYW5kIDAwIGEubS4oMCkgYXJlIHN0dWNrIHRvZ2V0aGVyLiBTbyBob3cgZG8geW91IGRvIGl0PwoKSW4gZmFjdCwgYSB3YXRjaCBpcyBtYXJrZWQgd2l0aCBob3VycyBhbmQgbWludXRlcy4gV2hhdCB3ZSBuZWVkIHRvIGRvIGlzIHNldCB1cCBkb3RzIHRvIHJlcHJlc2VudCBob3VycyBzZXBhcmF0ZWQgYnkgbWludXRlcy4gVGhlIHByb2JsZW0gaXMgdGhhdCBvdXIgZ3JhcGggaXMgcmVwcmVzZW50ZWQgb3ZlciAyNCBob3VycyBpbnN0ZWFkIG9mIDEyIGZvciBhIG5vcm1hbCB3YXRjaC4gSG93ZXZlciwgc2VwYXJhdGluZyB0aGUgaG91ciBkb3RzIGZyb20gNCBtaW51dGUgZG90cyB3b3VsZG4ndCBiZSB2ZXJ5IGFlc3RoZXRpYywgc28gd2UnbGwgZG8gaXQgd2l0aCB0d28gKHdoYXQgd2UgY2FsbCBnYXA9MyBpbiB0aGUgZm9sbG93aW5nKS4KCkVzcGVjaWFsbHkgYXMgaXQgd291bGQgYmUgaW1wb3NzaWJsZSB0byBkbyB0aGlzIHdpdGggdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhlIGRhdGFiYXNlLiBZb3UncmUgcHJvYmFibHkgd29uZGVyaW5nIHdoeSB0aGlzIGlzIGltcG9zc2libGUuIEluIGZhY3QsIHRoZSB4LWF4aXMgb2YgdGhlIGN1cnJlbnQgZ3JhcGggaGFzIDI0IGRpc2NyZXRlIChjYXRlZ29yaWNhbCkgdmFsdWVzLCBzbyBpbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgcG9pbnRzIHRvIGBnYXAqMjQ9NzJgIGluIHRoZSBnZW9tX3BvaW50IGZ1bmN0aW9uIHdvdWxkIGNyZWF0ZSBhIHNoaWZ0ZWQgZ3JhcGggdGhhdCdzIGNvbXBsZXRlbHkgZGlmZmVyZW50IGZyb20gd2hhdCB3ZSB3YW50IHRoZSByZXN1bHQgdG8gYmUuIFNvIHdlJ3JlIGdvaW5nIHRvIGhhdmUgdG8gZ2V0IGNyYWZ0eSEKCgojIEZpcnN0IGNpcmN1bGFyIGxldmVsIG9mIHRoZSBncmFwaCwgd2l0aCB0aGUgd2F0Y2guCioqKgoKRmlyc3RseSwgd2UncmUgZ29pbmcgdG8gcmVmb3JtYXQgb3VyIGRhdGFiYXNlIHNvIGFzIHRvIG9idGFpbiBhIGhvbGxvdyBtYXRyaXgsIGkuZS4gaW50ZWdyYXRlIHR3byBlbXB0eSByb3dzIHN1cHBvc2VkIHRvIHJlcHJlc2VudCB0aGUgbWludXRlcyBhZnRlciBlYWNoIGhvdXIuCgpgYGB7cn0KZ2FwID0gMwp3aWR0aCA9IGdhcCoyLjc1LzMKYGBgCgpXaHkgdGhpcyB3aWR0aCBleHByZXNzaW9uPwpCeSByZXBsYWNpbmcgdGhlIGdhcCB2YWx1ZT0zIGluIHRoZSBleHByZXNzaW9uLCB3ZSBnZXQgYHdpZHRoPTIuNzVgLgpJbiBmYWN0LCB3ZSB3YW50IHRvIHJlcHJlc2VudCAyNCBiYXJzIG9uIGFuIGF4aXMgb2YgYDMqMjQ9NzJgIHZhbHVlcy4gVGhlcmUgd2lsbCB0aGVyZWZvcmUgYmUgYSBsb3Qgb2YgZW1wdHkgc3BhY2UgYmV0d2VlbiBlYWNoIGJhci4gVG8gYXZvaWQgdGhpcywgd2lkdGggbXVzdCBiZSBwcm9wb3J0aW9uYWwgdG8gdGhlIGF2YWlsYWJsZSBzcGFjZSwgd2hpY2ggaXMgMywgcmVkdWNlZCBieSAuMjUgdG8gZ2l2ZSBhIGxpdHRsZSBzcGFjZS4KV2UgdGhlbiB0b29rIHRoZSBsaWJlcnR5IG9mIG1ha2luZyB0aGUgZXhwcmVzc2lvbiBjb21wYXRpYmxlIHdpdGggYW55IGdhcCB2YWx1ZS4KClNlY29uZGx5LCB3ZSdyZSBnb2luZyB0byBjcmVhdGUgdGhlIHZhbHVlX2RhdGEgZGF0YWJhc2UsIHdoaWNoLCBhcyB5b3UgbWF5IGhhdmUgZ3Vlc3NlZCwgY29udGFpbnMgY291bnRzIG9mIHRoZSBudW1iZXIgb2YgY3JpbWVzIGNvbW1pdHRlZCBvdmVyIDI0IGhvdXJzLgoKYGBge3J9CnZhbHVlX2RhdGEgPC0gZGYgJT4lCiAgY291bnQoaG91cl9vY2MsIHNvcnQgPSBUUlVFKSAlPiUKICBtdXRhdGUoCiAgICBpZCA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGhvdXJfb2NjKSpnYXApCiAgKSAKYGBgCgp3aGljaCB3ZSdsbCB0aGVuIGpvaW4gd2l0aCBhIDcyLWxpbmUgZW1wdHlfZGF0YSBkYXRhYmFzZSB0byBvYnRhaW4gbWl4ZWRfZGF0YS4KCmBgYHtyfQplbXB0eV9kYXRhIDwtIGRhdGEuZnJhbWUoaWQ9YXMuZmFjdG9yKHNlcSgwLCBucm93KHZhbHVlX2RhdGEpKmdhcC0xKSkpCgptaXhlZF9kYXRhIDwtIGxlZnRfam9pbihlbXB0eV9kYXRhLCB2YWx1ZV9kYXRhLCBieSA9IGpvaW5fYnkoaWQpKSAKYGBgCgpXZSBhcmUgbm93IHJlYWR5IHRvIHBsb3QgdGhlIHJlc3VsdC4KCmBgYHtyfQptaXhlZF9kYXRhICU+JQogIHNsaWNlKDE6KGdhcCoyNC0xKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWlkLCB5PW4pKSsKICBnZW9tX2NvbCh3aWR0aD13aWR0aCkrCiAgZ2VvbV90ZXh0KGFlcyh4PWlkLCB5PS0xNDAwMCwgbGFiZWw9aG91cl9vY2MsIGhqdXN0PTAuNSksIGNvbG9yPXByaW1hcnlfY29sb3IsIGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0xLjc1KSsKICBhbm5vdGF0ZSgic2VnbWVudCIsIHg9MzcsIHhlbmQ9MzcsIHk9LTI3MDAwLCB5ZW5kPS02MDAwMCwgbGluZXdpZHRoPTAuNCwgY29sb3I9IHByaW1hcnlfY29sb3IsIGFycm93ID0gYXJyb3coZW5kcz0iZmlyc3QiLCB0eXBlPSJjbG9zZWQiLCBsZW5ndGggPSB1bml0KDAuMTUsICJjbSIpKSkgKwogIGFubm90YXRlKCJzZWdtZW50IiwgeD01NSwgeGVuZD01NSwgeT0tMjcwMDAsIHllbmQ9LTYwMDAwLCBsaW5ld2lkdGg9MC40LCBjb2xvcj1wcmltYXJ5X2NvbG9yLCBhcnJvdyA9IGFycm93KGVuZHM9ImZpcnN0IiwgdHlwZT0iY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCgwLjE1LCAiY20iKSkpICsKICBnZW9tX3BvaW50KGFlcyh4PWlkLCB5PS03MDAwKSkrCiAgZ2VvbV9wb2ludChhZXMoeD0wLCB5PS02MDAwMCksIGNvbG9yPXByaW1hcnlfY29sb3IsIHNpemU9Z2FwLzEuNSkrCiAgbGFicygKICAgIHg9TlVMTCwKICAgIHk9TlVMTAogICAgKSsKICB5bGltKC02MDAwMCw3MDAwMCkgKwogIGNvb3JkX3BvbGFyKHN0YXJ0ID0gLTAuMTMpKwogIHRoZW1lCiAgCmBgYAoKTm93IHRoYXQncyBtdWNoIGJldHRlciEKCldlJ3JlIG5vdyBnb2luZyB0byBpbnRlZ3JhdGU6CgoqIEEgY29sb3Igc2NhbGUgb24gdGhlIGdyYXBoIHVzaW5nIHRoZSBoaWdobGlnaHQgdmFyaWFibGUsIHVzaW5nIGEgbW9yZSBhY2NlbnR1YXRlZCBjb2xvciBvbiB0aGUgdHdvIGxhcmdlc3QgYmFycy4KKiBBIGRpZmZlcmVudCBzaXplIGZvciBlYWNoIHBvaW50IHVzZWQgdG8gbWFyayBob3VycyBhbmQgbWludXRlcywKKiBBbiBhbm5vdGF0aW9uIG9mIHRoZSByZXByZXNlbnRhdGl2ZSBudW1iZXIgb2YgZWFjaCBiYXIsIGJ1dCBvbmx5IGZvciB0aGUgdHdvIGxhcmdlc3QuCiogY29sb3JzIHJlcHJlc2VudGF0aXZlIG9mIGVhY2ggaG91ciBjbGFzcywgd2hpY2ggd2UnbGwgdXNlIGxhdGVyLgoKYGBge3J9CnZhbHVlX2RhdGEgPC0gZGYgJT4lCiAgY291bnQoaG91cl9vY2MpICU+JQogIG11dGF0ZSgKICAgIGlkID0gYXMuZmFjdG9yKGFzLm51bWVyaWMoaG91cl9vY2MpKmdhcCksCiAgICBoaWdobGlnaHQgPSBpZl9lbHNlKGhvdXJfb2NjICVpbiUgYygiMTIiLCIxOCIpLCAiMSIsIjAiKSwKICAgIHZhbHVlID0gaWZfZWxzZShob3VyX29jYyAlaW4lIGMoIjEyIiwiMTgiKSwgZm9ybWF0KG4sIGJpZy5tYXJrID0gIiwiKSwgTkEpCiAgKSAKCm1peGVkX2RhdGEgPC0gCiAgbGVmdF9qb2luKGVtcHR5X2RhdGEsIHZhbHVlX2RhdGEsIGJ5ID0gam9pbl9ieShpZCkpICU+JQogIG11dGF0ZSgKICAgIHNpemUgPSBpZl9lbHNlKGlzLm5hKGhvdXJfb2NjKSxnYXAvNixnYXAvMyksCiAgICBhbmdsZSA9IDUtKDM2MCAqIChhcy5pbnRlZ2VyKGlkKSkvbnJvdyguKSksCiAgICBhbmdsZSA9IGlmZWxzZShhbmdsZSA8PSAtOTAgJiBhbmdsZSA+IC0yNzAsIGFuZ2xlKzE4MCwgYW5nbGUpLAogICAgaG91cl9jbGFzcyA9IGN1dChhcy5udW1lcmljKGlkKSwgYnJlYWtzID0gYygtMSw2LDEyLDE4LDI0KSpnYXAsIGxhYmVscyA9IGMoIjAwLTA2SCIsICIwNi0xMkgiLCAiMTItMThIIiwgIjE4LTAwSCIpKQogICkKCgpwbG90PC1taXhlZF9kYXRhICU+JQogIHNsaWNlKDE6KGdhcCoyNC0xKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWlkLCB5PW4pKSsKICBnZW9tX2NvbChhZXMoZmlsbD1oaWdobGlnaHQpLCB3aWR0aD13aWR0aCkrCiAgZ2VvbV90ZXh0KGFlcyh4PWlkLCB5PS0xNDAwMCwgbGFiZWw9aG91cl9vY2MsIGhqdXN0PTAuNSksIGNvbG9yPXByaW1hcnlfY29sb3IsIGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0xLjc1KSsKICBnZW9tX3RleHQoYWVzKHg9aWQsIHk9bis0MDAwLCBsYWJlbD12YWx1ZSwgaGp1c3Q9MC41LCksIGNvbG9yPXByaW1hcnlfY29sb3IsIGFscGhhPTEsIHNpemU9MS43NSwgYW5nbGU9bWl4ZWRfZGF0YSRhbmdsZVsxOjcxXSwgZm9udGZhY2U9ImJvbGQiKSsKICBhbm5vdGF0ZSgic2VnbWVudCIsIHg9MzcsIHhlbmQ9MzcsIHk9LTI3MDAwLCB5ZW5kPS02MDAwMCwgbGluZXdpZHRoPTAuNCwgY29sb3I9IHByaW1hcnlfY29sb3IsIGFycm93ID0gYXJyb3coZW5kcz0iZmlyc3QiLCB0eXBlPSJjbG9zZWQiLCBsZW5ndGggPSB1bml0KDAuMTUsICJjbSIpKSkgKwogIGFubm90YXRlKCJzZWdtZW50IiwgeD01NSwgeGVuZD01NSwgeT0tMjcwMDAsIHllbmQ9LTYwMDAwLCBsaW5ld2lkdGg9MC40LCBjb2xvcj1wcmltYXJ5X2NvbG9yLCBhcnJvdyA9IGFycm93KGVuZHM9ImZpcnN0IiwgdHlwZT0iY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCgwLjE1LCAiY20iKSkpICsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MCwgeT0tMzAwMDAsIGxhYmVsPSJIb3VycyIsIGhqdXN0PTAuNCwgY29sb3I9cHJpbWFyeV9jb2xvciwgZm9udGZhY2U9ImJvbGQiLCBzaXplPTIpICsKICBnZW9tX3BvaW50KGFlcyh4PTAsIHk9LTYwMDAwKSwgY29sb3I9cHJpbWFyeV9jb2xvciwgc2l6ZT1nYXAvMS41KSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGFscGhhKGJhcl9jb2xvciwwLjYpLCBhbHBoYShiYXJfY29sb3IsMSkpKSsKICBnZW9tX3BvaW50KGFlcyh4PWlkLCB5PS03MDAwLCBjb2xvcj1ob3VyX2NsYXNzKSwgc2l6ZT1taXhlZF9kYXRhJHNpemVbMTo3MV0pKwogIGxhYnMoCiAgICB4PU5VTEwsCiAgICB5PU5VTEwKICAgICkrCiAgeWxpbSgtNjAwMDAsNzAwMDApICsKICB0aGVtZSsKICBjb29yZF9wb2xhcihzdGFydCA9IC0wLjEzKQoKCnBsb3QKYGBgCgpZb3UnbGwgbm90aWNlIHRoYXQgYm90aCBhbm5vdGF0aW9ucyBhcmUgc3ViamVjdCB0byBhIGNlcnRhaW4gYW5nbGUuIFRvIGZpbmQgb3V0IG1vcmUgYWJvdXQgaG93IHRvIGRldGVybWluZSB0aGUgYW5nbGUsIHlvdSBjYW4gcmVhZCB0aGUgdHV0b3JpYWwgKENpcmN1bGFyIGJhcnBsb3Qgd2l0aCBncm91cHMpIFtodHRwczovL3ItZ3JhcGgtZ2FsbGVyeS5jb20vMjk3LWNpcmN1bGFyLWJhcnBsb3Qtd2l0aC1ncm91cHMuaHRtbF0gaW4gdGhlIGdhbGxlcnkuCgpOb3cgdGhhdCB0aGUgZmlyc3QgY2lyY3VsYXIgbGV2ZWwgb2YgdGhlIGdyYXBoaWMgaGFzIGJlZW4gY29tcGxldGVkLCB3ZSBjYW4gbW92ZSBvbiB0byB0aGUgc2Vjb25kIGxldmVsLCB3aGVyZSBhbm90aGVyIGNoYWxsZW5nZSBhd2FpdHMhCgojIFNlY29uZCBjaXJjdWxhciBjaGFydCBsZXZlbAoKVGhlIHNlY29uZCBjaXJjdWxhciBsZXZlbCBvZiB0aGUgZ3JhcGggcmVwcmVzZW50cyB0aGUgbW9zdCBmcmVxdWVudCBjcmltZXMgb3ZlciBhIHBlcmlvZCBvZiB0aW1lIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGhvdXIgY2xhc3Mgd2UgZGVzY3JpYmVkIGVhcmxpZXIuCgpMZXQncyBzdGFydCBieSBhZGRpbmcgdG8gb3VyIG1haW4gZ3JhcGgsIHRoZSBiYXIgZ3JhcGggb2YgdGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlIG9mIGEgY3JpbWUgb3ZlciBhIGNsYXNzIG9mIHRpbWUuCgpOb3RlIHRoYXQgb3VyIHgtYXhpcyBjYW4gb25seSBjYXJyeSBgMjQqZ2FwPTcyYCBiYXJzLCBzbyB3ZSBjYW4gb25seSByZXByZXNlbnQgYDcyLzQ9MThgIHBlciBob3VyIGNsYXNzLiBCdXQgd2UnbGwgbGltaXQgb3Vyc2VsdmVzIHRvIHRoZSB0b3AgMTYgZnJlcXVlbmNpZXMgb2Ygb2NjdXJyZW5jZSBmb3IgZWFjaCBob3VyIGNsYXNzLgoKYGBge3J9CmNybV9ieV9ob3VyX2NsYXNzIDwtIGRmICU+JQogIGNvdW50KGhvdXJfY2xhc3MsIGNybV9jZCkgJT4lCiAgZ3JvdXBfYnkoaG91cl9jbGFzcykgJT4lCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSBuKjEwMC9zdW0obiksIGxpbT1zdW0obikpICU+JQogIHRvcF9uKHBlcmNlbnRhZ2Usbj0oMjQqZ2FwLzQpLTIpICU+JQogIHVuZ3JvdXAoKSU+JQogIGdyb3VwX3NwbGl0KGhvdXJfY2xhc3MpICU+JQogIG1hcF9kZnIofiAueFsxOihucm93KC54KSsyKSxdKSAlPiUKICBtdXRhdGUoaWQgPSBtaXhlZF9kYXRhJGlkKQpgYGAKCgpgYGB7cn0KcGxvdCsKICBnZW9tX2NvbChkYXRhID0gZmlsdGVyKGNybV9ieV9ob3VyX2NsYXNzLCAhKGlkPT03MSkpLCBhZXMoeD1pZCwgeT1wZXJjZW50YWdlKjI1MDApLCBzaG93LmxlZ2VuZCA9IEZBTFNFLCBpbmhlcml0LmFlcyA9IEZBTFNFLCBmaWxsPXByaW1hcnlfY29sb3IpCmBgYAoKCldoeSBgeT1wZXJjZW50YWdlKjI1MDBgPyAKSW4gZmFjdCwgdG8gcmVwcmVzZW50IHRoZSBmcmVxdWVuY2llcyBvZiB2YWx1ZXMgYmV0d2VlbiAwIGFuZCAxMDAsIHRoZXJlJ3MgYSBwcm9ibGVtIG9mIHNjYWxlOiB0aGUgYmFycyBhcmUgdGhlbiBpbnZpc2libGUgY29tcGFyZWQgdG8gdGhvc2Ugb2YgdGhlIGZpcnN0IGNpcmN1bGFyIGxldmVsLCB3aGljaCByZXByZXNlbnRzIHZhbHVlcyAxMDAwIHRpbWVzIGhpZ2hlci4gQnV0IHdlIGNhbiBtb2RpZnkgdGhlIHNjYWxlIG9mIGFsbCB0aGUgdmFsdWVzIHNvIHRoYXQgdGhlIGJhcnMgYXJlIHZpc2libGUsIHNpbmNlIHdlJ3JlIGp1c3QgdHJ5aW5nIHRvIHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW5jZSBpbiBmcmVxdWVuY3kgYmV0d2VlbiB0aGUgZGlmZmVyZW50IGNyaW1lcy4KCkJ1dCBhcyB5b3UgY2FuIHNlZSwgdGhlIG1haW4gZmxhdyBpbiB0aGlzIGdyYXBoIGlzIGl0cyBsZWdpYmlsaXR5LCBkdWUgdG8gdGhlIGZhY3QgdGhhdCBhbGwgdGhlIGJhcnMgaW4gdGhlIHNlY29uZCBjaXJjdWxhciBsZXZlbCBzdGFydCBhdCBgeD0wYC4gT25jZSBhZ2Fpbiwgd2UncmUgZ29pbmcgdG8gaGF2ZSB0byB3b3JrIG91dCBob3cgdG8gY2hhbmdlIHRoZSBvcmlnaW4gb2YgdGhlIGJhcnMuCgpIb3cgZG8gd2UgZG8gaXQ/CldlJ3JlIGdvaW5nIHRvIHVzZSBhIHN0YWNrZWQgYmFyIGNoYXJ0LCB0aGVuIGFzc2lnbiB6ZXJvIG9wYWNpdHkgdG8gdGhlIGZpcnN0IHN0YWNraW5nIGxldmVsLgoKCmBgYHtyfQpkdXBsaWNhdGVkX2RhdGE8LXJiaW5kKAogIGNybV9ieV9ob3VyX2NsYXNzICU+JSBtdXRhdGUobGFiPTEsIHBlcmNlbnRhZ2U9MjUwMCpwZXJjZW50YWdlKSwKICBjcm1fYnlfaG91cl9jbGFzcyAlPiUgbXV0YXRlKAogICAgbGFiPTAsCiAgICBwZXJjZW50YWdlPWlmX2Vsc2UoaXMubmEocGVyY2VudGFnZSkscGVyY2VudGFnZSwgbGltKjAuMjkpKQogICkKYGBgCgpgYGB7cn0KcGxvdCsKICBnZW9tX2NvbChkYXRhID0gZmlsdGVyKGR1cGxpY2F0ZWRfZGF0YSwgIShpZD09NzEpKSxhZXMoeD1pZCwgeT1wZXJjZW50YWdlLCBmaWxsPWFzLmZhY3RvcihsYWIpKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayhyZXZlcnNlID0gVFJVRSksIHNob3cubGVnZW5kID0gRkFMU0UsIGluaGVyaXQuYWVzID0gRkFMU0UpKwogIHlsaW0oLTYwMDAwLDE1MDAwMCkgCmBgYAoKClRoZSBzZWNvbmQgY2lyY3VsYXIgbGV2ZWwgcmV0YWlucyB0aGUgc2FtZSBmaWxsIHNjYWxlIGRlZmluZWQgZm9yIHRoZSBmaXJzdCBjaXJjdWxhciBsZXZlbC4gRm9ydHVuYXRlbHksIHRoZSBgbmV3X3NjYWxlX2ZpbGxgIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBhZGp1c3QgdGhpcy4KCmBgYHtyfQpwbG90KwogIG5ld19zY2FsZV9maWxsKCkrCiAgZ2VvbV9jb2woZGF0YSA9IGZpbHRlcihkdXBsaWNhdGVkX2RhdGEsICEoaWQ9PTcxKSksYWVzKHg9aWQsIHk9cGVyY2VudGFnZSwgZmlsbD1hcy5mYWN0b3IobGFiKSksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2socmV2ZXJzZSA9IFRSVUUpLCBzaG93LmxlZ2VuZCA9IEZBTFNFLCBpbmhlcml0LmFlcyA9IEZBTFNFKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGFscGhhKCJza3libHVlIiwwKSwgYWxwaGEocHJpbWFyeV9jb2xvciwxKSkpKwogIHlsaW0oLTYwMDAwLDE1MDAwMCkKYGBgCgojIEZpbmFsIHBsb3QKKioqCgpgYGB7cn0KdG9wM19sYWJlbF9jcm1fYnlfY2xhc3NfaG91ciA8LSBkZiAlPiUKICBjb3VudChob3VyX2NsYXNzLCBjcm1fY2QpICU+JQogIGdyb3VwX2J5KGhvdXJfY2xhc3MpICU+JQogIG11dGF0ZShwZXIgPSBuKjEwMC9zdW0obiksIGxhYmVsPXBhc3RlMChyb3VuZChwZXIpLCIlIikpICU+JQogIHRvcF9uKHBlcixuPTMpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBsZWZ0X2pvaW4oLixkaXN0aW5jdChzZWxlY3QoZGYsY3JtX2NkLGNybV9jZF9kZXNjKSksIGJ5PWpvaW5fYnkoY3JtX2NkKSkgJT4lCiAgc2VsZWN0KC1jKG4scGVyKSkgJT4lCiAgbXV0YXRlKAogICAgaGp1c3QgPSBjKHJlcCgibGVmdCIsNikscmVwKCJyaWdodCIsNikpLAogICAgbnVkZ2VfeCA9IGMoLTEsLS41LC0wLjgsMSwtMSwyLHJlcCgtLjIsMyksMC41LC0xLDIpLAogICAgY3JtX2NkX2Rlc2MgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZShjcm1fY2RfZGVzYywgcGF0dGVybiA9ICIgIiwgcmVwbGFjZW1lbnQgPSAiXG4iKSwKICAgIGNybV9jZF9kZXNjID0gc3RyaW5ncjo6c3RyX3JlbW92ZShjcm1fY2RfZGVzYywgcGF0dGVybiA9ICItIikKICApCgpjcm1fYnlfaG91cl9jbGFzcyA8LSBjcm1fYnlfaG91cl9jbGFzcyAlPiUKICBsZWZ0X2pvaW4odG9wM19sYWJlbF9jcm1fYnlfY2xhc3NfaG91ciwgYnk9am9pbl9ieShob3VyX2NsYXNzLCBjcm1fY2QpKQoKZHVwbGljYXRlZF9kYXRhPC1yYmluZCgKICBjcm1fYnlfaG91cl9jbGFzcyAlPiUgbXV0YXRlKGxhYj0xLCBwZXJjZW50YWdlPTI1MDAqcGVyY2VudGFnZSksCiAgY3JtX2J5X2hvdXJfY2xhc3MgJT4lIG11dGF0ZSgKICAgIGxhYj0wLAogICAgcGVyY2VudGFnZT1pZl9lbHNlKGlzLm5hKHBlcmNlbnRhZ2UpLHBlcmNlbnRhZ2UsIGxpbSowLjI5KSkKICApCgoKCnBsb3QrCiAgeWxpbSgtNjAwMDAsMTUwMDAwKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGFscGhhKGJhcl9jb2xvciwwLjYpLCBhbHBoYShiYXJfY29sb3IsMSkpKSsKICBuZXdfc2NhbGVfZmlsbCgpKwogIGdlb21fcG9pbnQoYWVzKHg9aWQsIHk9LTcwMDAsIGNvbG9yPWhvdXJfY2xhc3MpLCBzaXplPW1peGVkX2RhdGEkc2l6ZVsxOjcxXSkrCiAgZ2VvbV9saW5lKGRhdGEgPSBmaWx0ZXIoY3JtX2J5X2hvdXJfY2xhc3MsICEoaWQ9PTcxKSksIGFlcyh4ID0gYXMubnVtZXJpYyhpZCksIHk9bGltKjAuMjcsIGNvbG9yPWhvdXJfY2xhc3MpLCBsaW5ld2lkdGg9MS41KSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gaG91cl9jbGFzc19jb2xvcikrCiAgbmV3X3NjYWxlX2NvbG9yKCkrCiAgI3NjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJCIikrCiAgZ2VvbV9jb2woZGF0YSA9IGZpbHRlcihkdXBsaWNhdGVkX2RhdGEsICEoaWQ9PTcxKSksYWVzKHg9aWQsIHk9cGVyY2VudGFnZSwgZmlsbD1hcy5mYWN0b3IobGFiKSksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2socmV2ZXJzZSA9IFRSVUUpLCBzaG93LmxlZ2VuZCA9IEZBTFNFLCBpbmhlcml0LmFlcyA9IEZBTFNFKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGFscGhhKCJza3libHVlIiwwKSwgYWxwaGEocHJpbWFyeV9jb2xvciwxKSkpKwogIGdlb21fdGV4dF9yZXBlbCgKICAgIGRhdGEgPSBmaWx0ZXIoZHVwbGljYXRlZF9kYXRhLCAhKGlkPT03MSkpLAogICAgYWVzKHk9cGVyY2VudGFnZStsaW0qMC4yOSwgbGFiZWwgPSBjcm1fY2RfZGVzYywgY29sb3I9YXMuZmFjdG9yKGxhYikpLAogICAgI2ZhbWlseSA9ICJQb3BwaW5zIiwKICAgIHNlZWQ9MTIzNCwKICAgIGZvbnRmYWNlPSJib2xkIiwKICAgIHNpemUgPSAxLjc1LAogICAgaGp1c3Q9ZmlsdGVyKGR1cGxpY2F0ZWRfZGF0YSwgIShpZD09NzEpKSRoanVzdCwKICAgICN2anVzdD0xLAogICAgbWluLnNlZ21lbnQubGVuZ3RoID0gMCwgCiAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4wMDIsICJucGMiKSksCiAgICBudWRnZV94ID0gZmlsdGVyKGR1cGxpY2F0ZWRfZGF0YSwgIShpZD09NzEpKSRudWRnZV94LAogICAgI251ZGdlX3kgPSAuNSwKICAgIG5hLnJtID0gVFJVRSwKICAgIGRpcmVjdGlvbiA9ICJ5IiwKICApKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGFscGhhKCJibGFjayIsMCksIGFscGhhKHByaW1hcnlfY29sb3IsMSkpKQpgYGAKCjwhLS0gQ2xvc2UgY29udGFpbmVyIC0tPgo8L2Rpdj4KCg==