This R Markdown Notebook will provide you a quick tutorial on how to use my package, nbastatR, and the plotly extension, heatmaply to create an interactive heatmap exploring the future cap space of the 30 NBA teams.

Step 1: Load the required packages

Please note that if you don’t have the following packages installed follow the code in the comments. I also recommend that even if you have these packages installed that you run the code in the comments and update to the most recent development version of each package.

Step 2: Acquire the data

This is the most important step, bringing the data into R to explore. In order to do that we will use nbastatR and its function get_teams_yahoo_team_salary_data which brings in team salary data from Yahoo’s fantastic new team salary pages.

all_team_data <-
  get_teams_yahoo_team_salary_data(
    use_all_teams = T,
    nest_data = F
  )
You got team summary salary data for Boston Celtics
You got team summary salary data for Brooklyn Nets
You got team summary salary data for New York Knicks
You got team summary salary data for Philadelphia 76ers
You got team summary salary data for Toronto Raptors
You got team summary salary data for Chicago Bulls
You got team summary salary data for Cleveland Cavaliers
You got team summary salary data for Detroit Pistons
You got team summary salary data for Indiana Pacers
You got team summary salary data for Milwaukee Bucks
You got team summary salary data for Atlanta Hawks
You got team summary salary data for Charlotte Hornets
You got team summary salary data for Miami Heat
You got team summary salary data for Orlando Magic
You got team summary salary data for Washington Wizards
You got team summary salary data for Golden State Warriors
You got team summary salary data for Los Angeles Clippers
You got team summary salary data for Los Angeles Lakers
You got team summary salary data for Phoenix Suns
You got team summary salary data for Sacramento Kings
You got team summary salary data for Dallas Mavericks
You got team summary salary data for Houston Rockets
You got team summary salary data for Memphis Grizzlies
You got team summary salary data for New Orleans Pelicans
You got team summary salary data for San Antonio Spurs
You got team summary salary data for Denver Nuggets
You got team summary salary data for Minnesota Timberwolves
You got team summary salary data for Oklahoma City Thunder
You got team summary salary data for Portland Trail Blazers
You got team summary salary data for Utah Jazz

Step 3: Mung the and summarize the data

The next step involves a little data munging. The first thing we will do is convert the idSeason variable into a factor in order to preserve the variable’s order.

Next, since we only want to explore future available cap space we limit our items to the projected amount of cap space and cap holds {if you don’t know what a cap hold is I highly advise you to read about them here}.

Next, since we want to look at the data by team and season, we group by the season, team, and item. After this we summarize the totals, and convert the values into amount in millions. Then we convert the data from tidy long form to wide form.

Spreading the data creates NA values since certain teams have no cap holds in the future, to fix that we replace NAs with zeros. After that we create a new variable that is the sum of the amount of available cap space and the amount of projected cap holds. This is the number we will explore that shows nominally the amount of money available for teams to sign players while not being over the salary cap, note that after 2016-17 the salary cap number is a projection.

The final step involves a little more tidying to get the data frame into our desired data format.

Finally, we will select the only the variables we want to visualize, season, and projected cap space and add team slug as the row-name of the data.

team_cap_space <-
  all_team_data %>%
  mutate(idSeason = idSeason %>% factor(ordered = T)) %>%
  dplyr::filter(nameItem %in% c('amountCapSpace', 'amountCapHold')) %>%
  dplyr::select(idSeason, slugTeamYahoo, nameItem, value) %>%
  group_by(idSeason, slugTeamYahoo, nameItem) %>%
  summarise(value = sum(value, na.rm = T) / 1000000) %>%
  ungroup %>%
  spread(nameItem, value) %>%
  replace_na(list(amountCapHold = 0)) %>%
  mutate(amountSpaceLessHold = amountCapHold + amountCapSpace) %>%
  dplyr::select(idSeason, slugTeamYahoo, amountSpaceLessHold) %>%
  gather(item, amountSpaceLessHold, -c(idSeason, slugTeamYahoo)) %>%
  dplyr::select(-item) %>%
  spread(idSeason, amountSpaceLessHold)
row.names(team_cap_space) <-
  team_cap_space$slugTeamYahoo
Warning: Setting row names on a tibble is deprecated.
plot_data <-
  team_cap_space %>%
  dplyr::select(-slugTeamYahoo)

