Code Show All Code

Hide All Code



Download Rmd Exploring NBA team salary cap room with nbastatR and heatmaply Alex Bresler

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 % 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==