Let’s get to coding!

We will start with an example where we take mpg data and make a scatterplot.

How do we get from this:

The data

head(mpg)

to this:

The plot

Let’s plot!

Load the ggplot2 library:

library(ggplot2)

ggplot is the base layer.

ggplot()

This is a good place to for us to define our plot’s default data.

ggplot(data = mpg)

There isn’t much being plotted yet… Let’s tell our plot about our mapping, where our x will be our cty and y will be our hwy.

ggplot(
    data = mpg,
    mapping = aes(x = cty, y = hwy)
  )

Our plot now has enough information to set some automatic axis limits based on the data.

ggplot(
    data = mpg,
    mapping = aes(x = cty, y = hwy)
  ) +
  geom_point()

We can map the number of cylinders as a set of discrete values to color.

ggplot(
    data = mpg,
    mapping = aes(x = cty, y = hwy)
  ) +
  geom_point(
    mapping = aes(color = factor(cyl))
  )

At this point, we have the base of the plot. We can save this to a variable, mpg_base_plot, and make additional adjustments.

mpg_base_plot <- ggplot(
    data = mpg,
    mapping = aes(x = cty, y = hwy)
  ) +
  geom_point(
    mapping = aes(color = factor(cyl))
  )
mpg_base_plot

To add the reference line, we add a layer like so:

mpg_base_plot +
  geom_abline(
    slope = 1,
    intercept = 0,
    color = 'black',
    linetype = 'dotted'
  )

Note that this geom layer can be added on top of a blank plot. All geom_ prefixed functions return an environment object that holds onto the information we call it with.

ggplot() +
  geom_abline(
    slope = 1,
    intercept = 0,
    color = 'black',
    linetype = 'dotted'
  )

Let’s continue. Our plot looks a bit funny – the grid for the x and y are not 1 to 1. Let’s use a coord_ function to adjust this:

mpg_base_plot +
  geom_abline(
    slope = 1,
    intercept = 0,
    color = 'black',
    linetype = 'dotted'
  ) +
  coord_fixed(ratio = 1)

We can clean up the plot like so:

mpg_plot <- mpg_base_plot +
  geom_abline(
    slope = 1,
    intercept = 0,
    color = 'black',
    linetype = 'dotted'
  ) +
  coord_fixed(ratio = 1) +
  scale_color_manual(
    name = '# of Cyl',
    values = c('skyblue','royalblue', 'blue', 'navy')
  ) +
  ggtitle('Miles per gallon') +
  theme_light()
mpg_plot

We can further manipulate the plot using things like facet_.

mpg_plot +
  facet_grid(.~class)

Let’s experiment

We can get the base plots from the ggplot cheatsheets on our computer by sourcing this R script.

source('https://goo.gl/3EQSXt')

The magic behind the +

We can discover how the + is helping us construct these plots by running:

methods('+')
[1] +.Date   +.gg*    +.POSIXt
see '?methods' for accessing help and source code

From the result, we can see that the + function is overloaded for objects that match the gg namespace. If we look at the code for this function, we learn more about how it works.

Object-oriented

We can explore more of how ggplot works underneath by looking at:

base_plot <- ggplot()
names(base_plot)
[1] "data"        "layers"      "scales"      "mapping"     "theme"       "coordinates" "facet"       "plot_env"   
[9] "labels"     

It’s an object that can be mutated. Other functions output objects as well.

mpg_mapping <- aes(x = cty, y = hwy)
mpg_mapping
* x -> cty
* y -> hwy

Let’s mutate the base_plot:

base_plot$data <- mpg
base_plot$mapping <- mpg_mapping
base_plot$layers <- c(base_plot$layers, geom_point())
base_plot

Extensibility

You can also create custom stat_s, geom_s, and theme_s as needed. This vignette explains the object-oriented patterns for ggplot2, and how to extend the library for your needs.

There’s actually a lot we can learn from the source code, such as all possible aes parameters.

