这本书集中在静态图形-图像可以放置在论文,海报,幻灯片和期刊文章。通过与JavaScript库的连接,R还可以生成可放置在web页面上的交互式图形。

交互式图形超出了本书的范围。本章将指出一些最佳选项,以便您可以进一步研究它们。大多数使用htmlwidgets的R。

注意,如果你在iPad上阅读这篇文章,一些功能将不可用(如鼠标悬停)

10.1 leaflet

Leaflet是用于交互式地图的JavaScript库。Leaflet包可用于生成Leaflet R图。

下面是一个简单的例子。点击图钉,用+/-按钮或鼠标滚轮放大或缩小,然后用手型光标拖动地图。

# create leaflet graph
library(leaflet)
leaflet() %>%
  addTiles() %>%
  addMarkers(lng=113.29, 
             lat=33.75, 
             popup="河南平顶山")

您可以创建点密度图和色谱图。软件包网站https://rstudio.github.io/leaflet/提供了详细的教程和大量示例。

10.2 plotly

Plotly是用于创建高端交互式可视化的商业服务和开源产品。plotly包允许您从R内创建图交互图。此外,任何ggplot2图都可以变成图。使用FuelEconomy“燃油经济性”数据,我们将创建一个交互式图表,显示按汽车类别划分的高速公路里程与发动机排量。将鼠标悬停在一个点上会显示有关该点的信息。单击图例点,从绘图中删除该类。再次单击它,将其返回。

library(tidyverse)
library(DT)
library(extrafont)
extrafont::choose_font(fonts = "Times New Roman")    # 很好用的改变字体的包!
## [1] "Times New Roman"
mpg %>% 
  ggplot(aes(displ,hwy)) +
  geom_point(aes(col = class),size = 3) +
   labs(x = "Engine displacement",
       y = "Highway Mileage",
       color = "Car Class") +
  theme(legend.title.align = 0.05,
        plot.title = element_text(hjust = 0.5)) +
  theme(text = element_text(family = "Times New Roman"))->p

p

plotly::ggplotly(p)

有很多关于plotly绘图信息的来源,参见R plotly和R的在线绘图书籍。 此外,DataCamp还提供免费的交互式教程

rbokeh是Bokeh图形库的接口。我们将使用mtcars数据集创建另一个图表,显示发动机缸数每加仑英里数的关系。鼠标移到,并尝试各种控件到图像的右边。

# prepare data
data(mtcars)
mtcars$name <- row.names(mtcars)
mtcars$cyl <- factor(mtcars$cyl)
library(rbokeh)
## Registered S3 method overwritten by 'pryr':
##   method      from
##   print.bytes Rcpp
figure() %>%
  ly_points(disp, mpg, data=mtcars,
            color = cyl, glyph = cyl,
            hover = list(name, mpg, wt))
## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

## Warning in structure(x, class = unique(c("AsIs", oldClass(x)))): Calling 'structure(NULL, *)' is deprecated, as NULL cannot have attributes.
##   Consider 'structure(list(), *)' instead.

你可以用Bokeh创建一些非凡的图形。

10.3 rCharts

rCharts可以创建各种交互式图形。在下面的示例中,创建了头发与眼睛颜色的条形图。尝试将鼠标悬停在栏上。 您可以在分组图和堆叠图之间进行交互选择,并通过眼睛颜色包括或排除个案。

# create interactive bar chart
library(rCharts)
## 
## Attaching package: 'rCharts'
## The following object is masked from 'package:purrr':
## 
##     %||%
## The following object is masked from 'package:readr':
## 
##     read_file
hair_eye_male = subset(as.data.frame(HairEyeColor), 
                       Sex == "Male")
