Dashboard merupakan salah satu perangkat visualisasi yang dapat digunakan untuk manayangkan ringkasan data, analisa,atau indikator tertentu. Penyusunan dashboard menggunakan R software dapat dilakukan dengan beberapa cara, di antaranya adalah dengan menggunakan:

Modul ini akan membahas secara ringkas tentang penyusunan dashboard dengan beberapa pendekatan tersebut dengan menggunakan R Studio.

flexdashboard

Package flexdashboard memungkinkan kita menyusun dashboard menggunakan file berbasis R markdown. Berikut adalah langkah-langkahnya. Pertama, kita perlu install terlebih dulu package flexdashboard tersebut.

install.packages("flexdashboard")

Setelah itu, buatlah file baru dengan cara mengakses menu File > New File > R Markdown > From Template > Flex Dashboard , seperti pada gambar di bawah ini.

Selanjutnya, layout dashboard dapat diatur dengan ketentuan sebagai berikut:

Ilustrasi

Program di atas akan menghasilkan tampilan dengan 2 page, dimana page pertama akan terdiri dari 2 kolom. Kolom pertama hanya berisi Chart A, sedangkan kolom kedua berisi Chart B dan Chart C yang berurut menurut baris. Page 2 akan kosong, karena belum diberikan isi apapun.

Di dalam setiap subheader Chart A, B, dan C, kita dapat menuliskan R code chunk yang akan muncul pada bagian tersebut masing-masing. Anda dapat mencoba menuliskan beberapa contoh syntax pada modul https://rpubs.com/r_anisa/interactive-plot-using-r ke dalam code chunk di Chart A, B, atau C, contohnya seperti hasil berikut ini.

Penjelasan lengkap tentang flexdashboard dapat dipelajari lebih lanjut di Grolemund (2016).

Pengenalan R Shiny

Struktur R shiny terdiri dari tiga komponen utama, yaitu:

R Shiny biasa digunakan untuk membangun aplikasi web, berikut adalah contoh sederhananya.

install.packages("shiny")
library(shiny)
runExample("01_hello")

Pada bagian bawah tampilan contoh tersebut, Anda dapat melihat code yang digunakan untuk membangun app tersebut. Objek ui berikut berisi pengaturan layout yang ingin ditampilkan. Terlihat bahwa interface terdiri dari tiga panel, yaitu:

# Define UI for app that draws a histogram ----
ui <- fluidPage(

  # App title ----
  titlePanel("Hello Shiny!"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Slider for the number of bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Histogram ----
      plotOutput(outputId = "distPlot")

    )
  )
)

Server menjalankan fungsi untuk menghasilkan output histogram yang ditampilkan pada ui. Fungsi renderPlot() digunakan untuk menghasilkan plot berdasarkan input yang diberikan pada menu slidebar di bagian ui.

# Define server logic required to draw a histogram ----
server <- function(input, output) {

  # Histogram of the Old Faithful Geyser Data ----
  # with requested number of bins
  # This expression that generates a histogram is wrapped in a call
  # to renderPlot to indicate that:
  #
  # 1. It is "reactive" and therefore should be automatically
  #    re-executed when inputs (input$bins) change
  # 2. Its output type is a plot
  output$distPlot <- renderPlot({

    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

    })

}

Pada bagian akhir, fungsi shinyApp() akan membuat object Shiny app dari pasangan object ui dan server yang telah dibuat sebelumnya.

# Create Shiny app ----
shinyApp(ui = ui, server = server)

shinydashboard

Package shinydashboard dapat digunakan untuk menyusun dashboard menggunakan R shiny. Berikut adalah contohnya.

install.packages("shinydashboard")
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(title = "Basic dashboard"),
  dashboardSidebar(),
  dashboardBody(
    # Boxes need to be put in a row (or column)
    fluidRow(
      box(plotOutput("plot1", height = 250)),

      box(
        title = "Controls",
        sliderInput("slider", "Number of observations:", 1, 100, 50)
      )
    )
  )
)

server <- function(input, output) {
  set.seed(122)
  histdata <- rnorm(500)

  output$plot1 <- renderPlot({
    data <- histdata[seq_len(input$slider)]
    hist(data)
  })
}

shinyApp(ui, server)

Program di atas akan menghasilkan dashboard sederhana seperti berikut ini.

Beberapa contoh dashboard dapat dilihat di: https://rstudio.github.io/shinydashboard/examples.html .

Modifikasi flexdashboard dengan shiny

Kita dapat memodifikasi dashboard yang disusun menggunakan flexdashboard dengan menambahkan argumen runtime: shiny pada bagian header YAML. Hal ini memungkinkan kita menjalankan konten reaktif pada dashboard menggunakan package shiny.

Perhatikan contoh berikut ini (Sumber: Berishvili, 2020).

Template dashboard yang digunakan dibuat oleh Berishvili (2020) dan dapat diakses di link ini.

Sebagai ilustrasi, akan digunakan data kartu kredit yang diambil dari Kaggle, data tersedia di link ini.

Tuliskan code berikut setelah code chunk dengan argumen {r data}.

data<-read.csv("https://github.com/raoy/data/raw/master/BankChurners.csv")

Categorical.Variables = c("Gender", "Education_Level", "Marital_Status")