The makings of a pie

Grammar-based graphics lends flexibility

Back to Leland Wilkinson’s point about making a pie – that a pie chart is really a stacked bar chart that has been transformed with polar coordinates where the y is mapped to the angle, a.k.a. theta.

We start with a bar chart:

diamonds_by_cut_base <- ggplot(data = diamonds, mapping = aes(x = cut, fill = cut))
diamonds_by_cut_base +
  geom_bar()

Here, we have a stacked bar chart of diamonds grouped by cut.

diamonds_by_cut_stacked <- diamonds_by_cut_base +
  geom_bar(width = 1, aes(x=factor("")))
diamonds_by_cut_stacked

Taking the bar chart, we simply “add” a coord_polar to it.

diamonds_by_cut_stacked +
  coord_polar()  +
  scale_x_discrete("")

By default, coord_polar maps the theta from x and the radius from y.

bullseye_coords <- coord_polar()
bullseye_coords$theta
[1] "x"
bullseye_coords$r
[1] "y"

We need to a coord_polar where the theta maps from y:

pie_coords <- coord_polar(theta = "y")
pie_coords$theta
[1] "y"

Now, we can transform the stacked bar chart to a pie chart:

diamonds_by_cut_stacked +
  pie_coords

?coord_polar

A layered grammar is powerful

The full grammar consists of:

Additionally, the grammar includes control over the whole plot’s

Reading and Sources

A Layered Grammar of Graphics

Mastering the Grammar

R for Data Science: Visualizing Data

Extending ggplot2

The R Graph Gallery

Data Camp: Data Visualization with ggplot2

Swirl: Exploratory Data Analysis

ggplot2 Cheatsheet

Telling stories with data using the grammar of graphics

Introduction to R Graphics with ggplot2

ggplot2 source code

ggplot2: Elegant Graphics for Data Analysis

The Grammar of Graphics

Semiology of Graphics

