Today, we’ll explore gganimate!

It turns out that there’s a number of additional packages that gganimate needs ot run - you should install

  • gganimate
  • png
  • gifski

before starting. I have to admit, the gganimate package is not super easy to use “out of the box” – it took some googling to figure out which additional packages it needed, and it was slow using the default settings to test things out and troubleshoot. I’ve compiled some tips here to hopefully remove some of that friction!

Some tips for working with gganimate:

  • Save animations to an object (eg anim_1 = ggplot(..))
  • When you’re trying things out, “play” the animation using the animate command with a small number of frames (eg animate(anim_1, nframes = 10)). This tells gganimate to only create 10 frames of ggplot2 graphs (the default is 100).
  • If you tell R to run chunk output in console:

you’ll see a progress bar as your animation is rendered (and can cancel running the code if it seems too long)

When you do your final knit to HTML, you’ll want to change the nframes argument to something longer so that your animation looks “smoother”.

Today, I’m going to walk through the steps of building two animations – one traditional time series graph “reveal-style”, and one time-based “bouncing bubbles” style animation. You should turn in an HTML document (via RPubs) or use anim_save() to save your animation and turn in alongside your .rmd file. You do not need to turn in the intermediate steps that I outline here, but you should follow them as you develop your animation.

Example 1: Temperature Anomalies

The first example comes from the dslabs package (which you do not have to install) and contains the nnual mean global temperature anomaly (deviations from 20th century average) on land, sea and combined for 1880-2018. It also contains the annual global carbon emissions from 1751-2014.

Our goal is to create a time series graph of Global, Land, and Ocean temperature anomalies for each year.

Static Graph

Following best practices, let’s first make a static version of the graph.

data(temp_carbon)
temp_anomaly = temp_carbon %>%
    select(Year = year, Global = temp_anomaly, Land = land_anomaly, Ocean = ocean_anomaly) %>%
  pivot_longer(Global:Ocean, names_to = "Region", values_to = "Anomaly") %>%
  filter(!is.na(Anomaly)) 

temp_anomaly %>%
    ggplot(aes(x = Year, y = Anomaly, col = Region)) +
    geom_line(size = 1) +
    geom_hline(aes(yintercept = 0), lty = 2) +
    annotate("text", x = 2005, y = -.08, 
               label = "20th century mean", size = 3) +
    scale_color_viridis_d(option = "C", end = .75) +
  labs(
    title = "Temperature anomaly relative to 20th century mean",
    x = "Year", 
    y = "Temperature anomaly (degrees C)"
  ) + 
  lab11_theme

Adding the base animation

Next, we’ll add the gganimate element. We want to “reveal” the line over time, so we’ll use transition_reveal(). The time periods are “years”, and they’re stored in the variable called Year.

anim_1 = temp_anomaly %>%
    ggplot(aes(x = Year, y = Anomaly, col = Region)) +
    geom_line(size = 1) +
    geom_hline(aes(yintercept = 0), lty = 2) +
    annotate("text", x = 2005, y = -.08, 
               label = "20th century mean", size = 3) +
    scale_color_viridis_d(option = "C", end = .75) +
  labs(
    title = "Temperature anomaly relative to 20th century mean",
    x = "Year", 
    y = "Temperature anomaly (degrees C)"
  ) + 
  lab11_theme + 
  transition_reveal(Year) 

animate(anim_1, nframes = 10)

That was easy! This isn’t super pretty, largely due to the nframes = 10 argument.

Adding some flourish

This is not harnessing the power of an animated graph - let’s add a few more touches. I’m going to get rid of the legend, and add a label to the lines that moves as the line reveals itself. I’ll start from the bottom up of things that I’ve added.

  • Hide legend for color (guides(col = FALSE))
  • Since I’m going to be labeling each line on the right side of the plot, so I’m going to use coord_cartesion(clip = "off") to allow ggplot2 to draw anywhere on the plot (including in the margins)
  • I’ll use geom_text to label each line and keep the labels to the right of the plot area, so I’ll say x = 2030 to make sure the x position doesn’t change. I’ll label the lines based on the color, so label = Region.
  • I’ll also add a dotted line between the time series line and the label using geom_segment. It gets the x and y aesthetic from ggplot2, and I’ll also tell it to use xend = 2030 (where the label is) and yend = Anomaly (same as the y aesthetic, so every line will be horizontal)