Team cap space by season, excluding cap holds

This data makes some assumptions that are not likely in reality including, player Early Termination Options aren’t exercised and both Team Options and Player Options are exercised. Put another way it assumes that the player finishes out the contract in its absolute term. While this may be an unreasonable assumption, it also means that we know now that in all likelihood teams will have more available cap space than we see here in the event Player/Team options are not exercised, and Early Termination Options are exercised.


Step 4: Calculate the optimal number of clusters

This is an important step. There are many ways to try to optimize the number of clusters within a set of data. I personally prefer the mclust package which we will use today. For those interested in other possible clustering optimization methods this stackoverflow post is an absolute MUST read.


team_clusters <-
  plot_data %>% Mclust() %>% .$z %>% dim %>% .[2]
[1] 6

Step 5: Plot the clustered heat maps

The last step is the to plot the heat maps. We will do 2 heat maps, 1 of using the nominal amount of projected cap space, and the other using the amount of projected cap space scaled at mean zero. Please note that this code will only work if you have the Myriad Pro font library installed. You can either install the font or just take the font out of the code.

Plot 1: Unscaled heatmap
unscaled <-
  plot_data %>%
  heatmaply(
    column_text_angle = 0,
    k_row = team_clusters,
    Colv = FALSE,
    Rowv = T
  ) %>%
  layout(
    margin = list(l = 130, b = 40),
    font = list(
      family = "MyriadPro-Cond", # Requires Myriad Pro font to be installed on your computer
      size = 12,
      color = "#7f7f7f"
    ),
    title = "NBA Cap Space by Team and Season, Less Cap Holds (millions)"
  )
Plot 1: Scaled heatmap
scaled <-
  plot_data %>%
  heatmaply(
    column_text_angle = 0,
    k_row = team_clusters,
    Colv = FALSE,
    Rowv = T,
    scale = 'col'
  ) %>%
  layout(
    margin = list(l = 130, b = 40),
    font = list(
      family = "MyriadPro-Cond",
      size = 12,
      color = "#7f7f7f"
    ),
    title = "NBA Cap Space by Team and Season, Scaled Mean 0"
  )

Conclusion

Now you have successfully explored the entire landscape of NBA salary through the 2020-21 season in only a few lines of code all thanks to R and it’s community of package developers! Until next time, keep learning and exploring, preferably in R.