LS0tCnRpdGxlOiAiR3JhbW1hciBvZiBHcmFwaGljcyBpbiBSIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBsb2FkX2dncGxvdCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMgTGV0J3MgZ2V0IHRvIGNvZGluZyEKCldlIHdpbGwgc3RhcnQgd2l0aCBhbiBleGFtcGxlIHdoZXJlIHdlIHRha2UgYG1wZ2AgZGF0YSBhbmQgbWFrZSBhIHNjYXR0ZXJwbG90LgoKSG93IGRvIHdlIGdldCBmcm9tIHRoaXM6CgojIyBUaGUgZGF0YQoKYGBge3Igdmlld19kYXRhfQpoZWFkKG1wZykKYGBgCgp0byB0aGlzOgoKIyMgVGhlIHBsb3QKCmBgYHtyIHByZXZpZXdfcGxvdCwgZWNobyA9IEZ9Cm1wZ19wbG90IDwtIGdncGxvdCgKICAgIGRhdGEgPSBtcGcsCiAgICBtYXBwaW5nID0gYWVzKHggPSBjdHksIHkgPSBod3kpCiAgKSArCiAgZ2VvbV9wb2ludCgKICAgIG1hcHBpbmcgPSBhZXMoY29sb3IgPSBmYWN0b3IoY3lsKSkKICApICsKICBnZW9tX2FibGluZSgKICAgIHNsb3BlID0gMSwKICAgIGludGVyY2VwdCA9IDAsCiAgICBjb2xvciA9ICdibGFjaycsCiAgICBsaW5ldHlwZSA9ICdkb3R0ZWQnCiAgKSArCiAgY29vcmRfZml4ZWQoCiAgICByYXRpbyA9IDEKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gJyMgb2YgQ3lsJywKICAgIHZhbHVlcyA9IGMoJ3NreWJsdWUnLCdyb3lhbGJsdWUnLCAnYmx1ZScsICduYXZ5JykKICApICsKICBnZ3RpdGxlKCdNaWxlcyBwZXIgZ2FsbG9uJykgKwogIHRoZW1lX2xpZ2h0KCkKCm1wZ19wbG90CmBgYAoKIyBMZXQncyBwbG90IQoKTG9hZCB0aGUgZ2dwbG90MiBsaWJyYXJ5OgoKYGBge3IgbG9hZF9saWJyYXJ5X25vX2V2YWwsIGV2YWwgPSBGfQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKYGdncGxvdGAgaXMgdGhlIGJhc2UgbGF5ZXIuCgpgYGB7ciBzdGVwMDBfYmFzZX0KZ2dwbG90KCkKYGBgCgpUaGlzIGlzIGEgZ29vZCBwbGFjZSB0byBmb3IgdXMgdG8gZGVmaW5lIG91ciBwbG90J3MgZGVmYXVsdCBkYXRhLgoKYGBge3Igc3RlcDAxX2RhdGF9CmdncGxvdChkYXRhID0gbXBnKQpgYGAKClRoZXJlIGlzbid0IG11Y2ggYmVpbmcgcGxvdHRlZCB5ZXQuLi4gTGV0J3MgdGVsbCBvdXIgcGxvdCBhYm91dCBvdXIgbWFwcGluZywgd2hlcmUgb3VyIGB4YCB3aWxsIGJlIG91ciBgY3R5YCBhbmQgYHlgIHdpbGwgYmUgb3VyIGBod3lgLgoKYGBge3Igc3RlcDAyX21hcHBpbmd9CmdncGxvdCgKICAgIGRhdGEgPSBtcGcsCiAgICBtYXBwaW5nID0gYWVzKHggPSBjdHksIHkgPSBod3kpCiAgKQpgYGAKCk91ciBwbG90IG5vdyBoYXMgZW5vdWdoIGluZm9ybWF0aW9uIHRvIHNldCBzb21lIGF1dG9tYXRpYyBheGlzIGxpbWl0cyBiYXNlZCBvbiB0aGUgZGF0YS4KCmBgYHtyIHN0ZXAwM19wb2ludHN9CmdncGxvdCgKICAgIGRhdGEgPSBtcGcsCiAgICBtYXBwaW5nID0gYWVzKHggPSBjdHksIHkgPSBod3kpCiAgKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKV2UgY2FuIG1hcCB0aGUgbnVtYmVyIG9mIGN5bGluZGVycyBhcyBhIHNldCBvZiBkaXNjcmV0ZSB2YWx1ZXMgdG8gY29sb3IuCgpgYGB7ciBzdGVwMDRfY29sb3JzfQpnZ3Bsb3QoCiAgICBkYXRhID0gbXBnLAogICAgbWFwcGluZyA9IGFlcyh4ID0gY3R5LCB5ID0gaHd5KQogICkgKwogIGdlb21fcG9pbnQoCiAgICBtYXBwaW5nID0gYWVzKGNvbG9yID0gZmFjdG9yKGN5bCkpCiAgKQpgYGAKCkF0IHRoaXMgcG9pbnQsIHdlIGhhdmUgdGhlIGJhc2Ugb2YgdGhlIHBsb3QuICBXZSBjYW4gc2F2ZSB0aGlzIHRvIGEgdmFyaWFibGUsIGBtcGdfYmFzZV9wbG90YCwgYW5kIG1ha2UgYWRkaXRpb25hbCBhZGp1c3RtZW50cy4KCmBgYHtyIHN0ZXAwNV9hc3NpZ259Cm1wZ19iYXNlX3Bsb3QgPC0gZ2dwbG90KAogICAgZGF0YSA9IG1wZywKICAgIG1hcHBpbmcgPSBhZXMoeCA9IGN0eSwgeSA9IGh3eSkKICApICsKICBnZW9tX3BvaW50KAogICAgbWFwcGluZyA9IGFlcyhjb2xvciA9IGZhY3RvcihjeWwpKQogICkKbXBnX2Jhc2VfcGxvdApgYGAKCgpUbyBhZGQgdGhlIHJlZmVyZW5jZSBsaW5lLCB3ZSBhZGQgYSBsYXllciBsaWtlIHNvOgoKYGBge3Igc3RlcDA2X2xpbmV9Cm1wZ19iYXNlX3Bsb3QgKwogIGdlb21fYWJsaW5lKAogICAgc2xvcGUgPSAxLAogICAgaW50ZXJjZXB0ID0gMCwKICAgIGNvbG9yID0gJ2JsYWNrJywKICAgIGxpbmV0eXBlID0gJ2RvdHRlZCcKICApCmBgYAoKTm90ZSB0aGF0IHRoaXMgZ2VvbSBsYXllciBjYW4gYmUgYWRkZWQgb24gdG9wIG9mIGEgYmxhbmsgcGxvdC4gIEFsbCBgZ2VvbV9gIHByZWZpeGVkIGZ1bmN0aW9ucyByZXR1cm4gYW4gYGVudmlyb25tZW50YCBvYmplY3QgdGhhdCBob2xkcyBvbnRvIHRoZSBpbmZvcm1hdGlvbiB3ZSBjYWxsIGl0IHdpdGguCgpgYGB7ciBsaW5lX2xheWVyX2V4YW1wbGV9CmdncGxvdCgpICsKICBnZW9tX2FibGluZSgKICAgIHNsb3BlID0gMSwKICAgIGludGVyY2VwdCA9IDAsCiAgICBjb2xvciA9ICdibGFjaycsCiAgICBsaW5ldHlwZSA9ICdkb3R0ZWQnCiAgKQpgYGAKCkxldCdzIGNvbnRpbnVlLiAgT3VyIHBsb3QgbG9va3MgYSBiaXQgZnVubnkgLS0gdGhlIGdyaWQgZm9yIHRoZSB4IGFuZCB5IGFyZSBub3QgMSB0byAxLiAgTGV0J3MgdXNlIGEgYGNvb3JkX2AgZnVuY3Rpb24gdG8gYWRqdXN0IHRoaXM6CgpgYGB7ciBzdGVwMDdfZml4ZWR9Cm1wZ19iYXNlX3Bsb3QgKwogIGdlb21fYWJsaW5lKAogICAgc2xvcGUgPSAxLAogICAgaW50ZXJjZXB0ID0gMCwKICAgIGNvbG9yID0gJ2JsYWNrJywKICAgIGxpbmV0eXBlID0gJ2RvdHRlZCcKICApICsKICBjb29yZF9maXhlZChyYXRpbyA9IDEpCmBgYAoKV2UgY2FuIGNsZWFuIHVwIHRoZSBwbG90IGxpa2Ugc286CgpgYGB7ciBzdGVwMDhfY2xlYW51cH0KbXBnX3Bsb3QgPC0gbXBnX2Jhc2VfcGxvdCArCiAgZ2VvbV9hYmxpbmUoCiAgICBzbG9wZSA9IDEsCiAgICBpbnRlcmNlcHQgPSAwLAogICAgY29sb3IgPSAnYmxhY2snLAogICAgbGluZXR5cGUgPSAnZG90dGVkJwogICkgKwogIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSAnIyBvZiBDeWwnLAogICAgdmFsdWVzID0gYygnc2t5Ymx1ZScsJ3JveWFsYmx1ZScsICdibHVlJywgJ25hdnknKQogICkgKwogIGdndGl0bGUoJ01pbGVzIHBlciBnYWxsb24nKSArCiAgdGhlbWVfbGlnaHQoKQptcGdfcGxvdApgYGAKCldlIGNhbiBmdXJ0aGVyIG1hbmlwdWxhdGUgdGhlIHBsb3QgdXNpbmcgdGhpbmdzIGxpa2UgYGZhY2V0X2AuCgpgYGB7ciBmYWNldHNfZXhhbXBsZX0KbXBnX3Bsb3QgKwogIGZhY2V0X2dyaWQoLn5jbGFzcykKYGBgCgojIExldCdzIGV4cGVyaW1lbnQKCldlIGNhbiBnZXQgdGhlIGJhc2UgcGxvdHMgZnJvbSB0aGUgZ2dwbG90IGNoZWF0c2hlZXRzIG9uIG91ciBjb21wdXRlciBieSBzb3VyY2luZyBbdGhpcyBSIHNjcmlwdF0oaHR0cHM6Ly9ob3VzdG9udXNlcnMuZ2l0aHViLmlvL2ludHJvLXRvLWdncGxvdDIvZXhhbXBsZXNfZnJvbV9jaGVhdHNoZWV0KS4KCmBgYHtyIGV2YWwgPSBGfQpzb3VyY2UoJ2h0dHBzOi8vZ29vLmdsLzNFUVNYdCcpCmBgYAoKCiMgVGhlIG1hZ2ljIGJlaGluZCB0aGUgYCtgCgpXZSBjYW4gZGlzY292ZXIgaG93IHRoZSBgK2AgaXMgaGVscGluZyB1cyBjb25zdHJ1Y3QgdGhlc2UgcGxvdHMgYnkgcnVubmluZzoKCmBgYHtyIHdoYXRfdGhlX3BsdXN9Cm1ldGhvZHMoJysnKQpgYGAKCkZyb20gdGhlIHJlc3VsdCwgd2UgY2FuIHNlZSB0aGF0IHRoZSBgK2AgZnVuY3Rpb24gaXMgb3ZlcmxvYWRlZCBmb3Igb2JqZWN0cyB0aGF0IG1hdGNoIHRoZSBgZ2dgIG5hbWVzcGFjZS4gIElmIHdlIGxvb2sgW2F0IHRoZSBjb2RlXShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2dncGxvdDIvYmxvYi9tYXN0ZXIvUi9wbG90LWNvbnN0cnVjdGlvbi5yI0wzOSkgZm9yIHRoaXMgZnVuY3Rpb24sIHdlIGxlYXJuIG1vcmUgYWJvdXQgaG93IGl0IHdvcmtzLgoKIyMgT2JqZWN0LW9yaWVudGVkCgpXZSBjYW4gZXhwbG9yZSBtb3JlIG9mIGhvdyBgZ2dwbG90YCB3b3JrcyB1bmRlcm5lYXRoIGJ5IGxvb2tpbmcgYXQ6CgpgYGB7cn0KYmFzZV9wbG90IDwtIGdncGxvdCgpCm5hbWVzKGJhc2VfcGxvdCkKYGBgCgpJdCdzIGFuIG9iamVjdCB0aGF0IGNhbiBiZSBtdXRhdGVkLiAgT3RoZXIgZnVuY3Rpb25zIG91dHB1dCBvYmplY3RzIGFzIHdlbGwuCgpgYGB7cn0KbXBnX21hcHBpbmcgPC0gYWVzKHggPSBjdHksIHkgPSBod3kpCm1wZ19tYXBwaW5nCmBgYAoKTGV0J3MgbXV0YXRlIHRoZSBgYmFzZV9wbG90YDoKCmBgYHtyfQpiYXNlX3Bsb3QkZGF0YSA8LSBtcGcKYmFzZV9wbG90JG1hcHBpbmcgPC0gbXBnX21hcHBpbmcKYmFzZV9wbG90JGxheWVycyA8LSBjKGJhc2VfcGxvdCRsYXllcnMsIGdlb21fcG9pbnQoKSkKYmFzZV9wbG90CmBgYAoKCiMjIEV4dGVuc2liaWxpdHkKCllvdSBjYW4gYWxzbyBjcmVhdGUgY3VzdG9tIGBzdGF0X2BzLCBgZ2VvbV9gcywgYW5kIGB0aGVtZV9gcyBhcyBuZWVkZWQuICBUaGlzIFt2aWduZXR0ZV0oaHR0cDovL2RvY3MuZ2dwbG90Mi5vcmcvY3VycmVudC92aWduZXR0ZXMvZXh0ZW5kaW5nLWdncGxvdDIuaHRtbCkgZXhwbGFpbnMgdGhlIG9iamVjdC1vcmllbnRlZCBwYXR0ZXJucyBmb3IgZ2dwbG90MiwgYW5kIGhvdyB0byBleHRlbmQgdGhlIGxpYnJhcnkgZm9yIHlvdXIgbmVlZHMuCgpUaGVyZSdzIGFjdHVhbGx5IGEgbG90IHdlIGNhbiBsZWFybiBmcm9tIFt0aGUgc291cmNlIGNvZGVdKGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5dmVyc2UvZ2dwbG90Mi90cmVlL21hc3Rlci9SKSwgc3VjaCBhcyBhbGwgcG9zc2libGUgW2BhZXNgIHBhcmFtZXRlcnNdKGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5dmVyc2UvZ2dwbG90Mi9ibG9iL21hc3Rlci9SL2Flcy5yI0w0LUw4KS4KCiMgVGhlIG1ha2luZ3Mgb2YgYSBwaWUKCiMjIyBHcmFtbWFyLWJhc2VkIGdyYXBoaWNzIGxlbmRzIGZsZXhpYmlsaXR5CgpCYWNrIHRvIExlbGFuZCBXaWxraW5zb24ncyBwb2ludCBhYm91dCBtYWtpbmcgYSBwaWUgLS0gdGhhdCBhIHBpZSBjaGFydCBpcyByZWFsbHkgYSBzdGFja2VkIGJhciBjaGFydCB0aGF0IGhhcyBiZWVuIHRyYW5zZm9ybWVkIHdpdGggcG9sYXIgY29vcmRpbmF0ZXMgd2hlcmUgdGhlIHkgaXMgbWFwcGVkIHRvIHRoZSBhbmdsZSwgYS5rLmEuIHRoZXRhLgoKIyMjIFdlIHN0YXJ0IHdpdGggYSBiYXIgY2hhcnQ6CgpgYGB7ciBwaWUwMF9iYXJ9CmRpYW1vbmRzX2J5X2N1dF9iYXNlIDwtIGdncGxvdChkYXRhID0gZGlhbW9uZHMsIG1hcHBpbmcgPSBhZXMoeCA9IGN1dCwgZmlsbCA9IGN1dCkpCmRpYW1vbmRzX2J5X2N1dF9iYXNlICsKICBnZW9tX2JhcigpCmBgYAoKSGVyZSwgd2UgaGF2ZSBhIHN0YWNrZWQgYmFyIGNoYXJ0IG9mIGRpYW1vbmRzIGdyb3VwZWQgYnkgY3V0LgoKYGBge3IgcGllMDFfc3RhY2tlZH0KZGlhbW9uZHNfYnlfY3V0X3N0YWNrZWQgPC0gZGlhbW9uZHNfYnlfY3V0X2Jhc2UgKwogIGdlb21fYmFyKHdpZHRoID0gMSwgYWVzKHg9ZmFjdG9yKCIiKSkpCgpkaWFtb25kc19ieV9jdXRfc3RhY2tlZApgYGAKClRha2luZyB0aGUgYmFyIGNoYXJ0LCB3ZSBzaW1wbHkgImFkZCIgYSBgY29vcmRfcG9sYXJgIHRvIGl0LgoKYGBge3IgcGllMDJfYnVsbHNleWV9CmRpYW1vbmRzX2J5X2N1dF9zdGFja2VkICsKICBjb29yZF9wb2xhcigpICArCiAgc2NhbGVfeF9kaXNjcmV0ZSgiIikKYGBgCgpCeSBkZWZhdWx0LCBgY29vcmRfcG9sYXJgIG1hcHMgdGhlIGB0aGV0YWAgZnJvbSBgeGAgYW5kIHRoZSBgcmFkaXVzYCBmcm9tIGB5YC4KCmBgYHtyIHdoYXRfaXNfYV9jb29yZF9hbnl3YXl9CmJ1bGxzZXllX2Nvb3JkcyA8LSBjb29yZF9wb2xhcigpCmJ1bGxzZXllX2Nvb3JkcyR0aGV0YQpidWxsc2V5ZV9jb29yZHMkcgpgYGAKCldlIG5lZWQgdG8gYSBgY29vcmRfcG9sYXJgIHdoZXJlIHRoZSBgdGhldGFgIG1hcHMgZnJvbSBgeWA6CgpgYGB7ciBwaWVfY29vcmRzfQpwaWVfY29vcmRzIDwtIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKQpwaWVfY29vcmRzJHRoZXRhCmBgYAoKTm93LCB3ZSBjYW4gdHJhbnNmb3JtIHRoZSBzdGFja2VkIGJhciBjaGFydCB0byBhIHBpZSBjaGFydDoKCmBgYHtyIHBpZTAzX3BpZX0KZGlhbW9uZHNfYnlfY3V0X3N0YWNrZWQgKwogIHBpZV9jb29yZHMKYGBgCgpgYGB7cn0KP2Nvb3JkX3BvbGFyCmBgYAoKCiMgQSBsYXllcmVkIGdyYW1tYXIgaXMgcG93ZXJmdWwKClRoZSBmdWxsIGdyYW1tYXIgY29uc2lzdHMgb2Y6CgogICogVGhlIGJhc2UgcGxvdCAtLSBgZ2dwbG90YCAtLSB3aXRoIGRlZmF1bHQgYGRhdGFgIGFuZCBgbWFwcGluZ2BzCiAgKiBBbnkgbnVtYmVyIG9mIGxheWVycyAtLSBgZ2VvbV9gIG9yIGBzdGF0X2AgLS0gZWFjaCB3aXRoCiAgICAqIGBkYXRhYAogICAgICAgICogcGxvdCdzIGBkYXRhYCBieSBkZWZhdWx0CiAgICAqIGBtYXBwaW5nYAogICAgICAgICogcGxvdCdzIGBtYXBwaW5nYHMgYnkgZGVmYXVsdAogICAgICAgICogZGVmaW5lZCBieSBgYWVzYHRoZXRpY3MKICAgICogYGdlb21fYAogICAgICAgICogU29tZXRpbWVzIGRlZmluZWQgYnkgZGVmYXVsdCBieSBgc3RhdF9gIGlmIGRlZmluZWQKICAgICogYHN0YXRfYAogICAgICAgICogSWRlbnRpdHkgYnkgZGVmYXVsdAogICAgICAgICogU29tZXRpbWVzIGRlZmluZWQgYnkgZGVmYXVsdCBieSBgZ2VvbV9gIGlmIGRlZmluZWQKICAgICogYHBvc2l0aW9uYAogICAgICAgICogT3B0aW9uYWwsIGFueSBhZGp1c3RtZW50cyBuZWNlc3NhcnkgdG8gZ2VvbSBwb3NpdGlvbmluZwoKQWRkaXRpb25hbGx5LCB0aGUgZ3JhbW1hciBpbmNsdWRlcyBjb250cm9sIG92ZXIgdGhlIHdob2xlIHBsb3QncwoKICAqIGBzY2FsZWBzCiAgICAqIHRvIGRlZmluZSBob3cgZGF0YSB2YWx1ZXMgbWFwIGBhZXNgdGhldGljIHZhbHVlcwogICogYGNvb3JkaW5hdGVgcwogICogYGZhY2V0YHMKICAgICogc3VicGxvdHMKCgojIFJlYWRpbmcgYW5kIFNvdXJjZXMKCltBIExheWVyZWQgR3JhbW1hciBvZiBHcmFwaGljc10oaHR0cDovL3ZpdGEuaGFkLmNvLm56L3BhcGVycy9sYXllcmVkLWdyYW1tYXIucGRmKQoKW01hc3RlcmluZyB0aGUgR3JhbW1hcl0oaHR0cHM6Ly9ycHVicy5jb20vZmxvd2VydGVhci8yMjQ0MjQpCgpbUiBmb3IgRGF0YSBTY2llbmNlOiBWaXN1YWxpemluZyBEYXRhXShodHRwOi8vcjRkcy5oYWQuY28ubnovZGF0YS12aXN1YWxpc2F0aW9uLmh0bWwjaW50cm9kdWN0aW9uLTEpCgpbRXh0ZW5kaW5nIGdncGxvdDJdKGh0dHA6Ly9kb2NzLmdncGxvdDIub3JnL2N1cnJlbnQvdmlnbmV0dGVzL2V4dGVuZGluZy1nZ3Bsb3QyLmh0bWwpCgpbVGhlIFIgR3JhcGggR2FsbGVyeV0oaHR0cDovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tL3BvcnRmb2xpby9nZ3Bsb3QyLXBhY2thZ2UvKQoKW0RhdGEgQ2FtcDogRGF0YSBWaXN1YWxpemF0aW9uIHdpdGggZ2dwbG90Ml0oaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL2NvdXJzZXMvZGF0YS12aXN1YWxpemF0aW9uLXdpdGgtZ2dwbG90Mi0xKQoKW1N3aXJsOiBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzXShodHRwOi8vc3dpcmxzdGF0cy5jb20vc2NuL2VkYS5odG1sKQoKW2dncGxvdDIgQ2hlYXRzaGVldF0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTYvMTEvZ2dwbG90Mi1jaGVhdHNoZWV0LTIuMS5wZGYpCgpbVGVsbGluZyBzdG9yaWVzIHdpdGggZGF0YSB1c2luZyB0aGUgZ3JhbW1hciBvZiBncmFwaGljc10oaHR0cHM6Ly9jb2Rld29yZHMucmVjdXJzZS5jb20vaXNzdWVzL3NpeC90ZWxsaW5nLXN0b3JpZXMtd2l0aC1kYXRhLXVzaW5nLXRoZS1ncmFtbWFyLW9mLWdyYXBoaWNzKQoKW0ludHJvZHVjdGlvbiB0byBSIEdyYXBoaWNzIHdpdGggZ2dwbG90Ml0oaHR0cDovL3R1dG9yaWFscy5pcS5oYXJ2YXJkLmVkdS9SL1JncmFwaGljcy9SZ3JhcGhpY3MuaHRtbCkKCltnZ3Bsb3QyIHNvdXJjZSBjb2RlXShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2dncGxvdDIvdHJlZS9tYXN0ZXIvUikKCltnZ3Bsb3QyOiBFbGVnYW50IEdyYXBoaWNzIGZvciBEYXRhIEFuYWx5c2lzXShodHRwOi8vZ2dwbG90Mi5vcmcvYm9vay8pCgpbVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3NdKGh0dHBzOi8vd3d3LmFtYXpvbi5jb20vR3JhbW1hci1HcmFwaGljcy1TdGF0aXN0aWNzLUNvbXB1dGluZy9kcC8wMzg3MjQ1NDQ4KQoKW1NlbWlvbG9neSBvZiBHcmFwaGljc10oaHR0cHM6Ly93d3cuYW1hem9uLmNvbS9TZW1pb2xvZ3ktR3JhcGhpY3MtRGlhZ3JhbXMtTmV0d29ya3MtTWFwcy9kcC8xNTg5NDgyNjExKQo=