n1 <- nPlot(Freq ~ Hair, 
            group = 'Eye', 
            data = hair_eye_male, 
            type = 'multiBarChart'
)
n1$set(width = 600)
n1$show('iframesrc', cdn=TRUE)
## <iframe srcdoc=' &lt;!doctype HTML&gt;
## &lt;meta charset = &#039;utf-8&#039;&gt;
## &lt;html&gt;
##   &lt;head&gt;
##     
##     &lt;link rel=&#039;stylesheet&#039; href=&#039;//cdnjs.cloudflare.com/ajax/libs/nvd3/1.1.15-beta/nv.d3.min.css&#039;&gt;
##     
##     
##     
##     &lt;script src=&#039;//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js&#039; type=&#039;text/javascript&#039;&gt;&lt;/script&gt;
##     
##     &lt;script src=&#039;//d3js.org/d3.v3.min.js&#039; type=&#039;text/javascript&#039;&gt;&lt;/script&gt;
##     
##     &lt;script src=&#039;//cdnjs.cloudflare.com/ajax/libs/nvd3/1.1.15-beta/nv.d3.min.js&#039; type=&#039;text/javascript&#039;&gt;&lt;/script&gt;
##     
##     &lt;script src=&#039;//nvd3.org/assets/lib/fisheye.js&#039; type=&#039;text/javascript&#039;&gt;&lt;/script&gt;
##     
##     
##     &lt;style&gt;
##     .rChart {
##       display: block;
##       margin-left: auto; 
##       margin-right: auto;
##       width: 600px;
##       height: 400px;
##     }  
##     &lt;/style&gt;
##     
##   &lt;/head&gt;
##   &lt;body &gt;
##     
##     &lt;div id = &#039;chart186041c96627&#039; class = &#039;rChart nvd3&#039;&gt;&lt;/div&gt;    
##     &lt;script type=&#039;text/javascript&#039;&gt;
##  $(document).ready(function(){
##       drawchart186041c96627()
##     });
##     function drawchart186041c96627(){  
##       var opts = {
##  &quot;dom&quot;: &quot;chart186041c96627&quot;,
## &quot;width&quot;:      600,
## &quot;height&quot;:      400,
## &quot;x&quot;: &quot;Hair&quot;,
## &quot;y&quot;: &quot;Freq&quot;,
## &quot;group&quot;: &quot;Eye&quot;,
## &quot;type&quot;: &quot;multiBarChart&quot;,
## &quot;id&quot;: &quot;chart186041c96627&quot; 
## },
##         data = [
##  {
##  &quot;Hair&quot;: &quot;Black&quot;,
## &quot;Eye&quot;: &quot;Brown&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             32 
## },
## {
##  &quot;Hair&quot;: &quot;Brown&quot;,
## &quot;Eye&quot;: &quot;Brown&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             53 
## },
## {
##  &quot;Hair&quot;: &quot;Red&quot;,
## &quot;Eye&quot;: &quot;Brown&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             10 
## },
## {
##  &quot;Hair&quot;: &quot;Blond&quot;,
## &quot;Eye&quot;: &quot;Brown&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              3 
## },
## {
##  &quot;Hair&quot;: &quot;Black&quot;,
## &quot;Eye&quot;: &quot;Blue&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             11 
## },
## {
##  &quot;Hair&quot;: &quot;Brown&quot;,
## &quot;Eye&quot;: &quot;Blue&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             50 
## },
## {
##  &quot;Hair&quot;: &quot;Red&quot;,
## &quot;Eye&quot;: &quot;Blue&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             10 
## },
## {
##  &quot;Hair&quot;: &quot;Blond&quot;,
## &quot;Eye&quot;: &quot;Blue&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             30 
## },
## {
##  &quot;Hair&quot;: &quot;Black&quot;,
## &quot;Eye&quot;: &quot;Hazel&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             10 
## },
## {
##  &quot;Hair&quot;: &quot;Brown&quot;,
## &quot;Eye&quot;: &quot;Hazel&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             25 
## },
## {
##  &quot;Hair&quot;: &quot;Red&quot;,
## &quot;Eye&quot;: &quot;Hazel&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              7 
## },
## {
##  &quot;Hair&quot;: &quot;Blond&quot;,
## &quot;Eye&quot;: &quot;Hazel&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              5 
## },
## {
##  &quot;Hair&quot;: &quot;Black&quot;,
## &quot;Eye&quot;: &quot;Green&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              3 
## },
## {
##  &quot;Hair&quot;: &quot;Brown&quot;,
## &quot;Eye&quot;: &quot;Green&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:             15 
## },
## {
##  &quot;Hair&quot;: &quot;Red&quot;,
## &quot;Eye&quot;: &quot;Green&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              7 
## },
## {
##  &quot;Hair&quot;: &quot;Blond&quot;,
## &quot;Eye&quot;: &quot;Green&quot;,
## &quot;Sex&quot;: &quot;Male&quot;,
## &quot;Freq&quot;:              8 
## } 
## ]
##   
##       if(!(opts.type===&quot;pieChart&quot; || opts.type===&quot;sparklinePlus&quot; || opts.type===&quot;bulletChart&quot;)) {
##         var data = d3.nest()
##           .key(function(d){
##             //return opts.group === undefined ? &#039;main&#039; : d[opts.group]
##             //instead of main would think a better default is opts.x
##             return opts.group === undefined ? opts.y : d[opts.group];
##           })
##           .entries(data);
##       }
##       
##       if (opts.disabled != undefined){
##         data.map(function(d, i){
##           d.disabled = opts.disabled[i]
##         })
##       }
##       
##       nv.addGraph(function() {
##         var chart = nv.models[opts.type]()
##           .width(opts.width)
##           .height(opts.height)
##           
##         if (opts.type != &quot;bulletChart&quot;){
##           chart
##             .x(function(d) { return d[opts.x] })
##             .y(function(d) { return d[opts.y] })
##         }
##           
##          
##         
##           
##         
## 
##         
##         
##         
##       
##        d3.select(&quot;#&quot; + opts.id)
##         .append(&#039;svg&#039;)
##         .datum(data)
##         .transition().duration(500)
##         .call(chart);
## 
##        nv.utils.windowResize(chart.update);
##        return chart;
##       });
##     };
## &lt;/script&gt;
##     
##     &lt;script&gt;&lt;/script&gt;    
##   &lt;/body&gt;
## &lt;/html&gt; ' scrolling='no' frameBorder='0' seamless class='rChart  nvd3  ' id='iframe-chart186041c96627'> </iframe>
##  <style>iframe.rChart{ width: 100%; height: 400px;}</style>