Numeric.Variables = c("Customer_Age", "Total_Trans_Ct", "Credit_Limit")

Untuk membuat widgets yang berfungsi sebagai user input, silahkan Anda mencoba menuliskan code chunk berikut pada bagian column pertama.

selectInput("categorical_variable", label = "Select Categorical Variable:", choices = Categorical.Variables)

selectInput("numeric_variable", label = "Select Numeric Variable:", choices = Numeric.Variables)

Syntax tersebut akan menghasilkan tampilan seperti di bawah ini.

Terdapat berbagai jenis input yang tersedia dalam package shiny, di antaranya adalah:

Kegunaan shiny adalah rectivity yang memungkinkan pengguna memperoleh output yang updated sesuai dengan input yang dapat diubah-ubah. Caranya adalah dengan menggunakan berbagai fungsi render yang tersedia pada package shiny, diantaranya adalah:

Pada ilustrasi ini, kita akan membuat dashboard menggunakan grafik dari package plotly yang terdiri dari boxplot, bar chart, dan histogram. Dengan menggunakan fungsi renderPlotly(), grafik yang dihasilkan akan secara otomatis diupdate sesuai dengan input dari user.

Silahkan tuliskan r code chunk berikut pada bagian Chart A.

renderPlotly({
   plot_ly(data,
              x = ~data[[input$numeric_variable]],
              color = ~data[[input$categorical_variable]],
              colors = "Paired",
              type = "box") %>%
  layout(title = "",
         xaxis = list(title = "" ,
                      zeroline = FALSE))
})

Selanjutnya, tuliskan R code chunk berikut pada bagian Chart B.

renderPlotly({
  data %>%
    count(var = data[[input$categorical_variable]], name = "count") %>%
    plot_ly( x = ~var, y = ~ count, type = "bar", marker = list(color = '#008ae6',
                           line = list(color = '#008ae6', width = 2)), hoverinfo = "x+y") %>%
    add_text(text = ~paste0( " (",   scales::percent(count/sum(count)),")"), 
           textposition = "bottom", 
           textfont = list(size = 12, color = "white"), 
           showlegend = FALSE) %>%
    layout(xaxis = list(title = ""), yaxis = list(title = ""))
    
})

Terakhir, silahkan tuliskan code berikut pada bagian Chart C.

renderPlotly({
  plot_ly(x = data[[input$numeric_variable]], type = "histogram",  marker = list(color = "#008ae6",
                            line = list(color = "darkgray",
                                        width = 1)))
})

Program di atas akan menghasilkan dashboard seperti di bawah ini.

Sebagai perbandingan, dengan mengikuti tahapan pada ilustrasi di atas, seharusnya Anda memiliki code akhir seperti pada contoh ini, dan dashboard seperti pada link ini.

REFERENCES

Berishvili, N. (2020, December 31). Create an interactive dashboard with shiny, Flexdashboard, and Plotly. Medium. https://towardsdatascience.com/create-an-interactive-dashboard-with-shiny-flexdashboard-and-plotly-b1f025aebc9c

Grolemund, G. (2016, June 8). Introducing flexdashboards. RStudio | Open source & professional software for data science teams - RStudio. https://www.rstudio.com/resources/webinars/introducing-flexdashboards/

R Studio inc. (2020). The basic parts of a shiny app. Shiny. https://shiny.rstudio.com/articles/basics.html