anim_2 = temp_anomaly %>%
    ggplot(aes(x = Year, y = Anomaly, col = Region)) +
    geom_line(size = 1) +
    geom_segment(aes(xend = 2030, yend = Anomaly), linetype = 2) + 
    geom_text(aes(x = 2030, label = Region), hjust = 0) +
    geom_hline(aes(yintercept = 0), lty = 2) +
    annotate("text", x = 2005, y = -.08, 
               label = "20th century mean", size = 3) +
    scale_color_viridis_d(option = "C", end = .75) +
  labs(
    title = "Temperature anomaly relative to 20th century mean",
    x = "Year", 
    y = "Temperature anomaly (degrees C)"
  ) + 
  xlim(1880, 2040) + 
  coord_cartesian(clip = 'off') + 
  lab11_theme + 
  guides(col = FALSE) +
  transition_reveal(Year) 

animate(anim_2, nframes = 10)

I’m pretty happy with this! Now let’s see what it looks like with the default number of frames:

animate(anim_2, nframes = 150)

For reference, this rendering took ~40 seconds on my computer, while the nframes=10 rendering took ~6.

Example 2: Covid Data

For the second example, we’ll make the “bouncing-bubbles” style animation using the COVID data. Each point will be a state, and we’ll pick variables for the x, y, and size aesthetics – let’s try smoothed_wearing_mask on the x-axis, smoothed_restuarant_1d on the y-axis, and smoothed_cli for the size.

covid_wide = covid %>%
  pivot_wider(., id_cols = c(geo_value, time_value), names_from = signal, values_from = value)

covid_wide %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_restaurant_1d, size = smoothed_cli)) + 
  geom_point()

This is…. not great. Since each state shows up 90 times in the data, there are 4,500 points here that vary in size. We can get a better sense of what the animation will look like by filtering the data to include a single day. In the next graph, I’ve also mapped smoothed_cli to color to make it pretty.

covid_wide %>%
  filter(time_value == "2020-10-31") %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_restaurant_1d, size = smoothed_cli, col = smoothed_cli)) + 
  geom_point(alpha = .5) + 
  scale_color_viridis_c(option = "C", end = .75) +
  lab11_theme + 
  theme(legend.position = "bottom")

Adding the base animation

Next, we add our gganimate element. (Note that I removed the filter argument) Here, we want the points to vary across time, so we’ll use transition_time(time_value), since time_value is where our time variable is stored.

anim_3 = covid_wide %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_restaurant_1d, size = smoothed_cli, col = smoothed_cli)) + 
  geom_point(alpha = .5) + 
  scale_color_viridis_c(option = "C", end = .75) +
  lab11_theme + 
  theme(legend.position = "bottom") + 
  transition_time(time_value)

animate(anim_3, nframes = 10)

Not the most informative and/or beautiful animation, but we’ll make it (marginally) better!

Adding some flourish

Next, we’ll add a few minor change to make the graph more readable and informative. I’m not adding as extensive changes as in Example 1. Changes I’ve made:

  • Titles, legends, etc.
  • Add the date of each animation in the subtitle
  • Make col and size have the same label, and add guide = "legend" to scale_color_viridis_c to combine both legends into one
  • Add a shadow_mark so we can see where the points were even after they disappear
anim_4 = covid_wide %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_restaurant_1d, size = smoothed_cli, col = smoothed_cli)) + 
  geom_point(alpha = .5) + 
  scale_color_viridis_c(option = "C", end = .75, guide = "legend") +
  lab11_theme + 
  theme(legend.position = "bottom") + 
  transition_time(time_value) +
  shadow_mark(size = .1, alpha = .1) +
  labs(
    title = "Restaurant visits, mask-wearing, and COVID-like illness over time",
    subtitle = 'Date: {frame_time}',
    x = "% Wearing Mask in Public",
    y = "% Visited Restaurant",
    col = "% with COVID-like Illness",
    size = "% with COVID-like Illness",
    caption = "Source: CMU Delphi Symptom Survey"
  )
  

animate(anim_4, nframes = 10)

Again, this isn’t a perfect graph, but I’m pretty happy with it! Let’s see what it looks like with 100 frames:

animate(anim_4, nframes = 100)

Part 3: Your turn!

Using any data besides gapminder, create any animation you want (besides the ones I’ve included here). Your animations can be similar to Examples 1 and 2, but should include changes beyond plugging in different variables to the aes calls.

Everybody should submit an .rmd file with your animation. You should also turn in an HTML document (via RPubs) or use anim_save() to save your animation and turn it in alongside your .rmd file:

anim_save(FILENAME, animation = last_animation())

(for more info, use ?anim_save)