LS0tCnRpdGxlOiAiRXhwbG9yaW5nIE5CQSB0ZWFtIHNhbGFyeSBjYXAgcm9vbSB3aXRoIG5iYXN0YXRSIGFuZCBoZWF0bWFwbHkiCmF1dGhvcjogIkFsZXggQnJlc2xlciIKZGF0ZTogIjIwMTYtMDctMDciCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBjc3M6IHNlbWFudGljX2Nzcy9zZW1hbnRpYy5taW4uY3NzCiAgICBmaWdfaGVpZ2h0OiA1CiAgICBmaWdfd2lkdGg6IDEyCiAgICB0b2M6IG5vCi0tLQo8c2NyaXB0IHNyYz0ic2VtYW50aWNfY3NzL3NlbWFudGljLm1pbi5qcyI+PC9zY3JpcHQ+Cjxicj4KPHA+VGhpcyBbUiBNYXJrZG93biBOb3RlYm9va10oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS9yX25vdGVib29rcy5odG1sKSB3aWxsIHByb3ZpZGUgeW91IGEgcXVpY2sgdHV0b3JpYWwgb24gaG93IHRvIHVzZSBteSBwYWNrYWdlLCBbbmJhc3RhdFJdKGh0dHBzOi8vZ2l0aHViLmNvbS9hYnJlc2xlci9uYmFzdGF0UiksIGFuZCB0aGUgW3Bsb3RseV0oaHR0cHM6Ly9wbG90Lmx5LykgZXh0ZW5zaW9uLCBbaGVhdG1hcGx5XShodHRwczovL2dpdGh1Yi5jb20vdGFsZ2FsaWxpL2hlYXRtYXBseSkgdG8gY3JlYXRlIGFuIGludGVyYWN0aXZlIGhlYXRtYXAgZXhwbG9yaW5nIHRoZSBmdXR1cmUgY2FwIHNwYWNlIG9mIHRoZSAzMCBOQkEgdGVhbXMuPC9wPgoKPGg0PlN0ZXAgMTogTG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXM8L2g0PgoKUGxlYXNlIG5vdGUgdGhhdCBpZiB5b3UgZG9uJ3QgaGF2ZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzIGluc3RhbGxlZCBmb2xsb3cgdGhlIGNvZGUgaW4gdGhlIGNvbW1lbnRzLiAgSSBhbHNvIHJlY29tbWVuZCB0aGF0IGV2ZW4gaWYgeW91IGhhdmUgdGhlc2UgcGFja2FnZXMgaW5zdGFsbGVkIHRoYXQgeW91IHJ1biB0aGUgY29kZSBpbiB0aGUgY29tbWVudHMgYW5kIHVwZGF0ZSB0byB0aGUgbW9zdCByZWNlbnQgZGV2ZWxvcG1lbnQgdmVyc2lvbiBvZiBlYWNoIHBhY2thZ2UuCgpgYGB7ciBsb2FkX3BhY2thZ2VzLCBlY2hvPVQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQpsYXBwbHkoCiAgYygnaGVhdG1hcGx5JywgIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ3RhbGdhbGlsaS9oZWF0bWFwbHknKQogICAgJ21jbHVzdCcsICNpbnN0YWxsLnBhY2thZ2VzKCdtY2x1c3QnKQogICAgJ2RwbHlyJywgIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ2hhZGxleS9kcGx5cicpCiAgICAncGxvdGx5JywgIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ3JvcGVuc2NpL3Bsb3RseScpCiAgICAnbmJhc3RhdFInLCAjIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignYWJyZXNsZXIvbmJhc3RhdFInKQogICAgJ3B1cnJyJywgIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ2hhZGxleS9wdXJycicpCiAgICAndGlkeXInICMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdoYWRsZXkvdGlkeXInKQogICksCiAgbGlicmFyeSwKICBjaGFyYWN0ZXIub25seSA9IFQKKQoKb3B0aW9ucyhkaWdpdHMgPSA0KQoKYGBgCgo8aDQ+U3RlcCAyOiBBY3F1aXJlIHRoZSBkYXRhPC9oND4KClRoaXMgaXMgdGhlIG1vc3QgaW1wb3J0YW50IHN0ZXAsIGJyaW5naW5nIHRoZSBkYXRhIGludG8gUiB0byBleHBsb3JlLiAgSW4gb3JkZXIgdG8gZG8gdGhhdCB3ZSB3aWxsIHVzZSBgbmJhc3RhdFJgIGFuZCBpdHMgZnVuY3Rpb24gYGdldF90ZWFtc195YWhvb190ZWFtX3NhbGFyeV9kYXRhYCB3aGljaCBicmluZ3MgaW4gdGVhbSBzYWxhcnkgZGF0YSBmcm9tIFlhaG9vJ3MgZmFudGFzdGljIG5ldyB0ZWFtIHNhbGFyeSBwYWdlcy4KCmBgYHtyIGdldF9kYXRhfQphbGxfdGVhbV9kYXRhIDwtCiAgZ2V0X3RlYW1zX3lhaG9vX3RlYW1fc2FsYXJ5X2RhdGEoCiAgICB1c2VfYWxsX3RlYW1zID0gVCwKICAgIG5lc3RfZGF0YSA9IEYKICApCgpgYGAKCgo8aDQ+U3RlcCAzOiBNdW5nIHRoZSBhbmQgc3VtbWFyaXplIHRoZSBkYXRhPC9oND4KClRoZSBuZXh0IHN0ZXAgaW52b2x2ZXMgYSBsaXR0bGUgZGF0YSBtdW5naW5nLiAgVGhlIGZpcnN0IHRoaW5nIHdlIHdpbGwgZG8gaXMgY29udmVydCB0aGUgYGlkU2Vhc29uYCB2YXJpYWJsZSBpbnRvIGEgZmFjdG9yIGluIG9yZGVyIHRvIHByZXNlcnZlIHRoZSB2YXJpYWJsZSdzIG9yZGVyLiAgCgpOZXh0LCBzaW5jZSB3ZSBvbmx5IHdhbnQgdG8gZXhwbG9yZSBmdXR1cmUgYXZhaWxhYmxlIGNhcCBzcGFjZSB3ZSBsaW1pdCBvdXIgaXRlbXMgdG8gdGhlIHByb2plY3RlZCBhbW91bnQgb2YgY2FwIHNwYWNlIGFuZCBjYXAgaG9sZHMge2lmIHlvdSBkb27igJl0IGtub3cgd2hhdCBhIGNhcCBob2xkIGlzIEkgaGlnaGx5IGFkdmlzZSB5b3UgdG8gcmVhZCBhYm91dCB0aGVtIFtoZXJlXShodHRwOi8vd3d3LmNiYWZhcS5jb20vc2FsYXJ5Y2FwLmh0bSNRMTQpfS4gIAoKTmV4dCwgc2luY2Ugd2Ugd2FudCB0byBsb29rIGF0IHRoZSBkYXRhIGJ5IHRlYW0gYW5kIHNlYXNvbiwgd2UgZ3JvdXAgYnkgdGhlIHNlYXNvbiwgdGVhbSwgYW5kIGl0ZW0uIEFmdGVyIHRoaXMgd2Ugc3VtbWFyaXplIHRoZSB0b3RhbHMsIGFuZCBjb252ZXJ0IHRoZSB2YWx1ZXMgaW50byBhbW91bnQgaW4gbWlsbGlvbnMuIFRoZW4gd2UgY29udmVydCB0aGUgZGF0YSBmcm9tIFt0aWR5XShodHRwOi8vdml0YS5oYWQuY28ubnovcGFwZXJzL3RpZHktZGF0YS5odG1sKSBsb25nIGZvcm0gdG8gd2lkZSBmb3JtLiAgCgpTcHJlYWRpbmcgdGhlIGRhdGEgY3JlYXRlcyBOQSB2YWx1ZXMgc2luY2UgY2VydGFpbiB0ZWFtcyBoYXZlIG5vIGNhcCBob2xkcyBpbiB0aGUgZnV0dXJlLCB0byBmaXggdGhhdCB3ZSByZXBsYWNlIE5BcyB3aXRoIHplcm9zLiAgQWZ0ZXIgdGhhdCB3ZSBjcmVhdGUgYSBuZXcgdmFyaWFibGUgdGhhdCBpcyB0aGUgc3VtIG9mIHRoZSBhbW91bnQgb2YgYXZhaWxhYmxlIGNhcCBzcGFjZSBhbmQgdGhlIGFtb3VudCBvZiBwcm9qZWN0ZWQgY2FwIGhvbGRzLiAgVGhpcyBpcyB0aGUgbnVtYmVyIHdlIHdpbGwgZXhwbG9yZSB0aGF0IHNob3dzIG5vbWluYWxseSB0aGUgYW1vdW50IG9mIG1vbmV5IGF2YWlsYWJsZSBmb3IgdGVhbXMgdG8gc2lnbiBwbGF5ZXJzIHdoaWxlIG5vdCBiZWluZyBvdmVyIHRoZSBzYWxhcnkgY2FwLCBub3RlIHRoYXQgYWZ0ZXIgMjAxNi0xNyB0aGUgc2FsYXJ5IGNhcCBudW1iZXIgaXMgYSBwcm9qZWN0aW9uLiAgCgpUaGUgZmluYWwgc3RlcCBpbnZvbHZlcyBhIGxpdHRsZSBtb3JlIHRpZHlpbmcgdG8gZ2V0IHRoZSBkYXRhIGZyYW1lIGludG8gb3VyIGRlc2lyZWQgZGF0YSBmb3JtYXQuCgpGaW5hbGx5LCB3ZSB3aWxsIHNlbGVjdCB0aGUgb25seSB0aGUgdmFyaWFibGVzIHdlIHdhbnQgdG8gdmlzdWFsaXplLCBzZWFzb24sIGFuZCBwcm9qZWN0ZWQgY2FwIHNwYWNlIGFuZCBhZGQgdGVhbSBzbHVnIGFzIHRoZSByb3ctbmFtZSBvZiB0aGUgZGF0YS4KCmBgYHtyfQp0ZWFtX2NhcF9zcGFjZSA8LQogIGFsbF90ZWFtX2RhdGEgJT4lCiAgbXV0YXRlKGlkU2Vhc29uID0gaWRTZWFzb24gJT4lIGZhY3RvcihvcmRlcmVkID0gVCkpICU+JQogIGRwbHlyOjpmaWx0ZXIobmFtZUl0ZW0gJWluJSBjKCdhbW91bnRDYXBTcGFjZScsICdhbW91bnRDYXBIb2xkJykpICU+JQogIGRwbHlyOjpzZWxlY3QoaWRTZWFzb24sIHNsdWdUZWFtWWFob28sIG5hbWVJdGVtLCB2YWx1ZSkgJT4lCiAgZ3JvdXBfYnkoaWRTZWFzb24sIHNsdWdUZWFtWWFob28sIG5hbWVJdGVtKSAlPiUKICBzdW1tYXJpc2UodmFsdWUgPSBzdW0odmFsdWUsIG5hLnJtID0gVCkgLyAxMDAwMDAwKSAlPiUKICB1bmdyb3VwICU+JQogIHNwcmVhZChuYW1lSXRlbSwgdmFsdWUpICU+JQogIHJlcGxhY2VfbmEobGlzdChhbW91bnRDYXBIb2xkID0gMCkpICU+JQogIG11dGF0ZShhbW91bnRTcGFjZUxlc3NIb2xkID0gYW1vdW50Q2FwSG9sZCArIGFtb3VudENhcFNwYWNlKSAlPiUKICBkcGx5cjo6c2VsZWN0KGlkU2Vhc29uLCBzbHVnVGVhbVlhaG9vLCBhbW91bnRTcGFjZUxlc3NIb2xkKSAlPiUKICBnYXRoZXIoaXRlbSwgYW1vdW50U3BhY2VMZXNzSG9sZCwgLWMoaWRTZWFzb24sIHNsdWdUZWFtWWFob28pKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1pdGVtKSAlPiUKICBzcHJlYWQoaWRTZWFzb24sIGFtb3VudFNwYWNlTGVzc0hvbGQpCgpyb3cubmFtZXModGVhbV9jYXBfc3BhY2UpIDwtCiAgdGVhbV9jYXBfc3BhY2Ukc2x1Z1RlYW1ZYWhvbwoKcGxvdF9kYXRhIDwtCiAgdGVhbV9jYXBfc3BhY2UgJT4lCiAgZHBseXI6OnNlbGVjdCgtc2x1Z1RlYW1ZYWhvbykKYGBgCgojIyMjIFRlYW0gY2FwIHNwYWNlIGJ5IHNlYXNvbiwgZXhjbHVkaW5nIGNhcCBob2xkcwoKVGhpcyBkYXRhIG1ha2VzIHNvbWUgYXNzdW1wdGlvbnMgdGhhdCBhcmUgbm90IGxpa2VseSBpbiByZWFsaXR5IGluY2x1ZGluZywgcGxheWVyIFtFYXJseSBUZXJtaW5hdGlvbiBPcHRpb25zXShodHRwOi8vd3d3LmNiYWZhcS5jb20vc2FsYXJ5Y2FwLmh0bSNRNTkpIGFyZW4ndCBleGVyY2lzZWQgYW5kIGJvdGggW1RlYW0gT3B0aW9uc10oaHR0cDovL3d3dy5jYmFmYXEuY29tL3NhbGFyeWNhcC5odG0jUTU5KSBhbmQgW1BsYXllciBPcHRpb25zXShodHRwOi8vd3d3LmNiYWZhcS5jb20vc2FsYXJ5Y2FwLmh0bSNRNTkpIGFyZSBleGVyY2lzZWQuICBQdXQgYW5vdGhlciB3YXkgaXQgYXNzdW1lcyB0aGF0IHRoZSBwbGF5ZXIgZmluaXNoZXMgb3V0IHRoZSBjb250cmFjdCBpbiBpdHMgYWJzb2x1dGUgdGVybS4gIFdoaWxlIHRoaXMgbWF5IGJlIGFuIHVucmVhc29uYWJsZSBhc3N1bXB0aW9uLCBpdCBhbHNvIG1lYW5zIHRoYXQgd2Uga25vdyBub3cgdGhhdCBpbiBhbGwgbGlrZWxpaG9vZCB0ZWFtcyB3aWxsIGhhdmUgbW9yZSBhdmFpbGFibGUgY2FwIHNwYWNlIHRoYW4gd2Ugc2VlIGhlcmUgaW4gdGhlIGV2ZW50IFBsYXllci9UZWFtIG9wdGlvbnMgYXJlIG5vdCBleGVyY2lzZWQsIGFuZCBFYXJseSBUZXJtaW5hdGlvbiBPcHRpb25zIGFyZSBleGVyY2lzZWQuCgo8YnI+CmBgYHtyIHJlc3VsdHMgPSAnYXNpcycsIGVjaG89Rn0KcDIgPC0gCiAgcGxvdF9kYXRhCgpyb3duYW1lcyhwMikgPC0KICB0ZWFtX2NhcF9zcGFjZSRzbHVnVGVhbVlhaG9vICU+JQogIHBhc3RlMCgKICAgICI8YSBocmVmID0nIiwKICAgIGFsbF90ZWFtX2RhdGEgJT4lIGFycmFuZ2Uoc2x1Z1RlYW1ZYWhvbykgJT4lIC4kdXJsVGVhbVNhbGFyeVlhaG9vICU+JSB1bmlxdWUsCiAgICAiJyB0YXJnZXQ9J19ibGFuayc+IiwKICAgIC4sCiAgICAiPC9hPiIKICApCmZvcm1hdHRhYmxlOjpmb3JtYXR0YWJsZShwMikKYGBgCgo8aDQ+U3RlcCA0OiBDYWxjdWxhdGUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzPC9oND4KClRoaXMgaXMgYW4gaW1wb3J0YW50IHN0ZXAuICBUaGVyZSBhcmUgbWFueSB3YXlzIHRvIHRyeSB0byBvcHRpbWl6ZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHdpdGhpbiBhIHNldCBvZiBkYXRhLiAgSSBwZXJzb25hbGx5IHByZWZlciB0aGUgW21jbHVzdF0oaHR0cDovL3d3dy5zdGF0Lndhc2hpbmd0b24uZWR1L21jbHVzdC8pIHBhY2thZ2Ugd2hpY2ggd2Ugd2lsbCB1c2UgdG9kYXkuICBGb3IgdGhvc2UgaW50ZXJlc3RlZCBpbiBvdGhlciBwb3NzaWJsZSBjbHVzdGVyaW5nIG9wdGltaXphdGlvbiBtZXRob2RzIFt0aGlzIHN0YWNrb3ZlcmZsb3cgcG9zdF0oaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xNTM3NjA3NS9jbHVzdGVyLWFuYWx5c2lzLWluLXItZGV0ZXJtaW5lLXRoZS1vcHRpbWFsLW51bWJlci1vZi1jbHVzdGVycykgaXMgYW4gYWJzb2x1dGUgKipNVVNUKiogcmVhZC4KCmBgYHtyIHRlYW1fY2x1c3RlcnMsIGluY2x1ZGU9VFJVRX0KdGVhbV9jbHVzdGVycyA8LQogIHBsb3RfZGF0YSAlPiUgTWNsdXN0KCkgJT4lIC4keiAlPiUgZGltICU+JSAuWzJdCiAgCmBgYAoKPGg0PlN0ZXAgNTogUGxvdCB0aGUgY2x1c3RlcmVkIGhlYXQgbWFwczwvaDQ+CgpUaGUgbGFzdCBzdGVwIGlzIHRoZSB0byBwbG90IHRoZSBoZWF0IG1hcHMuICBXZSB3aWxsIGRvIDIgaGVhdCBtYXBzLCAxIG9mIHVzaW5nIHRoZSBub21pbmFsIGFtb3VudCBvZiBwcm9qZWN0ZWQgY2FwIHNwYWNlLCBhbmQgdGhlIG90aGVyIHVzaW5nIHRoZSBhbW91bnQgb2YgcHJvamVjdGVkIGNhcCBzcGFjZSBzY2FsZWQgYXQgbWVhbiB6ZXJvLiAgUGxlYXNlIG5vdGUgdGhhdCB0aGlzIGNvZGUgd2lsbCBvbmx5IHdvcmsgaWYgeW91IGhhdmUgdGhlIFtNeXJpYWQgUHJvXShodHRwOi8vZm9udHN1cC5jb20vZm9udC9teXJpYWQtcHJvLXNlbWlib2xkLWNvbmRlbnNlZC5odG1sKSBmb250IGxpYnJhcnkgaW5zdGFsbGVkLiAgWW91IGNhbiBlaXRoZXIgaW5zdGFsbCB0aGUgZm9udCBvciBqdXN0IHRha2UgdGhlIGZvbnQgb3V0IG9mIHRoZSBjb2RlLgoKPGg1PlBsb3QgMTogVW5zY2FsZWQgaGVhdG1hcDwvaDU+CgpgYGB7ciBoZWF0bWFwX3NhbGFyaWVzLCBpbmNsdWRlPVRSVUV9CnVuc2NhbGVkIDwtCiAgcGxvdF9kYXRhICU+JQogIGhlYXRtYXBseSgKICAgIGNvbHVtbl90ZXh0X2FuZ2xlID0gMCwKICAgIGtfcm93ID0gdGVhbV9jbHVzdGVycywKICAgIENvbHYgPSBGQUxTRSwKICAgIFJvd3YgPSBUCiAgKSAlPiUKICBsYXlvdXQoCiAgICBtYXJnaW4gPSBsaXN0KGwgPSAxMzAsIGIgPSA0MCksCiAgICBmb250ID0gbGlzdCgKICAgICAgZmFtaWx5ID0gIk15cmlhZFByby1Db25kIiwgIyBSZXF1aXJlcyBNeXJpYWQgUHJvIGZvbnQgdG8gYmUgaW5zdGFsbGVkIG9uIHlvdXIgY29tcHV0ZXIKICAgICAgc2l6ZSA9IDEyLAogICAgICBjb2xvciA9ICIjN2Y3ZjdmIgogICAgKSwKICAgIHRpdGxlID0gIk5CQSBDYXAgU3BhY2UgYnkgVGVhbSBhbmQgU2Vhc29uLCBMZXNzIENhcCBIb2xkcyAobWlsbGlvbnMpIgogICkKCmBgYAoKYGBge3IgdW5zY2FsZWRfcGxvdCwgcmVzdWx0cyA9ICdhc2lzJywgZWNobz1GLCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLndpZHRoPTEyfQp1bnNjYWxlZApgYGAKCgo8aDU+UGxvdCAxOiBTY2FsZWQgaGVhdG1hcDwvaDU+CgpgYGB7ciBoZWF0bWFwX3NhbGFyaWVzX3NjYWxlZCwgaW5jbHVkZT1UUlVFfQpzY2FsZWQgPC0KICBwbG90X2RhdGEgJT4lCiAgaGVhdG1hcGx5KAogICAgY29sdW1uX3RleHRfYW5nbGUgPSAwLAogICAga19yb3cgPSB0ZWFtX2NsdXN0ZXJzLAogICAgQ29sdiA9IEZBTFNFLAogICAgUm93diA9IFQsCiAgICBzY2FsZSA9ICdjb2wnCiAgKSAlPiUKICBsYXlvdXQoCiAgICBtYXJnaW4gPSBsaXN0KGwgPSAxMzAsIGIgPSA0MCksCiAgICBmb250ID0gbGlzdCgKICAgICAgZmFtaWx5ID0gIk15cmlhZFByby1Db25kIiwKICAgICAgc2l6ZSA9IDEyLAogICAgICBjb2xvciA9ICIjN2Y3ZjdmIgogICAgKSwKICAgIHRpdGxlID0gIk5CQSBDYXAgU3BhY2UgYnkgVGVhbSBhbmQgU2Vhc29uLCBTY2FsZWQgTWVhbiAwIgogICkKCmBgYAoKYGBge3Igc2NhbGVkX3Bsb3QsIHJlc3VsdHMgPSAnYXNpcycsIGVjaG89RiwgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy53aWR0aD0xMn0Kc2NhbGVkCmBgYAoKPGg0PkNvbmNsdXNpb248L2g0PgoKTm93IHlvdSBoYXZlIHN1Y2Nlc3NmdWxseSBleHBsb3JlZCB0aGUgZW50aXJlIGxhbmRzY2FwZSBvZiBOQkEgc2FsYXJ5IHRocm91Z2ggdGhlIDIwMjAtMjEgc2Vhc29uIGluIG9ubHkgYSBmZXcgbGluZXMgb2YgY29kZSBhbGwgdGhhbmtzIHRvIFIgYW5kIGl0J3MgY29tbXVuaXR5IG9mIHBhY2thZ2UgZGV2ZWxvcGVycyEgIFVudGlsIG5leHQgdGltZSwga2VlcCBsZWFybmluZyBhbmQgZXhwbG9yaW5nLCBwcmVmZXJhYmx5IGluIFIuCg==