LS0tDQp0aXRsZTogIk1lbnl1c3VuIERhc2hib2FyZCBNZW5nZ3VuYWthbiBSIg0KYXV0aG9yOiBSYWhtYSBBbmlzYQ0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBsdW1lbg0KLS0tDQoNCkRhc2hib2FyZCBtZXJ1cGFrYW4gc2FsYWggc2F0dSBwZXJhbmdrYXQgdmlzdWFsaXNhc2kgeWFuZyBkYXBhdCBkaWd1bmFrYW4gdW50dWsgbWFuYXlhbmdrYW4gcmluZ2thc2FuIGRhdGEsIGFuYWxpc2EsYXRhdSBpbmRpa2F0b3IgdGVydGVudHUuIFBlbnl1c3VuYW4gZGFzaGJvYXJkIG1lbmdndW5ha2FuIFIgc29mdHdhcmUgZGFwYXQgZGlsYWt1a2FuIGRlbmdhbiBiZWJlcmFwYSBjYXJhLCBkaSBhbnRhcmFueWEgYWRhbGFoIGRlbmdhbiBtZW5nZ3VuYWthbjoNCg0KLSBmbGV4ZGFzaGJvYXJkDQoNCi0gUiBzaGlueWRhc2hib2FyZA0KDQotIFIgc2hpbnkNCg0KTW9kdWwgaW5pIGFrYW4gbWVtYmFoYXMgc2VjYXJhIHJpbmdrYXMgdGVudGFuZyBwZW55dXN1bmFuIGRhc2hib2FyZCBkZW5nYW4gYmViZXJhcGEgcGVuZGVrYXRhbiB0ZXJzZWJ1dCBkZW5nYW4gbWVuZ2d1bmFrYW4gUiBTdHVkaW8uDQoNCiMgZmxleGRhc2hib2FyZA0KDQpQYWNrYWdlIGBmbGV4ZGFzaGJvYXJkYCBtZW11bmdraW5rYW4ga2l0YSBtZW55dXN1biBkYXNoYm9hcmQgbWVuZ2d1bmFrYW4gZmlsZSBiZXJiYXNpcyBSIG1hcmtkb3duLiBCZXJpa3V0IGFkYWxhaCBsYW5na2FoLWxhbmdrYWhueWEuIFBlcnRhbWEsIGtpdGEgcGVybHUgaW5zdGFsbCB0ZXJsZWJpaCBkdWx1IHBhY2thZ2UgYGZsZXhkYXNoYm9hcmRgIHRlcnNlYnV0Lg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygiZmxleGRhc2hib2FyZCIpDQpgYGANCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCnNldHdkKCJEOi9EZXB0LlNUSy9Db3Vyc2VzL0Vrc3Bsb3Jhc2kgZGFuIHZpc3VhbGlzYXNpIGRhdGEvUHJha3Rpa3VtL1AxMiIpDQpgYGANCg0KDQpTZXRlbGFoIGl0dSwgYnVhdGxhaCBmaWxlIGJhcnUgZGVuZ2FuIGNhcmEgbWVuZ2Frc2VzIG1lbnUgKipGaWxlID4gTmV3IEZpbGUgPiBSIE1hcmtkb3duID4gRnJvbSBUZW1wbGF0ZSA+IEZsZXggRGFzaGJvYXJkKiogLCBzZXBlcnRpIHBhZGEgZ2FtYmFyIGRpIGJhd2FoIGluaS4NCg0KIVtdKDEtdGVtcGxhdGUtZmxleGRhc2hib2FyZC5wbmcpe3dpZHRoPTUwMH0NCg0KU2VsYW5qdXRueWEsIGxheW91dCBkYXNoYm9hcmQgZGFwYXQgZGlhdHVyIGRlbmdhbiBrZXRlbnR1YW4gc2ViYWdhaSBiZXJpa3V0Og0KDQoqIEd1bmFrYW4gYCNgIHVudHVrIG1lbWJ1YXQgcGFnZQ0KDQoqIEd1bmFrYW4gYCMjYCB1bnR1ayBtZW1idWF0IHRhbXBpbGFuIGtvbG9tDQoNCiogR3VuYWthbiBgIyMjYCB1bnR1ayBtZW1idWF0IHRhbXBpbGFuIGJhcmlzDQoNCioqSWx1c3RyYXNpKioNCg0KIVtdKDEtY29udG9oLXN5bnRheC1mbGV4ZGFzaGJvYXJkLnBuZyl7d2lkdGg9NzUwfQ0KDQpQcm9ncmFtIGRpIGF0YXMgYWthbiBtZW5naGFzaWxrYW4gdGFtcGlsYW4gZGVuZ2FuIDIgcGFnZSwgZGltYW5hIHBhZ2UgcGVydGFtYSBha2FuIHRlcmRpcmkgZGFyaSAyIGtvbG9tLiBLb2xvbSBwZXJ0YW1hIGhhbnlhIGJlcmlzaSBDaGFydCBBLCBzZWRhbmdrYW4ga29sb20ga2VkdWEgYmVyaXNpIENoYXJ0IEIgZGFuIENoYXJ0IEMgeWFuZyBiZXJ1cnV0IG1lbnVydXQgYmFyaXMuIFBhZ2UgMiBha2FuIGtvc29uZywga2FyZW5hIGJlbHVtIGRpYmVyaWthbiBpc2kgYXBhcHVuLg0KDQohW10oMy1jb250b2gtbGF5b3V0LWZsZXhkYXNoYm9hcmQucG5nKQ0KDQpEaSBkYWxhbSBzZXRpYXAgc3ViaGVhZGVyIENoYXJ0IEEsIEIsIGRhbiBDLCBraXRhIGRhcGF0IG1lbnVsaXNrYW4gUiBjb2RlIGNodW5rIHlhbmcgYWthbiBtdW5jdWwgcGFkYSBiYWdpYW4gdGVyc2VidXQgbWFzaW5nLW1hc2luZy4gQW5kYSBkYXBhdCBtZW5jb2JhIG1lbnVsaXNrYW4gYmViZXJhcGEgY29udG9oIHN5bnRheCBwYWRhIG1vZHVsIDxodHRwczovL3JwdWJzLmNvbS9yX2FuaXNhL2ludGVyYWN0aXZlLXBsb3QtdXNpbmctcj4ga2UgZGFsYW0gY29kZSBjaHVuayBkaSBDaGFydCBBLCBCLCBhdGF1IEMsIGNvbnRvaG55YSBzZXBlcnRpIGhhc2lsIGJlcmlrdXQgaW5pLg0KDQohW10oNC1jb250b2gtZmxleGRhc2hib2FyZC5wbmcpDQoNClBlbmplbGFzYW4gbGVuZ2thcCB0ZW50YW5nIGBmbGV4ZGFzaGJvYXJkYCBkYXBhdCBkaXBlbGFqYXJpIGxlYmloIGxhbmp1dCBkaSBHcm9sZW11bmQgKDIwMTYpLg0KDQoNCiMgUGVuZ2VuYWxhbiBSIFNoaW55DQoNClN0cnVrdHVyIFIgc2hpbnkgdGVyZGlyaSBkYXJpIHRpZ2Ega29tcG9uZW4gdXRhbWEsIHlhaXR1Og0KDQoqICoqdXNlciBpbnRlcmZhY2UgKFVJKSoqLCBpbmkgbWVydXBha2FuIGJhZ2lhbiBkaW1hbmEga2l0YSBiaXNhIG1lbmdhdHVyIHRhbXBpbGFuIGRhcmkgYXBwIHlhbmcga2l0YSBiYW5ndW4uDQoNCiogKipzZXJ2ZXIqKiwgaW5pIG1lcnVwYWthbiBiYWdpYW4gdW50dWsgbWVuamFsYW5rYW4gZnVuZ3NpLWZ1bmdzaSB5YW5nIGFkYSBkaSBkYWxhbSBhcHAuDQoNCiogKipzaGlueUFwcCoqLCBpbmkgbWVydXBha2FuIGJhZ2lhbiB5YW5nIG1lbmdodWJ1bmdrYW4gYW50YXJhIFVJIGRlbmdhbiBzZXJ2ZXIuDQoNClIgU2hpbnkgYmlhc2EgZGlndW5ha2FuIHVudHVrIG1lbWJhbmd1biBhcGxpa2FzaSB3ZWIsIGJlcmlrdXQgYWRhbGFoIGNvbnRvaCBzZWRlcmhhbmFueWEuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCJzaGlueSIpDQpsaWJyYXJ5KHNoaW55KQ0KcnVuRXhhbXBsZSgiMDFfaGVsbG8iKQ0KYGBgDQoNCiFbXSg1LWNvbnRvaC1kZW1vLXNoaW55LnBuZykNCg0KUGFkYSBiYWdpYW4gYmF3YWggdGFtcGlsYW4gY29udG9oIHRlcnNlYnV0LCBBbmRhIGRhcGF0IG1lbGloYXQgY29kZSB5YW5nIGRpZ3VuYWthbiB1bnR1ayBtZW1iYW5ndW4gYXBwIHRlcnNlYnV0LiBPYmplayBgdWlgIGJlcmlrdXQgYmVyaXNpIHBlbmdhdHVyYW4gbGF5b3V0IHlhbmcgaW5naW4gZGl0YW1waWxrYW4uIFRlcmxpaGF0IGJhaHdhIGludGVyZmFjZSB0ZXJkaXJpIGRhcmkgdGlnYSBwYW5lbCwgeWFpdHU6DQoNCiogKipKdWR1bCoqLCBkaWlzaSBkZW5nYW4gdHVsaXNhbiAiSGVsbG8gU2hpbnkhIg0KDQoqICoqU2lkZSBiYXIqKiwgZGlpc2kgZGVuZ2FuIHNsaWRlIGJhciB1bnR1ayBtZW5naW5wdXQgbGViYXIga2VsYXMgaGlzdG9ncmFtICgqYmlucyopLCBkZW5nYW4gYmF0YXMgYmF3YWggMSBkYW4gYmF0YXMgbmlsYWkgbWFrc2ltdW0gNTAsIHNlcnRhIGRpc2V0IGRlbmdhbiBuaWxhaSBhd2FsIDMwLg0KDQoqICoqTWFpbiBwYW5lbCoqLCBkaWd1bmFrYW4gdW50dWsgbWVuYW1waWxrYW4gb3V0cHV0IGJlcnVwYSBoaXN0b2dyYW0uDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojIERlZmluZSBVSSBmb3IgYXBwIHRoYXQgZHJhd3MgYSBoaXN0b2dyYW0gLS0tLQ0KdWkgPC0gZmx1aWRQYWdlKA0KDQogICMgQXBwIHRpdGxlIC0tLS0NCiAgdGl0bGVQYW5lbCgiSGVsbG8gU2hpbnkhIiksDQoNCiAgIyBTaWRlYmFyIGxheW91dCB3aXRoIGlucHV0IGFuZCBvdXRwdXQgZGVmaW5pdGlvbnMgLS0tLQ0KICBzaWRlYmFyTGF5b3V0KA0KDQogICAgIyBTaWRlYmFyIHBhbmVsIGZvciBpbnB1dHMgLS0tLQ0KICAgIHNpZGViYXJQYW5lbCgNCg0KICAgICAgIyBJbnB1dDogU2xpZGVyIGZvciB0aGUgbnVtYmVyIG9mIGJpbnMgLS0tLQ0KICAgICAgc2xpZGVySW5wdXQoaW5wdXRJZCA9ICJiaW5zIiwNCiAgICAgICAgICAgICAgICAgIGxhYmVsID0gIk51bWJlciBvZiBiaW5zOiIsDQogICAgICAgICAgICAgICAgICBtaW4gPSAxLA0KICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsDQogICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQ0KDQogICAgKSwNCg0KICAgICMgTWFpbiBwYW5lbCBmb3IgZGlzcGxheWluZyBvdXRwdXRzIC0tLS0NCiAgICBtYWluUGFuZWwoDQoNCiAgICAgICMgT3V0cHV0OiBIaXN0b2dyYW0gLS0tLQ0KICAgICAgcGxvdE91dHB1dChvdXRwdXRJZCA9ICJkaXN0UGxvdCIpDQoNCiAgICApDQogICkNCikNCmBgYA0KDQpTZXJ2ZXIgbWVuamFsYW5rYW4gZnVuZ3NpIHVudHVrIG1lbmdoYXNpbGthbiBvdXRwdXQgaGlzdG9ncmFtIHlhbmcgZGl0YW1waWxrYW4gcGFkYSBgdWlgLiBGdW5nc2kgYHJlbmRlclBsb3QoKWAgZGlndW5ha2FuIHVudHVrIG1lbmdoYXNpbGthbiBwbG90IGJlcmRhc2Fya2FuIGlucHV0IHlhbmcgZGliZXJpa2FuIHBhZGEgbWVudSBzbGlkZWJhciBkaSBiYWdpYW4gYHVpYC4gDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojIERlZmluZSBzZXJ2ZXIgbG9naWMgcmVxdWlyZWQgdG8gZHJhdyBhIGhpc3RvZ3JhbSAtLS0tDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KDQogICMgSGlzdG9ncmFtIG9mIHRoZSBPbGQgRmFpdGhmdWwgR2V5c2VyIERhdGEgLS0tLQ0KICAjIHdpdGggcmVxdWVzdGVkIG51bWJlciBvZiBiaW5zDQogICMgVGhpcyBleHByZXNzaW9uIHRoYXQgZ2VuZXJhdGVzIGEgaGlzdG9ncmFtIGlzIHdyYXBwZWQgaW4gYSBjYWxsDQogICMgdG8gcmVuZGVyUGxvdCB0byBpbmRpY2F0ZSB0aGF0Og0KICAjDQogICMgMS4gSXQgaXMgInJlYWN0aXZlIiBhbmQgdGhlcmVmb3JlIHNob3VsZCBiZSBhdXRvbWF0aWNhbGx5DQogICMgICAgcmUtZXhlY3V0ZWQgd2hlbiBpbnB1dHMgKGlucHV0JGJpbnMpIGNoYW5nZQ0KICAjIDIuIEl0cyBvdXRwdXQgdHlwZSBpcyBhIHBsb3QNCiAgb3V0cHV0JGRpc3RQbG90IDwtIHJlbmRlclBsb3Qoew0KDQogICAgeCAgICA8LSBmYWl0aGZ1bCR3YWl0aW5nDQogICAgYmlucyA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSBpbnB1dCRiaW5zICsgMSkNCg0KICAgIGhpc3QoeCwgYnJlYWtzID0gYmlucywgY29sID0gIiM3NUFBREIiLCBib3JkZXIgPSAid2hpdGUiLA0KICAgICAgICAgeGxhYiA9ICJXYWl0aW5nIHRpbWUgdG8gbmV4dCBlcnVwdGlvbiAoaW4gbWlucykiLA0KICAgICAgICAgbWFpbiA9ICJIaXN0b2dyYW0gb2Ygd2FpdGluZyB0aW1lcyIpDQoNCiAgICB9KQ0KDQp9DQoNCmBgYA0KDQpQYWRhIGJhZ2lhbiBha2hpciwgZnVuZ3NpIGBzaGlueUFwcCgpYCBha2FuIG1lbWJ1YXQgb2JqZWN0IFNoaW55IGFwcCBkYXJpIHBhc2FuZ2FuIG9iamVjdCBgdWlgIGRhbiBgc2VydmVyYCB5YW5nIHRlbGFoIGRpYnVhdCBzZWJlbHVtbnlhLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyBDcmVhdGUgU2hpbnkgYXBwIC0tLS0NCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikNCmBgYA0KDQojIHNoaW55ZGFzaGJvYXJkDQoNClBhY2thZ2UgYHNoaW55ZGFzaGJvYXJkYCBkYXBhdCBkaWd1bmFrYW4gdW50dWsgbWVueXVzdW4gZGFzaGJvYXJkIG1lbmdndW5ha2FuIFIgc2hpbnkuIEJlcmlrdXQgYWRhbGFoIGNvbnRvaG55YS4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoInNoaW55ZGFzaGJvYXJkIikNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQoNCnVpIDwtIGRhc2hib2FyZFBhZ2UoDQogIGRhc2hib2FyZEhlYWRlcih0aXRsZSA9ICJCYXNpYyBkYXNoYm9hcmQiKSwNCiAgZGFzaGJvYXJkU2lkZWJhcigpLA0KICBkYXNoYm9hcmRCb2R5KA0KICAgICMgQm94ZXMgbmVlZCB0byBiZSBwdXQgaW4gYSByb3cgKG9yIGNvbHVtbikNCiAgICBmbHVpZFJvdygNCiAgICAgIGJveChwbG90T3V0cHV0KCJwbG90MSIsIGhlaWdodCA9IDI1MCkpLA0KDQogICAgICBib3goDQogICAgICAgIHRpdGxlID0gIkNvbnRyb2xzIiwNCiAgICAgICAgc2xpZGVySW5wdXQoInNsaWRlciIsICJOdW1iZXIgb2Ygb2JzZXJ2YXRpb25zOiIsIDEsIDEwMCwgNTApDQogICAgICApDQogICAgKQ0KICApDQopDQoNCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7DQogIHNldC5zZWVkKDEyMikNCiAgaGlzdGRhdGEgPC0gcm5vcm0oNTAwKQ0KDQogIG91dHB1dCRwbG90MSA8LSByZW5kZXJQbG90KHsNCiAgICBkYXRhIDwtIGhpc3RkYXRhW3NlcV9sZW4oaW5wdXQkc2xpZGVyKV0NCiAgICBoaXN0KGRhdGEpDQogIH0pDQp9DQoNCnNoaW55QXBwKHVpLCBzZXJ2ZXIpDQpgYGANCg0KUHJvZ3JhbSBkaSBhdGFzIGFrYW4gbWVuZ2hhc2lsa2FuIGRhc2hib2FyZCBzZWRlcmhhbmEgc2VwZXJ0aSBiZXJpa3V0IGluaS4NCg0KIVtdKDYtY29udG9oLXNoaW55ZGFzaGJvYXJkLnBuZykNCg0KQmViZXJhcGEgY29udG9oIGRhc2hib2FyZCBkYXBhdCBkaWxpaGF0IGRpOiA8aHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9zaGlueWRhc2hib2FyZC9leGFtcGxlcy5odG1sPiAuDQoNCiMgTW9kaWZpa2FzaSBmbGV4ZGFzaGJvYXJkIGRlbmdhbiBzaGlueQ0KDQpLaXRhIGRhcGF0IG1lbW9kaWZpa2FzaSBkYXNoYm9hcmQgeWFuZyBkaXN1c3VuIG1lbmdndW5ha2FuIGBmbGV4ZGFzaGJvYXJkYCBkZW5nYW4gbWVuYW1iYWhrYW4gYXJndW1lbiBgcnVudGltZTogc2hpbnlgIHBhZGEgYmFnaWFuIGhlYWRlciBZQU1MLiBIYWwgaW5pIG1lbXVuZ2tpbmthbiBraXRhIG1lbmphbGFua2FuIGtvbnRlbiByZWFrdGlmIHBhZGEgZGFzaGJvYXJkIG1lbmdndW5ha2FuIHBhY2thZ2UgYHNoaW55YC4NCg0KUGVyaGF0aWthbiBjb250b2ggYmVyaWt1dCBpbmkgKFN1bWJlcjogQmVyaXNodmlsaSwgMjAyMCkuIA0KDQohW10oNy1jdXN0b21pemluZy1mbGV4ZGFzaGJvYXJkLXdpdGgtc2hpbnkucG5nKXt3aWR0aD02MDB9DQpUZW1wbGF0ZSBkYXNoYm9hcmQgeWFuZyBkaWd1bmFrYW4gZGlidWF0IG9sZWggQmVyaXNodmlsaSAoMjAyMCkgZGFuIGRhcGF0IGRpYWtzZXMgZGkgW2xpbmsgaW5pXShodHRwczovL2dpc3QuZ2l0aHViLmNvbS9uYXRhYmVyaXNodmlsaS8xMjdlMjg0OWNmMzI0NjNiZWU5ZTE3Mjg2NDc2MzJmNSkuDQoNClNlYmFnYWkgaWx1c3RyYXNpLCBha2FuIGRpZ3VuYWthbiBkYXRhIGthcnR1IGtyZWRpdCB5YW5nIGRpYW1iaWwgZGFyaSBLYWdnbGUsIGRhdGEgdGVyc2VkaWEgZGkgW2xpbmsgaW5pXShodHRwczovL3d3dy5rYWdnbGUuY29tL3Nha3NoaWdveWFsNy9jcmVkaXQtY2FyZC1jdXN0b21lcnMpLiANCg0KKiAqKk1lbmdpbXBvciBkYXRhKioNCg0KVHVsaXNrYW4gY29kZSBiZXJpa3V0IHNldGVsYWggY29kZSBjaHVuayBkZW5nYW4gYXJndW1lbiBge3IgZGF0YX1gLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KDQpkYXRhPC1yZWFkLmNzdigiaHR0cHM6Ly9naXRodWIuY29tL3Jhb3kvZGF0YS9yYXcvbWFzdGVyL0JhbmtDaHVybmVycy5jc3YiKQ0KDQpDYXRlZ29yaWNhbC5WYXJpYWJsZXMgPSBjKCJHZW5kZXIiLCAiRWR1Y2F0aW9uX0xldmVsIiwgIk1hcml0YWxfU3RhdHVzIikNCg0KTnVtZXJpYy5WYXJpYWJsZXMgPSBjKCJDdXN0b21lcl9BZ2UiLCAiVG90YWxfVHJhbnNfQ3QiLCAiQ3JlZGl0X0xpbWl0IikNCg0KYGBgDQoNCiogKipNZW1idWF0IHVzZXIgaW5wdXQgKHdpZGdldHMpKioNCg0KVW50dWsgbWVtYnVhdCB3aWRnZXRzIHlhbmcgYmVyZnVuZ3NpIHNlYmFnYWkgdXNlciBpbnB1dCwgc2lsYWhrYW4gQW5kYSBtZW5jb2JhIG1lbnVsaXNrYW4gY29kZSBjaHVuayBiZXJpa3V0IHBhZGEgYmFnaWFuIGNvbHVtbiBwZXJ0YW1hLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KDQpzZWxlY3RJbnB1dCgiY2F0ZWdvcmljYWxfdmFyaWFibGUiLCBsYWJlbCA9ICJTZWxlY3QgQ2F0ZWdvcmljYWwgVmFyaWFibGU6IiwgY2hvaWNlcyA9IENhdGVnb3JpY2FsLlZhcmlhYmxlcykNCg0Kc2VsZWN0SW5wdXQoIm51bWVyaWNfdmFyaWFibGUiLCBsYWJlbCA9ICJTZWxlY3QgTnVtZXJpYyBWYXJpYWJsZToiLCBjaG9pY2VzID0gTnVtZXJpYy5WYXJpYWJsZXMpDQoNCmBgYA0KDQpTeW50YXggdGVyc2VidXQgYWthbiBtZW5naGFzaWxrYW4gdGFtcGlsYW4gc2VwZXJ0aSBkaSBiYXdhaCBpbmkuDQoNCiFbXSg4LWZsZXhkYXNoYm9hcmQtd2lkZ2V0cy5wbmcpDQoNClRlcmRhcGF0IGJlcmJhZ2FpIGplbmlzIGlucHV0IHlhbmcgdGVyc2VkaWEgZGFsYW0gcGFja2FnZSBgc2hpbnlgLCBkaSBhbnRhcmFueWEgYWRhbGFoOg0KDQorIGBhY3Rpb25CdXR0b24oKWANCg0KKyBgY2hlY2tib3hJbnB1dCgpYA0KDQorIGBkYXRhSW5wdXQoKWANCg0KKyBgbnVtZXJpY0lucHV0KClgDQoNCisgYHJhZGlvQnV0dG9ucygpYA0KDQorIGBzZWxlY3RJbnB1dCgpYA0KDQorIGBzbGlkZXJJbnB1dCgpYA0KDQorIGB0ZXh0SW5wdXQoKWANCg0KDQoqICoqTWVtYnVhdCBPdXRwdXQgUmVhY3RpdmUgKioNCg0KS2VndW5hYW4gYHNoaW55YCBhZGFsYWggcmVjdGl2aXR5IHlhbmcgbWVtdW5na2lua2FuIHBlbmdndW5hIG1lbXBlcm9sZWggb3V0cHV0IHlhbmcgdXBkYXRlZCBzZXN1YWkgZGVuZ2FuIGlucHV0IHlhbmcgZGFwYXQgZGl1YmFoLXViYWguIENhcmFueWEgYWRhbGFoIGRlbmdhbiBtZW5nZ3VuYWthbiBiZXJiYWdhaSBmdW5nc2kgYHJlbmRlcmAgeWFuZyB0ZXJzZWRpYSBwYWRhIHBhY2thZ2UgYHNoaW55YCwgZGlhbnRhcmFueWEgYWRhbGFoOg0KDQorIGByZW5kZXJQcmludCgpYA0KDQorIGByZW5kZXJUZXh0KClgDQoNCisgYHJlbmRlclRhYmxlKClgDQoNCisgYHJlbmRlckRhdGFUYWJsZSgpYA0KDQorIGByZW5kZXJQbG90KClgDQoNCisgYHJlbmRlckltYWdlKClgDQoNCisgYHJlbmRlclVJKClgDQoNClBhZGEgaWx1c3RyYXNpIGluaSwga2l0YSBha2FuIG1lbWJ1YXQgZGFzaGJvYXJkIG1lbmdndW5ha2FuIGdyYWZpayBkYXJpIHBhY2thZ2UgYHBsb3RseWAgeWFuZyB0ZXJkaXJpIGRhcmkgYm94cGxvdCwgYmFyIGNoYXJ0LCBkYW4gaGlzdG9ncmFtLiBEZW5nYW4gbWVuZ2d1bmFrYW4gZnVuZ3NpIGByZW5kZXJQbG90bHkoKWAsIGdyYWZpayB5YW5nIGRpaGFzaWxrYW4gYWthbiBzZWNhcmEgb3RvbWF0aXMgZGl1cGRhdGUgc2VzdWFpIGRlbmdhbiBpbnB1dCBkYXJpIHVzZXIuDQoNClNpbGFoa2FuIHR1bGlza2FuIHIgY29kZSBjaHVuayBiZXJpa3V0IHBhZGEgYmFnaWFuIENoYXJ0IEEuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpyZW5kZXJQbG90bHkoew0KICAgcGxvdF9seShkYXRhLA0KICAgICAgICAgICAgICB4ID0gfmRhdGFbW2lucHV0JG51bWVyaWNfdmFyaWFibGVdXSwNCiAgICAgICAgICAgICAgY29sb3IgPSB+ZGF0YVtbaW5wdXQkY2F0ZWdvcmljYWxfdmFyaWFibGVdXSwNCiAgICAgICAgICAgICAgY29sb3JzID0gIlBhaXJlZCIsDQogICAgICAgICAgICAgIHR5cGUgPSAiYm94IikgJT4lDQogIGxheW91dCh0aXRsZSA9ICIiLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIiIgLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5lID0gRkFMU0UpKQ0KfSkNCmBgYA0KDQpTZWxhbmp1dG55YSwgdHVsaXNrYW4gUiBjb2RlIGNodW5rIGJlcmlrdXQgcGFkYSBiYWdpYW4gQ2hhcnQgQi4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCnJlbmRlclBsb3RseSh7DQogIGRhdGEgJT4lDQogICAgY291bnQodmFyID0gZGF0YVtbaW5wdXQkY2F0ZWdvcmljYWxfdmFyaWFibGVdXSwgbmFtZSA9ICJjb3VudCIpICU+JQ0KICAgIHBsb3RfbHkoIHggPSB+dmFyLCB5ID0gfiBjb3VudCwgdHlwZSA9ICJiYXIiLCBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJyMwMDhhZTYnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnIzAwOGFlNicsIHdpZHRoID0gMikpLCBob3ZlcmluZm8gPSAieCt5IikgJT4lDQogICAgYWRkX3RleHQodGV4dCA9IH5wYXN0ZTAoICIgKCIsICAgc2NhbGVzOjpwZXJjZW50KGNvdW50L3N1bShjb3VudCkpLCIpIiksIA0KICAgICAgICAgICB0ZXh0cG9zaXRpb24gPSAiYm90dG9tIiwgDQogICAgICAgICAgIHRleHRmb250ID0gbGlzdChzaXplID0gMTIsIGNvbG9yID0gIndoaXRlIiksIA0KICAgICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UpICU+JQ0KICAgIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiIiksIHlheGlzID0gbGlzdCh0aXRsZSA9ICIiKSkNCiAgICANCn0pDQpgYGANCg0KVGVyYWtoaXIsIHNpbGFoa2FuIHR1bGlza2FuIGNvZGUgYmVyaWt1dCBwYWRhIGJhZ2lhbiBDaGFydCBDLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KcmVuZGVyUGxvdGx5KHsNCiAgcGxvdF9seSh4ID0gZGF0YVtbaW5wdXQkbnVtZXJpY192YXJpYWJsZV1dLCB0eXBlID0gImhpc3RvZ3JhbSIsICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gIiMwMDhhZTYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImRhcmtncmF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDEpKSkNCn0pDQpgYGANCg0KUHJvZ3JhbSBkaSBhdGFzIGFrYW4gbWVuZ2hhc2lsa2FuIGRhc2hib2FyZCBzZXBlcnRpIGRpIGJhd2FoIGluaS4NCg0KIVtdKDktZmluYWwtZmxleGRhc2hib2FyZC1zaGlueS11aS5wbmcpDQoNClNlYmFnYWkgcGVyYmFuZGluZ2FuLCBkZW5nYW4gbWVuZ2lrdXRpIHRhaGFwYW4gcGFkYSBpbHVzdHJhc2kgZGkgYXRhcywgc2VoYXJ1c255YSBBbmRhIG1lbWlsaWtpIGNvZGUgYWtoaXIgc2VwZXJ0aSBwYWRhIFtjb250b2ggaW5pXShodHRwczovL2dpc3QuZ2l0aHViLmNvbS9uYXRhYmVyaXNodmlsaS8zMWQ2MTU2ZTYzNWQ2NTJkNDBhMzhiODgwMWVjYTRiZSksIGRhbiBkYXNoYm9hcmQgc2VwZXJ0aSBwYWRhIFtsaW5rIGluaV0oaHR0cHM6Ly9uYXRhYmVyaXNodmlsaS5zaGlueWFwcHMuaW8vRURBZGFzaGJvYXJkLykuDQoNCiMgUkVGRVJFTkNFUw0KDQpCZXJpc2h2aWxpLCBOLiAoMjAyMCwgRGVjZW1iZXIgMzEpLiBDcmVhdGUgYW4gaW50ZXJhY3RpdmUgZGFzaGJvYXJkIHdpdGggc2hpbnksIEZsZXhkYXNoYm9hcmQsIGFuZCBQbG90bHkuIE1lZGl1bS4gaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2NyZWF0ZS1hbi1pbnRlcmFjdGl2ZS1kYXNoYm9hcmQtd2l0aC1zaGlueS1mbGV4ZGFzaGJvYXJkLWFuZC1wbG90bHktYjFmMDI1YWViYzljDQoNCkdyb2xlbXVuZCwgRy4gKDIwMTYsIEp1bmUgOCkuIEludHJvZHVjaW5nIGZsZXhkYXNoYm9hcmRzLiBSU3R1ZGlvIHwgT3BlbiBzb3VyY2UgJiBwcm9mZXNzaW9uYWwgc29mdHdhcmUgZm9yIGRhdGEgc2NpZW5jZSB0ZWFtcyAtIFJTdHVkaW8uIGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy93ZWJpbmFycy9pbnRyb2R1Y2luZy1mbGV4ZGFzaGJvYXJkcy8NCg0KUiBTdHVkaW8gaW5jLiAoMjAyMCkuIFRoZSBiYXNpYyBwYXJ0cyBvZiBhIHNoaW55IGFwcC4gU2hpbnkuIGh0dHBzOi8vc2hpbnkucnN0dWRpby5jb20vYXJ0aWNsZXMvYmFzaWNzLmh0bWwNCg==