LS0tCnRpdGxlOiAiTGFiMTE6IEFuaW1hdGlvbiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjc3M6IHN0YXQ0MS1sYWItdGhlbWUuY3NzCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdnYW5pbWF0ZSkKbGlicmFyeShkc2xhYnMpCgpsYWIxMV90aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIsIGJhc2VfZmFtaWx5ID0gInNlcmlmIikKY292aWQgPSByZWFkX2NzdigiZGF0YS9jb3ZpZF9kYXRhLmNzdiIpCmBgYAoKVG9kYXksIHdlJ2xsIGV4cGxvcmUgYGdnYW5pbWF0ZWAhIAoKSXQgdHVybnMgb3V0IHRoYXQgdGhlcmUncyBhIG51bWJlciBvZiBhZGRpdGlvbmFsIHBhY2thZ2VzIHRoYXQgYGdnYW5pbWF0ZWAgbmVlZHMgb3QgcnVuIC0geW91IHNob3VsZCBpbnN0YWxsCgogIC0gYGdnYW5pbWF0ZWAKICAtIGBwbmdgCiAgLSBgZ2lmc2tpYAoKYmVmb3JlIHN0YXJ0aW5nLiBJIGhhdmUgdG8gYWRtaXQsIHRoZSBgZ2dhbmltYXRlYCBwYWNrYWdlIGlzIG5vdCBzdXBlciBlYXN5IHRvIHVzZSAib3V0IG9mIHRoZSBib3giIC0tIGl0IHRvb2sgc29tZSBnb29nbGluZyB0byBmaWd1cmUgb3V0IHdoaWNoIGFkZGl0aW9uYWwgcGFja2FnZXMgaXQgbmVlZGVkLCBhbmQgaXQgd2FzICoqc2xvdyoqIHVzaW5nIHRoZSBkZWZhdWx0IHNldHRpbmdzIHRvIHRlc3QgdGhpbmdzIG91dCBhbmQgdHJvdWJsZXNob290LiBJJ3ZlIGNvbXBpbGVkIHNvbWUgdGlwcyBoZXJlIHRvIGhvcGVmdWxseSByZW1vdmUgc29tZSBvZiB0aGF0IGZyaWN0aW9uIQoKU29tZSB0aXBzIGZvciB3b3JraW5nIHdpdGggYGdnYW5pbWF0ZWA6IAoKKyBTYXZlIGFuaW1hdGlvbnMgdG8gYW4gb2JqZWN0IChlZyBgYW5pbV8xID0gZ2dwbG90KC4uKWApCisgV2hlbiB5b3UncmUgdHJ5aW5nIHRoaW5ncyBvdXQsICJwbGF5IiB0aGUgYW5pbWF0aW9uIHVzaW5nIHRoZSBgYW5pbWF0ZWAgY29tbWFuZCB3aXRoIGEgKnNtYWxsKiBudW1iZXIgb2YgZnJhbWVzIChlZyBgYW5pbWF0ZShhbmltXzEsIG5mcmFtZXMgPSAxMClgKS4gVGhpcyB0ZWxscyBgZ2dhbmltYXRlYCB0byBvbmx5IGNyZWF0ZSAxMCBmcmFtZXMgb2YgYGdncGxvdDJgIGdyYXBocyAodGhlIGRlZmF1bHQgaXMgMTAwKS4gCisgSWYgeW91IHRlbGwgUiB0byBydW4gY2h1bmsgb3V0cHV0IGluIGNvbnNvbGU6IAoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9jaHVuay1vdXRwdXQtY29uc29sZS5wbmciKQpgYGAKCnlvdSdsbCBzZWUgYSBwcm9ncmVzcyBiYXIgYXMgeW91ciBhbmltYXRpb24gaXMgcmVuZGVyZWQgKGFuZCBjYW4gY2FuY2VsIHJ1bm5pbmcgdGhlIGNvZGUgaWYgaXQgc2VlbXMgdG9vIGxvbmcpCgpgYGB7ciwgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3JlbmRlcmluZy1wcm9ncmVzcy5wbmciKQpgYGAKCldoZW4geW91IGRvIHlvdXIgZmluYWwga25pdCB0byBIVE1MLCB5b3UnbGwgd2FudCB0byBjaGFuZ2UgdGhlIGBuZnJhbWVzYCBhcmd1bWVudCB0byBzb21ldGhpbmcgbG9uZ2VyIHNvIHRoYXQgeW91ciBhbmltYXRpb24gbG9va3MgInNtb290aGVyIi4gCgpUb2RheSwgSSdtIGdvaW5nIHRvIHdhbGsgdGhyb3VnaCB0aGUgc3RlcHMgb2YgYnVpbGRpbmcgdHdvIGFuaW1hdGlvbnMgLS0gb25lIHRyYWRpdGlvbmFsIHRpbWUgc2VyaWVzIGdyYXBoICJyZXZlYWwtc3R5bGUiLCBhbmQgb25lIHRpbWUtYmFzZWQgImJvdW5jaW5nIGJ1YmJsZXMiIHN0eWxlIGFuaW1hdGlvbi4gWW91IHNob3VsZCB0dXJuIGluIGFuIEhUTUwgZG9jdW1lbnQgKHZpYSBSUHVicykgb3IgdXNlIGBhbmltX3NhdmUoKWAgdG8gc2F2ZSB5b3VyIGFuaW1hdGlvbiBhbmQgdHVybiBpbiBhbG9uZ3NpZGUgeW91ciAucm1kIGZpbGUuIFlvdSBkbyAqbm90KiBuZWVkIHRvIHR1cm4gaW4gdGhlIGludGVybWVkaWF0ZSBzdGVwcyB0aGF0IEkgb3V0bGluZSBoZXJlLCBidXQgeW91IHNob3VsZCBmb2xsb3cgdGhlbSBhcyB5b3UgZGV2ZWxvcCB5b3VyIGFuaW1hdGlvbi4gCgojIEV4YW1wbGUgMTogVGVtcGVyYXR1cmUgQW5vbWFsaWVzCgpUaGUgZmlyc3QgZXhhbXBsZSBjb21lcyBmcm9tIHRoZSBgZHNsYWJzYCBwYWNrYWdlICh3aGljaCB5b3UgZG8gKm5vdCogaGF2ZSB0byBpbnN0YWxsKSBhbmQgY29udGFpbnMgdGhlIG5udWFsIG1lYW4gZ2xvYmFsIHRlbXBlcmF0dXJlIGFub21hbHkgKGRldmlhdGlvbnMgZnJvbSAyMHRoIGNlbnR1cnkgYXZlcmFnZSkgb24gbGFuZCwgc2VhIGFuZCBjb21iaW5lZCBmb3IgMTg4MC0yMDE4LiBJdCBhbHNvIGNvbnRhaW5zIHRoZSBhbm51YWwgZ2xvYmFsIGNhcmJvbiBlbWlzc2lvbnMgZnJvbSAxNzUxLTIwMTQuCgpPdXIgZ29hbCBpcyB0byBjcmVhdGUgYSB0aW1lIHNlcmllcyBncmFwaCBvZiBHbG9iYWwsIExhbmQsIGFuZCBPY2VhbiB0ZW1wZXJhdHVyZSBhbm9tYWxpZXMgZm9yIGVhY2ggeWVhci4gCgojIyBTdGF0aWMgR3JhcGgKCkZvbGxvd2luZyBiZXN0IHByYWN0aWNlcywgbGV0J3MgZmlyc3QgbWFrZSBhIHN0YXRpYyB2ZXJzaW9uIG9mIHRoZSBncmFwaC4KCmBgYHtyfQpkYXRhKHRlbXBfY2FyYm9uKQp0ZW1wX2Fub21hbHkgPSB0ZW1wX2NhcmJvbiAlPiUKICAgIHNlbGVjdChZZWFyID0geWVhciwgR2xvYmFsID0gdGVtcF9hbm9tYWx5LCBMYW5kID0gbGFuZF9hbm9tYWx5LCBPY2VhbiA9IG9jZWFuX2Fub21hbHkpICU+JQogIHBpdm90X2xvbmdlcihHbG9iYWw6T2NlYW4sIG5hbWVzX3RvID0gIlJlZ2lvbiIsIHZhbHVlc190byA9ICJBbm9tYWx5IikgJT4lCiAgZmlsdGVyKCFpcy5uYShBbm9tYWx5KSkgCgp0ZW1wX2Fub21hbHkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBZZWFyLCB5ID0gQW5vbWFseSwgY29sID0gUmVnaW9uKSkgKwogICAgZ2VvbV9saW5lKHNpemUgPSAxKSArCiAgICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gMCksIGx0eSA9IDIpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDIwMDUsIHkgPSAtLjA4LCAKICAgICAgICAgICAgICAgbGFiZWwgPSAiMjB0aCBjZW50dXJ5IG1lYW4iLCBzaXplID0gMykgKwogICAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1KSArCiAgbGFicygKICAgIHRpdGxlID0gIlRlbXBlcmF0dXJlIGFub21hbHkgcmVsYXRpdmUgdG8gMjB0aCBjZW50dXJ5IG1lYW4iLAogICAgeCA9ICJZZWFyIiwgCiAgICB5ID0gIlRlbXBlcmF0dXJlIGFub21hbHkgKGRlZ3JlZXMgQykiCiAgKSArIAogIGxhYjExX3RoZW1lCmBgYAoKCiMjIEFkZGluZyB0aGUgYmFzZSBhbmltYXRpb24KCk5leHQsIHdlJ2xsIGFkZCB0aGUgYGdnYW5pbWF0ZWAgZWxlbWVudC4gV2Ugd2FudCB0byAicmV2ZWFsIiB0aGUgbGluZSBvdmVyIHRpbWUsIHNvIHdlJ2xsIHVzZSBgdHJhbnNpdGlvbl9yZXZlYWwoKWAuIFRoZSB0aW1lIHBlcmlvZHMgYXJlICJ5ZWFycyIsIGFuZCB0aGV5J3JlIHN0b3JlZCBpbiB0aGUgdmFyaWFibGUgY2FsbGVkIGBZZWFyYC4gCgpgYGB7cn0KYW5pbV8xID0gdGVtcF9hbm9tYWx5ICU+JQogICAgZ2dwbG90KGFlcyh4ID0gWWVhciwgeSA9IEFub21hbHksIGNvbCA9IFJlZ2lvbikpICsKICAgIGdlb21fbGluZShzaXplID0gMSkgKwogICAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IDApLCBsdHkgPSAyKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSAyMDA1LCB5ID0gLS4wOCwgCiAgICAgICAgICAgICAgIGxhYmVsID0gIjIwdGggY2VudHVyeSBtZWFuIiwgc2l6ZSA9IDMpICsKICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJUZW1wZXJhdHVyZSBhbm9tYWx5IHJlbGF0aXZlIHRvIDIwdGggY2VudHVyeSBtZWFuIiwKICAgIHggPSAiWWVhciIsIAogICAgeSA9ICJUZW1wZXJhdHVyZSBhbm9tYWx5IChkZWdyZWVzIEMpIgogICkgKyAKICBsYWIxMV90aGVtZSArIAogIHRyYW5zaXRpb25fcmV2ZWFsKFllYXIpIAoKYW5pbWF0ZShhbmltXzEsIG5mcmFtZXMgPSAxMCkKCmBgYAoKVGhhdCB3YXMgZWFzeSEgVGhpcyBpc24ndCBzdXBlciBwcmV0dHksIGxhcmdlbHkgZHVlIHRvIHRoZSBgbmZyYW1lcyA9IDEwYCBhcmd1bWVudC4gCgojIyBBZGRpbmcgc29tZSAqZmxvdXJpc2gqCgpUaGlzIGlzIG5vdCBoYXJuZXNzaW5nIHRoZSBwb3dlciBvZiBhbiBhbmltYXRlZCBncmFwaCAtIGxldCdzIGFkZCBhIGZldyBtb3JlIHRvdWNoZXMuIEknbSBnb2luZyB0byBnZXQgcmlkIG9mIHRoZSBsZWdlbmQsIGFuZCBhZGQgYSBsYWJlbCB0byB0aGUgbGluZXMgdGhhdCBtb3ZlcyBhcyB0aGUgbGluZSByZXZlYWxzIGl0c2VsZi4gSSdsbCBzdGFydCBmcm9tIHRoZSBib3R0b20gdXAgb2YgdGhpbmdzIHRoYXQgSSd2ZSBhZGRlZC4KCiAgKyBIaWRlIGxlZ2VuZCBmb3IgY29sb3IgKGBndWlkZXMoY29sID0gRkFMU0UpYCkKICArIFNpbmNlIEknbSBnb2luZyB0byBiZSBsYWJlbGluZyBlYWNoIGxpbmUgb24gdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIHBsb3QsIHNvIEknbSBnb2luZyB0byB1c2UgYGNvb3JkX2NhcnRlc2lvbihjbGlwID0gIm9mZiIpYCB0byBhbGxvdyBnZ3Bsb3QyIHRvIGRyYXcgYW55d2hlcmUgb24gdGhlIHBsb3QgKGluY2x1ZGluZyBpbiB0aGUgbWFyZ2lucykKICArIEknbGwgdXNlIGBnZW9tX3RleHRgIHRvIGxhYmVsIGVhY2ggbGluZSBhbmQga2VlcCB0aGUgbGFiZWxzIHRvIHRoZSByaWdodCBvZiB0aGUgcGxvdCBhcmVhLCBzbyBJJ2xsIHNheSBgeCA9IDIwMzBgIHRvIG1ha2Ugc3VyZSB0aGUgeCBwb3NpdGlvbiBkb2Vzbid0IGNoYW5nZS4gSSdsbCBsYWJlbCB0aGUgbGluZXMgYmFzZWQgb24gdGhlIGNvbG9yLCBzbyBgbGFiZWwgPSBSZWdpb25gLiAKICArIEknbGwgYWxzbyBhZGQgYSBkb3R0ZWQgbGluZSBiZXR3ZWVuIHRoZSB0aW1lIHNlcmllcyBsaW5lIGFuZCB0aGUgbGFiZWwgdXNpbmcgYGdlb21fc2VnbWVudGAuIEl0IGdldHMgdGhlIGB4YCBhbmQgYHlgIGFlc3RoZXRpYyBmcm9tIGBnZ3Bsb3QyYCwgYW5kIEknbGwgYWxzbyB0ZWxsIGl0IHRvIHVzZSBgeGVuZCA9IDIwMzBgICh3aGVyZSB0aGUgbGFiZWwgaXMpIGFuZCBgeWVuZCA9IEFub21hbHlgIChzYW1lIGFzIHRoZSBgeWAgYWVzdGhldGljLCBzbyBldmVyeSBsaW5lIHdpbGwgYmUgaG9yaXpvbnRhbCkKCmBgYHtyfQphbmltXzIgPSB0ZW1wX2Fub21hbHkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBZZWFyLCB5ID0gQW5vbWFseSwgY29sID0gUmVnaW9uKSkgKwogICAgZ2VvbV9saW5lKHNpemUgPSAxKSArCiAgICBnZW9tX3NlZ21lbnQoYWVzKHhlbmQgPSAyMDMwLCB5ZW5kID0gQW5vbWFseSksIGxpbmV0eXBlID0gMikgKyAKICAgIGdlb21fdGV4dChhZXMoeCA9IDIwMzAsIGxhYmVsID0gUmVnaW9uKSwgaGp1c3QgPSAwKSArCiAgICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gMCksIGx0eSA9IDIpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDIwMDUsIHkgPSAtLjA4LCAKICAgICAgICAgICAgICAgbGFiZWwgPSAiMjB0aCBjZW50dXJ5IG1lYW4iLCBzaXplID0gMykgKwogICAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1KSArCiAgbGFicygKICAgIHRpdGxlID0gIlRlbXBlcmF0dXJlIGFub21hbHkgcmVsYXRpdmUgdG8gMjB0aCBjZW50dXJ5IG1lYW4iLAogICAgeCA9ICJZZWFyIiwgCiAgICB5ID0gIlRlbXBlcmF0dXJlIGFub21hbHkgKGRlZ3JlZXMgQykiCiAgKSArIAogIHhsaW0oMTg4MCwgMjA0MCkgKyAKICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICdvZmYnKSArIAogIGxhYjExX3RoZW1lICsgCiAgZ3VpZGVzKGNvbCA9IEZBTFNFKSArCiAgdHJhbnNpdGlvbl9yZXZlYWwoWWVhcikgCgphbmltYXRlKGFuaW1fMiwgbmZyYW1lcyA9IDEwKQpgYGAKCkknbSBwcmV0dHkgaGFwcHkgd2l0aCB0aGlzISBOb3cgbGV0J3Mgc2VlIHdoYXQgaXQgbG9va3MgbGlrZSB3aXRoIHRoZSBkZWZhdWx0IG51bWJlciBvZiBmcmFtZXM6IAoKYGBge3IsIGNhY2hlID0gVFJVRX0KYW5pbWF0ZShhbmltXzIsIG5mcmFtZXMgPSAxNTApCmBgYAoKRm9yIHJlZmVyZW5jZSwgdGhpcyByZW5kZXJpbmcgdG9vayB+NDAgc2Vjb25kcyBvbiBteSBjb21wdXRlciwgd2hpbGUgdGhlIGBuZnJhbWVzPTEwYCByZW5kZXJpbmcgdG9vayB+Ni4gCgojIEV4YW1wbGUgMjogQ292aWQgRGF0YSAKCkZvciB0aGUgc2Vjb25kIGV4YW1wbGUsIHdlJ2xsIG1ha2UgdGhlICJib3VuY2luZy1idWJibGVzIiBzdHlsZSBhbmltYXRpb24gdXNpbmcgdGhlIENPVklEIGRhdGEuIEVhY2ggcG9pbnQgd2lsbCBiZSBhIHN0YXRlLCBhbmQgd2UnbGwgcGljayB2YXJpYWJsZXMgZm9yIHRoZSBgeGAsIGB5YCwgYW5kIGBzaXplYCBhZXN0aGV0aWNzIC0tIGxldCdzIHRyeSBgc21vb3RoZWRfd2VhcmluZ19tYXNrYCBvbiB0aGUgeC1heGlzLCBgc21vb3RoZWRfcmVzdHVhcmFudF8xZGAgb24gdGhlIHktYXhpcywgYW5kIGBzbW9vdGhlZF9jbGlgIGZvciB0aGUgc2l6ZS4gCgpgYGB7cn0KY292aWRfd2lkZSA9IGNvdmlkICU+JQogIHBpdm90X3dpZGVyKC4sIGlkX2NvbHMgPSBjKGdlb192YWx1ZSwgdGltZV92YWx1ZSksIG5hbWVzX2Zyb20gPSBzaWduYWwsIHZhbHVlc19mcm9tID0gdmFsdWUpCgpjb3ZpZF93aWRlICU+JQogIGdncGxvdChhZXMoeCA9IHNtb290aGVkX3dlYXJpbmdfbWFzaywgeSA9IHNtb290aGVkX3Jlc3RhdXJhbnRfMWQsIHNpemUgPSBzbW9vdGhlZF9jbGkpKSArIAogIGdlb21fcG9pbnQoKQpgYGAKClRoaXMgaXMuLi4uIG5vdCBncmVhdC4gU2luY2UgZWFjaCBzdGF0ZSBzaG93cyB1cCA5MCB0aW1lcyBpbiB0aGUgZGF0YSwgdGhlcmUgYXJlIDQsNTAwIHBvaW50cyBoZXJlIHRoYXQgdmFyeSBpbiBzaXplLiBXZSBjYW4gZ2V0IGEgYmV0dGVyIHNlbnNlIG9mIHdoYXQgdGhlIGFuaW1hdGlvbiB3aWxsIGxvb2sgbGlrZSBieSBmaWx0ZXJpbmcgdGhlIGRhdGEgdG8gaW5jbHVkZSBhIHNpbmdsZSBkYXkuIEluIHRoZSBuZXh0IGdyYXBoLCBJJ3ZlIGFsc28gbWFwcGVkIGBzbW9vdGhlZF9jbGlgIHRvIGNvbG9yIHRvIG1ha2UgaXQgcHJldHR5LiAKCmBgYHtyfQpjb3ZpZF93aWRlICU+JQogIGZpbHRlcih0aW1lX3ZhbHVlID09ICIyMDIwLTEwLTMxIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc21vb3RoZWRfd2VhcmluZ19tYXNrLCB5ID0gc21vb3RoZWRfcmVzdGF1cmFudF8xZCwgc2l6ZSA9IHNtb290aGVkX2NsaSwgY29sID0gc21vb3RoZWRfY2xpKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gLjUpICsgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1KSArCiAgbGFiMTFfdGhlbWUgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgojIyBBZGRpbmcgdGhlIGJhc2UgYW5pbWF0aW9uCgpOZXh0LCB3ZSBhZGQgb3VyIGBnZ2FuaW1hdGVgIGVsZW1lbnQuIChOb3RlIHRoYXQgSSByZW1vdmVkIHRoZSBgZmlsdGVyYCBhcmd1bWVudCkgSGVyZSwgd2Ugd2FudCB0aGUgcG9pbnRzIHRvIHZhcnkgYWNyb3NzIHRpbWUsIHNvIHdlJ2xsIHVzZSBgdHJhbnNpdGlvbl90aW1lKHRpbWVfdmFsdWUpYCwgc2luY2UgYHRpbWVfdmFsdWVgIGlzIHdoZXJlIG91ciB0aW1lIHZhcmlhYmxlIGlzIHN0b3JlZC4KCmBgYHtyfQphbmltXzMgPSBjb3ZpZF93aWRlICU+JQogIGdncGxvdChhZXMoeCA9IHNtb290aGVkX3dlYXJpbmdfbWFzaywgeSA9IHNtb290aGVkX3Jlc3RhdXJhbnRfMWQsIHNpemUgPSBzbW9vdGhlZF9jbGksIGNvbCA9IHNtb290aGVkX2NsaSkpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IC41KSArIAogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkgKwogIGxhYjExX3RoZW1lICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsgCiAgdHJhbnNpdGlvbl90aW1lKHRpbWVfdmFsdWUpCgphbmltYXRlKGFuaW1fMywgbmZyYW1lcyA9IDEwKQpgYGAKCk5vdCB0aGUgbW9zdCBpbmZvcm1hdGl2ZSBhbmQvb3IgYmVhdXRpZnVsIGFuaW1hdGlvbiwgYnV0IHdlJ2xsIG1ha2UgaXQgKG1hcmdpbmFsbHkpIGJldHRlciEgCgojIyBBZGRpbmcgc29tZSAqZmxvdXJpc2gqIAoKTmV4dCwgd2UnbGwgYWRkIGEgZmV3IG1pbm9yIGNoYW5nZSB0byBtYWtlIHRoZSBncmFwaCBtb3JlIHJlYWRhYmxlIGFuZCBpbmZvcm1hdGl2ZS4gSSdtIG5vdCBhZGRpbmcgYXMgZXh0ZW5zaXZlIGNoYW5nZXMgYXMgaW4gRXhhbXBsZSAxLiBDaGFuZ2VzIEkndmUgbWFkZTogCgorIFRpdGxlcywgbGVnZW5kcywgZXRjLgorIEFkZCB0aGUgZGF0ZSBvZiBlYWNoIGFuaW1hdGlvbiBpbiB0aGUgYHN1YnRpdGxlYAorIE1ha2UgYGNvbGAgYW5kIGBzaXplYCBoYXZlIHRoZSBzYW1lIGxhYmVsLCBhbmQgYWRkIGBndWlkZSA9ICJsZWdlbmQiYCB0byBgc2NhbGVfY29sb3JfdmlyaWRpc19jYCB0byBjb21iaW5lIGJvdGggbGVnZW5kcyBpbnRvIG9uZQorIEFkZCBhIGBzaGFkb3dfbWFya2Agc28gd2UgY2FuIHNlZSB3aGVyZSB0aGUgcG9pbnRzIHdlcmUgZXZlbiBhZnRlciB0aGV5IGRpc2FwcGVhcgoKYGBge3J9CmFuaW1fNCA9IGNvdmlkX3dpZGUgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc21vb3RoZWRfd2VhcmluZ19tYXNrLCB5ID0gc21vb3RoZWRfcmVzdGF1cmFudF8xZCwgc2l6ZSA9IHNtb290aGVkX2NsaSwgY29sID0gc21vb3RoZWRfY2xpKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gLjUpICsgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1LCBndWlkZSA9ICJsZWdlbmQiKSArCiAgbGFiMTFfdGhlbWUgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKyAKICB0cmFuc2l0aW9uX3RpbWUodGltZV92YWx1ZSkgKwogIHNoYWRvd19tYXJrKHNpemUgPSAuMSwgYWxwaGEgPSAuMSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJSZXN0YXVyYW50IHZpc2l0cywgbWFzay13ZWFyaW5nLCBhbmQgQ09WSUQtbGlrZSBpbGxuZXNzIG92ZXIgdGltZSIsCiAgICBzdWJ0aXRsZSA9ICdEYXRlOiB7ZnJhbWVfdGltZX0nLAogICAgeCA9ICIlIFdlYXJpbmcgTWFzayBpbiBQdWJsaWMiLAogICAgeSA9ICIlIFZpc2l0ZWQgUmVzdGF1cmFudCIsCiAgICBjb2wgPSAiJSB3aXRoIENPVklELWxpa2UgSWxsbmVzcyIsCiAgICBzaXplID0gIiUgd2l0aCBDT1ZJRC1saWtlIElsbG5lc3MiLAogICAgY2FwdGlvbiA9ICJTb3VyY2U6IENNVSBEZWxwaGkgU3ltcHRvbSBTdXJ2ZXkiCiAgKQogIAoKYW5pbWF0ZShhbmltXzQsIG5mcmFtZXMgPSAxMCkKCmBgYAoKQWdhaW4sIHRoaXMgaXNuJ3QgYSBwZXJmZWN0IGdyYXBoLCBidXQgSSdtIHByZXR0eSBoYXBweSB3aXRoIGl0ISBMZXQncyBzZWUgd2hhdCBpdCBsb29rcyBsaWtlIHdpdGggMTAwIGZyYW1lczoKCmBgYHtyfQphbmltYXRlKGFuaW1fNCwgbmZyYW1lcyA9IDEwMCkKYGBgCgojIFBhcnQgMzogWW91ciB0dXJuISAKClVzaW5nIGFueSBkYXRhIGJlc2lkZXMgYGdhcG1pbmRlcmAsIGNyZWF0ZSBhbnkgYW5pbWF0aW9uIHlvdSB3YW50IChiZXNpZGVzIHRoZSBvbmVzIEkndmUgaW5jbHVkZWQgaGVyZSkuIFlvdXIgYW5pbWF0aW9ucyBjYW4gYmUgc2ltaWxhciB0byBFeGFtcGxlcyAxIGFuZCAyLCBidXQgc2hvdWxkIGluY2x1ZGUgY2hhbmdlcyBiZXlvbmQgcGx1Z2dpbmcgaW4gZGlmZmVyZW50IHZhcmlhYmxlcyB0byB0aGUgYGFlc2AgY2FsbHMuCgpFdmVyeWJvZHkgc2hvdWxkIHN1Ym1pdCBhbiAucm1kIGZpbGUgd2l0aCB5b3VyIGFuaW1hdGlvbi4gWW91IHNob3VsZCBhbHNvIHR1cm4gaW4gYW4gSFRNTCBkb2N1bWVudCAodmlhIFJQdWJzKSBvciB1c2UgYGFuaW1fc2F2ZSgpYCB0byBzYXZlIHlvdXIgYW5pbWF0aW9uIGFuZCB0dXJuIGl0IGluIGFsb25nc2lkZSB5b3VyIC5ybWQgZmlsZTogCgpgYW5pbV9zYXZlKEZJTEVOQU1FLCBhbmltYXRpb24gPSBsYXN0X2FuaW1hdGlvbigpKWAKCihmb3IgbW9yZSBpbmZvLCB1c2UgYD9hbmltX3NhdmVgKQo=