10.4 highcharter

highcharter包提供对Highcharts JavaScript图形库的访问。这个包对非商业用途是免费的。 让我们使用highcharter创建一个交互式折线图,显示几个亚洲国家的预期寿命随时间的变化。数据来自Gapminder数据集。同样,将鼠标移到这些行上并尝试单击图例名称。

# create interactive line chart
library(highcharter)
## Highcharts (www.highcharts.com) is a Highsoft software product which is
## not free for commercial and Governmental use
# prepare data
data(gapminder, package = "gapminder")
library(dplyr)
asia <- gapminder %>%
  filter(continent == "Asia") %>%
  select(year, country, lifeExp)

# convert to long to wide format
library(tidyr)
plotdata <- spread(asia, country, lifeExp)

# generate graph
h <- highchart() %>% 
  hc_xAxis(categories = plotdata$year) %>% 
  hc_add_series(name = "Afghanistan", 
                data = plotdata$Afghanistan) %>% 
  hc_add_series(name = "Bahrain", 
                data = plotdata$Bahrain) %>%
  hc_add_series(name = "Cambodia", 
                data = plotdata$Cambodia) %>%
  hc_add_series(name = "China", 
                data = plotdata$China) %>%
  hc_add_series(name = "India", 
                data = plotdata$India) %>%
  hc_add_series(name = "Iran", 
                data = plotdata$Iran)

h

与本章中的所有交互式图形一样,有一些选项允许自定义图形。

# customize interactive line chart
h <- h %>%
  hc_title(text = "Life Expectancy by Country",
           margin = 20, 
           align = "left",
           style = list(color = "steelblue")) %>% 
  hc_subtitle(text = "1952 to 2007",
              align = "left",
              style = list(color = "#2b908f", 
                           fontWeight = "bold")) %>% 
  hc_credits(enabled = TRUE, # add credits
             text = "Gapminder Data",
             href = "http://gapminder.com") %>% 
  hc_legend(align = "left", 
            verticalAlign = "top",
            layout = "vertical", 
            x = 0, 
            y = 100) %>%
  hc_tooltip(crosshairs = TRUE, 
             backgroundColor = "#FCFFC5",
             shared = TRUE, 
             borderWidth = 4) %>% 
  hc_exporting(enabled = TRUE)

h

通过R和JavaScript的结合,可以获得大量的交互图。选择适合你的方法。

LS0tDQp0aXRsZTogIuWIqeeUqFLov5vooYzmlbDmja7lj6/op4bljJbigJTigJTnrKzljYHnq6DkuqTkupLlm74iDQphdXRob3I6ICJMSkoiDQpkYXRlOiAiMjAyMC8zLzI1Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLGZpZy5zaG93ID0gImhvbGQiLGZpZy5hbGlnbiA9ICJjZW50ZXIiLGNhY2hlID0gVFJVRSkNCmBgYA0KDQrov5nmnKzkuabpm4bkuK3lnKjpnZnmgIHlm77lvaIt5Zu+5YOP5Y+v5Lul5pS+572u5Zyo6K665paH77yM5rW35oql77yM5bm754Gv54mH5ZKM5pyf5YiK5paH56ug44CC6YCa6L+H5LiOSmF2YVNjcmlwdOW6k+eahOi/nuaOpe+8jFLov5jlj6/ku6XnlJ/miJDlj6/mlL7nva7lnKh3ZWLpobXpnaLkuIrnmoTkuqTkupLlvI/lm77lvaLjgIINCg0K5Lqk5LqS5byP5Zu+5b2i6LaF5Ye65LqG5pys5Lmm55qE6IyD5Zu044CC5pys56ug5bCG5oyH5Ye65LiA5Lqb5pyA5L2z6YCJ6aG577yM5Lul5L6/5oKo5Y+v5Lul6L+b5LiA5q2l56CU56m25a6D5Lus44CC5aSn5aSa5pWw5L2/55SoaHRtbHdpZGdldHPnmoRS44CCDQoNCj4g5rOo5oSP77yM5aaC5p6c5L2g5ZyoaVBhZOS4iumYheivu+i/meevh+aWh+eroO+8jOS4gOS6m+WKn+iDveWwhuS4jeWPr+eUqCjlpoLpvKDmoIfmgqzlgZwpDQoNCiMjIDEwLjEgbGVhZmxldA0KDQpMZWFmbGV05piv55So5LqO5Lqk5LqS5byP5Zyw5Zu+55qESmF2YVNjcmlwdOW6k+OAgkxlYWZsZXTljIXlj6/nlKjkuo7nlJ/miJBMZWFmbGV0IFLlm77jgIINCg0K5LiL6Z2i5piv5LiA5Liq566A5Y2V55qE5L6L5a2Q44CC54K55Ye75Zu+6ZKJ77yM55SoKy8t5oyJ6ZKu5oiW6byg5qCH5rua6L2u5pS+5aSn5oiW57yp5bCP77yM54S25ZCO55So5omL5Z6L5YWJ5qCH5ouW5Yqo5Zyw5Zu+44CCDQoNCmBgYHtyfQ0KIyBjcmVhdGUgbGVhZmxldCBncmFwaA0KbGlicmFyeShsZWFmbGV0KQ0KbGVhZmxldCgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRNYXJrZXJzKGxuZz0xMTMuMjksIA0KICAgICAgICAgICAgIGxhdD0zMy43NSwgDQogICAgICAgICAgICAgcG9wdXA9Iuays+WNl+W5s+mhtuWxsSIpDQpgYGANCg0K5oKo5Y+v5Lul5Yib5bu654K55a+G5bqm5Zu+5ZKM6Imy6LCx5Zu+44CC6L2v5Lu25YyF572R56uZPGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8+5o+Q5L6b5LqG6K+m57uG55qE5pWZ56iL5ZKM5aSn6YeP56S65L6L44CCDQoNCiMjIDEwLjIgcGxvdGx5DQoNClBsb3RseeaYr+eUqOS6juWIm+W7uumrmOerr+S6pOS6kuW8j+WPr+inhuWMlueahOWVhuS4muacjeWKoeWSjOW8gOa6kOS6p+WTgeOAgnBsb3RseeWMheWFgeiuuOaCqOS7jlLlhoXliJvlu7rlm77kuqTkupLlm77jgILmraTlpJbvvIzku7vkvZVnZ3Bsb3Qy5Zu+6YO95Y+v5Lul5Y+Y5oiQ5Zu+44CC5L2/55SoRnVlbEVjb25vbXnigJznh4Pmsrnnu4/mtY7mgKfigJ3mlbDmja7vvIzmiJHku6zlsIbliJvlu7rkuIDkuKrkuqTkupLlvI/lm77ooajvvIzmmL7npLrmjInmsb3ovabnsbvliKvliJLliIbnmoTpq5jpgJ/lhazot6/ph4znqIvkuI7lj5HliqjmnLrmjpLph4/jgILlsIbpvKDmoIfmgqzlgZzlnKjkuIDkuKrngrnkuIrkvJrmmL7npLrmnInlhbPor6XngrnnmoTkv6Hmga/jgILljZXlh7vlm77kvovngrnvvIzku47nu5jlm77kuK3liKDpmaTor6XnsbvjgILlho3mrKHljZXlh7vlroPvvIzlsIblhbbov5Tlm57jgIINCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KGV4dHJhZm9udCkNCmV4dHJhZm9udDo6Y2hvb3NlX2ZvbnQoZm9udHMgPSAiVGltZXMgTmV3IFJvbWFuIikgICAgIyDlvojlpb3nlKjnmoTmlLnlj5jlrZfkvZPnmoTljIXvvIENCm1wZyAlPiUgDQogIGdncGxvdChhZXMoZGlzcGwsaHd5KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2wgPSBjbGFzcyksc2l6ZSA9IDMpICsNCiAgIGxhYnMoeCA9ICJFbmdpbmUgZGlzcGxhY2VtZW50IiwNCiAgICAgICB5ID0gIkhpZ2h3YXkgTWlsZWFnZSIsDQogICAgICAgY29sb3IgPSAiQ2FyIENsYXNzIikgKw0KICB0aGVtZShsZWdlbmQudGl0bGUuYWxpZ24gPSAwLjA1LA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJUaW1lcyBOZXcgUm9tYW4iKSktPnANCg0KcA0KDQpwbG90bHk6OmdncGxvdGx5KHApDQpgYGANCg0K5pyJ5b6I5aSa5YWz5LqOcGxvdGx557uY5Zu+5L+h5oGv55qE5p2l5rqQLOWPguingVtSIHBsb3RseV0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKeWSjFLnmoRb5Zyo57q/57uY5Zu+5Lmm57GNXShodHRwczovL3Bsb3RseS1yLmNvbS8p44CCIOatpOWklu+8jERhdGFDYW1w6L+Y5o+Q5L6b5YWN6LS555qEW+S6pOS6kuW8j+aVmeeoi10oaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL2NvbW11bml0eS9ibG9nL2EtZnJlZS1pbnRlcmFjdGl2ZS1wbG90bHktci10dXRvcmlhbCnjgIINCg0KcmJva2Vo5pivQm9rZWjlm77lvaLlupPnmoTmjqXlj6PjgILmiJHku6zlsIbkvb/nlKhtdGNhcnPmlbDmja7pm4bliJvlu7rlj6bkuIDkuKrlm77ooajvvIzmmL7npLoqKuWPkeWKqOacuue8uOaVsCoq5LiOKirmr4/liqDku5Hoi7Hph4zmlbAqKueahOWFs+ezu+OAgum8oOagh+enu+WIsO+8jOW5tuWwneivleWQhOenjeaOp+S7tuWIsOWbvuWDj+eahOWPs+i+ueOAgg0KDQpgYGB7cn0NCiMgcHJlcGFyZSBkYXRhDQpkYXRhKG10Y2FycykNCm10Y2FycyRuYW1lIDwtIHJvdy5uYW1lcyhtdGNhcnMpDQptdGNhcnMkY3lsIDwtIGZhY3RvcihtdGNhcnMkY3lsKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShyYm9rZWgpDQpmaWd1cmUoKSAlPiUNCiAgbHlfcG9pbnRzKGRpc3AsIG1wZywgZGF0YT1tdGNhcnMsDQogICAgICAgICAgICBjb2xvciA9IGN5bCwgZ2x5cGggPSBjeWwsDQogICAgICAgICAgICBob3ZlciA9IGxpc3QobmFtZSwgbXBnLCB3dCkpDQpgYGANCg0K5L2g5Y+v5Lul55SoQm9rZWjliJvlu7rkuIDkupvpnZ7lh6HnmoTlm77lvaLjgIINCg0KIyMgMTAuMyByQ2hhcnRzDQoNCnJDaGFydHPlj6/ku6XliJvlu7rlkITnp43kuqTkupLlvI/lm77lvaLjgILlnKjkuIvpnaLnmoTnpLrkvovkuK3vvIzliJvlu7rkuoblpLTlj5HkuI7nnLznnZvpopzoibLnmoTmnaHlvaLlm77jgILlsJ3or5XlsIbpvKDmoIfmgqzlgZzlnKjmoI/kuIrjgIIg5oKo5Y+v5Lul5Zyo5YiG57uE5Zu+5ZKM5aCG5Y+g5Zu+5LmL6Ze06L+b6KGM5Lqk5LqS6YCJ5oup77yM5bm26YCa6L+H55y8552b6aKc6Imy5YyF5ous5oiW5o6S6Zmk5Liq5qGI44CCDQoNCmBgYHtyfQ0KIyBjcmVhdGUgaW50ZXJhY3RpdmUgYmFyIGNoYXJ0DQpsaWJyYXJ5KHJDaGFydHMpDQpoYWlyX2V5ZV9tYWxlID0gc3Vic2V0KGFzLmRhdGEuZnJhbWUoSGFpckV5ZUNvbG9yKSwgDQogICAgICAgICAgICAgICAgICAgICAgIFNleCA9PSAiTWFsZSIpDQpuMSA8LSBuUGxvdChGcmVxIH4gSGFpciwgDQogICAgICAgICAgICBncm91cCA9ICdFeWUnLCANCiAgICAgICAgICAgIGRhdGEgPSBoYWlyX2V5ZV9tYWxlLCANCiAgICAgICAgICAgIHR5cGUgPSAnbXVsdGlCYXJDaGFydCcNCikNCm4xJHNldCh3aWR0aCA9IDYwMCkNCm4xJHNob3coJ2lmcmFtZXNyYycsIGNkbj1UUlVFKQ0KYGBgDQoNCiMjIDEwLjQgaGlnaGNoYXJ0ZXINCg0KaGlnaGNoYXJ0ZXLljIXmj5Dkvpvlr7lIaWdoY2hhcnRzIEphdmFTY3JpcHTlm77lvaLlupPnmoTorr/pl67jgILov5nkuKrljIXlr7npnZ7llYbkuJrnlKjpgJTmmK/lhY3otLnnmoTjgIINCuiuqeaIkeS7rOS9v+eUqGhpZ2hjaGFydGVy5Yib5bu65LiA5Liq5Lqk5LqS5byP5oqY57q/5Zu+77yM5pi+56S65Yeg5Liq5Lqa5rSy5Zu95a6255qE6aKE5pyf5a+/5ZG96ZqP5pe26Ze055qE5Y+Y5YyW44CC5pWw5o2u5p2l6IeqR2FwbWluZGVy5pWw5o2u6ZuG44CC5ZCM5qC377yM5bCG6byg5qCH56e75Yiw6L+Z5Lqb6KGM5LiK5bm25bCd6K+V5Y2V5Ye75Zu+5L6L5ZCN56ew44CCDQoNCmBgYHtyfQ0KIyBjcmVhdGUgaW50ZXJhY3RpdmUgbGluZSBjaGFydA0KbGlicmFyeShoaWdoY2hhcnRlcikNCg0KIyBwcmVwYXJlIGRhdGENCmRhdGEoZ2FwbWluZGVyLCBwYWNrYWdlID0gImdhcG1pbmRlciIpDQpsaWJyYXJ5KGRwbHlyKQ0KYXNpYSA8LSBnYXBtaW5kZXIgJT4lDQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFzaWEiKSAlPiUNCiAgc2VsZWN0KHllYXIsIGNvdW50cnksIGxpZmVFeHApDQoNCiMgY29udmVydCB0byBsb25nIHRvIHdpZGUgZm9ybWF0DQpsaWJyYXJ5KHRpZHlyKQ0KcGxvdGRhdGEgPC0gc3ByZWFkKGFzaWEsIGNvdW50cnksIGxpZmVFeHApDQoNCiMgZ2VuZXJhdGUgZ3JhcGgNCmggPC0gaGlnaGNoYXJ0KCkgJT4lIA0KICBoY194QXhpcyhjYXRlZ29yaWVzID0gcGxvdGRhdGEkeWVhcikgJT4lIA0KICBoY19hZGRfc2VyaWVzKG5hbWUgPSAiQWZnaGFuaXN0YW4iLCANCiAgICAgICAgICAgICAgICBkYXRhID0gcGxvdGRhdGEkQWZnaGFuaXN0YW4pICU+JSANCiAgaGNfYWRkX3NlcmllcyhuYW1lID0gIkJhaHJhaW4iLCANCiAgICAgICAgICAgICAgICBkYXRhID0gcGxvdGRhdGEkQmFocmFpbikgJT4lDQogIGhjX2FkZF9zZXJpZXMobmFtZSA9ICJDYW1ib2RpYSIsIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBwbG90ZGF0YSRDYW1ib2RpYSkgJT4lDQogIGhjX2FkZF9zZXJpZXMobmFtZSA9ICJDaGluYSIsIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBwbG90ZGF0YSRDaGluYSkgJT4lDQogIGhjX2FkZF9zZXJpZXMobmFtZSA9ICJJbmRpYSIsIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBwbG90ZGF0YSRJbmRpYSkgJT4lDQogIGhjX2FkZF9zZXJpZXMobmFtZSA9ICJJcmFuIiwgDQogICAgICAgICAgICAgICAgZGF0YSA9IHBsb3RkYXRhJElyYW4pDQoNCmgNCmBgYA0KDQrkuI7mnKznq6DkuK3nmoTmiYDmnInkuqTkupLlvI/lm77lvaLkuIDmoLfvvIzmnInkuIDkupvpgInpobnlhYHorrjoh6rlrprkuYnlm77lvaLjgIINCg0KYGBge3J9DQojIGN1c3RvbWl6ZSBpbnRlcmFjdGl2ZSBsaW5lIGNoYXJ0DQpoIDwtIGggJT4lDQogIGhjX3RpdGxlKHRleHQgPSAiTGlmZSBFeHBlY3RhbmN5IGJ5IENvdW50cnkiLA0KICAgICAgICAgICBtYXJnaW4gPSAyMCwgDQogICAgICAgICAgIGFsaWduID0gImxlZnQiLA0KICAgICAgICAgICBzdHlsZSA9IGxpc3QoY29sb3IgPSAic3RlZWxibHVlIikpICU+JSANCiAgaGNfc3VidGl0bGUodGV4dCA9ICIxOTUyIHRvIDIwMDciLA0KICAgICAgICAgICAgICBhbGlnbiA9ICJsZWZ0IiwNCiAgICAgICAgICAgICAgc3R5bGUgPSBsaXN0KGNvbG9yID0gIiMyYjkwOGYiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRXZWlnaHQgPSAiYm9sZCIpKSAlPiUgDQogIGhjX2NyZWRpdHMoZW5hYmxlZCA9IFRSVUUsICMgYWRkIGNyZWRpdHMNCiAgICAgICAgICAgICB0ZXh0ID0gIkdhcG1pbmRlciBEYXRhIiwNCiAgICAgICAgICAgICBocmVmID0gImh0dHA6Ly9nYXBtaW5kZXIuY29tIikgJT4lIA0KICBoY19sZWdlbmQoYWxpZ24gPSAibGVmdCIsIA0KICAgICAgICAgICAgdmVydGljYWxBbGlnbiA9ICJ0b3AiLA0KICAgICAgICAgICAgbGF5b3V0ID0gInZlcnRpY2FsIiwgDQogICAgICAgICAgICB4ID0gMCwgDQogICAgICAgICAgICB5ID0gMTAwKSAlPiUNCiAgaGNfdG9vbHRpcChjcm9zc2hhaXJzID0gVFJVRSwgDQogICAgICAgICAgICAgYmFja2dyb3VuZENvbG9yID0gIiNGQ0ZGQzUiLA0KICAgICAgICAgICAgIHNoYXJlZCA9IFRSVUUsIA0KICAgICAgICAgICAgIGJvcmRlcldpZHRoID0gNCkgJT4lIA0KICBoY19leHBvcnRpbmcoZW5hYmxlZCA9IFRSVUUpDQoNCmgNCmBgYA0KDQrpgJrov4dS5ZKMSmF2YVNjcmlwdOeahOe7k+WQiO+8jOWPr+S7peiOt+W+l+Wkp+mHj+eahOS6pOS6kuWbvuOAgumAieaLqemAguWQiOS9oOeahOaWueazleOAgg0KDQoNCg0KDQoNCg==