In this R Notebook we will try out some interactive visualizations and some dynamic visualizations that are possible using R.
library(pacman)
p_load(tidyverse, purrr, gapminder, plotly, trelliscopejs, echarts4r, echarts4r.assets, gganimate)
Using the gapminder dataset.
gapminder
Plot by continent.
gapm <- gapminder %>% group_by(continent, country) %>%
ggplot(aes(x = gdpPercap, y = lifeExp,
size = pop, colour = country )) +
geom_point() +
scale_x_log10() +
facet_wrap(~continent) +
theme(legend.position = "none")
gapm

ggplotly(gapm)
`group_by_()` is deprecated as of dplyr 0.7.0.
Please use `group_by()` instead.
See vignette('programming') for more help
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
Using facet_trelliscope can be used to create an interactive collection of plots. Can you image making a million plots?
gapminder %>% ggplot(aes(x = log10(gdpPercap), y = lifeExp,
color = country, size = pop)) +
geom_point() +
theme_bw() +
theme(legend.position = "none") +
facet_trelliscope(~ continent,
nrow = 2, ncol = 3)
using data from the first layer
gapm_trel <- gapminder %>%
ggplot(aes(x = log10(gdpPercap), y = lifeExp,
color = country, size = pop)) +
geom_point() +
theme_bw() +
theme(legend.position = "none") +
facet_trelliscope(~ country + continent,
nrow = 2, ncol = 3)
gapm_trel
using data from the first layer
Try echarts.
titles <- map(unique(gapminder$year), function(x){
list(
text = "Gapminder",
subtext = x,
left = "center"
)
})
gapminder %>%
group_by(year) %>%
e_charts(gdpPercap, timeline = TRUE) %>%
e_scatter(lifeExp, pop, country) %>%
e_y_axis(min = 20, max = 85) %>%
e_x_axis(type = "log", min = 100, max = 100000) %>%
e_timeline_serie(title = titles) %>%
e_tooltip() %>%
e_timeline_opts(
show = TRUE,
orient = "vertical",
symbol = "none",
right = 0,
top = 20,
bottom = 20,
height = NULL,
width = 45,
inverse = TRUE,
playInterval = 1000,
autoPlay = TRUE,
controlStyle = list(
showNextBtn = FALSE,
showPrevBtn = FALSE
),
label = list(
fontSize = 8
)
) %>%
e_theme("dark")
gapminder %>% ggplot(aes(x = gdpPercap, y = lifeExp,
size = pop, colour = country)) +
geom_point(alpha = 0.7, show.legend = FALSE) +
scale_colour_manual(values = country_colors) +
scale_size(range = c(2, 12)) +
scale_x_log10() +
facet_wrap(~ continent)

Try gganimate.
gapminder %>% ggplot(aes(x = gdpPercap, y = lifeExp,
size = pop, colour = country)) +
geom_point(alpha = 0.7, show.legend = FALSE) +
scale_colour_manual(values = country_colors) +
scale_size(range = c(2, 12)) +
scale_x_log10() +
facet_wrap(~ continent) +
# Here comes the gganimate specific bits
labs(title = 'Year: {frame_time}',
x = 'GDP per capita', y = 'life expectancy') +
transition_time(year) +
ease_aes('cubic-in-out')
goo <- gapminder %>% ggplot(aes(x = gdpPercap, y = lifeExp,
size = pop, colour = country)) +
geom_point(alpha = 0.7, show.legend = FALSE) +
scale_colour_manual(values = country_colors) +
scale_size(range = c(2, 12)) +
scale_x_log10() +
facet_wrap(~continent) +
# Here comes the gganimate specific bits
labs(title = 'Year: {frame_time}',
x = 'GDP per capita', y = 'life expectancy') +
transition_time(year) +
ease_aes('cubic-in-out')
anim_save("goo.gif", goo)

Try echarts.
airports <- read.csv(
paste0("https://raw.githubusercontent.com/plotly/datasets/",
"master/2011_february_us_airport_traffic.csv")
)
airports %>%
e_charts(long) %>%
e_globe(
environment = ea_asset("starfield"),
base_texture = ea_asset("world"),
globeOuterRadius = 100
) %>%
e_scatter_3d(lat, cnt, coord_system = "globe", blendMode = 'lighter') %>%
e_visual_map(inRange = list(symbolSize = c(1, 10)))
# download
flights <- jsonlite::fromJSON("https://raw.githubusercontent.com/apache/incubator-echarts/master/test/data/flight.json")
# airports
airports <- as.data.frame(flights$airports)
names(airports) <- flights$airportsFields
airports <- airports %>%
select(name, longitude, latitude) %>%
tibble::rownames_to_column("ID") %>%
mutate(ID = as.integer(paste0(ID)))
# routes
routes <- as.data.frame(flights$routes)
names(routes) <- c("ID", "from", "to")
# airlines
airlines <- as.data.frame(flights$airlines) %>%
tibble::rownames_to_column("ID") %>%
mutate(ID = as.integer(paste(ID))) %>%
select(ID, airline = V1, country = V2) %>%
filter(country == "United States")
# bind
data <- routes %>%
inner_join(airports, by = c("from" = "ID")) %>%
inner_join(airports, by = c("to" = "ID"), suffix = c(".start", ".end")) %>%
inner_join(airlines, by = "ID") %>%
select(airline, longitude.start, latitude.start, longitude.end, latitude.end)
# initialise plot
data %>%
group_by(airline) %>%
e_charts() %>%
e_globe(
base_texture = ea_asset("world dark"),
environment = ea_asset("starfield"),
displacementScale = 0.1,
displacementQuality = "high",
shading = "realistic",
realisticMaterial = list(
roughness = .2,
metalness = 0
),
postEffect = list(
enable = TRUE,
depthOfField = list(
enable = FALSE
)
),
temporalSuperSampling = list(
enable = TRUE
),
light = list(
ambient = list(
intensity = 1
),
main = list(
intensity = .1,
shadow = FALSE
)
),
viewControl = list(autoRotate = FALSE)
) %>%
e_legend(
selectedMode = "single",
left = "left",
textStyle = list(color = "#fff"),
orient = "vertical"
) %>%
e_lines_3d(
longitude.start, latitude.start, longitude.end, latitude.end,
coord_system = "globe",
effect = list(
show = TRUE,
trailWidth = 2,
trailLength = 0.15,
trailOpacity = 1,
trailColor = 'rgb(30, 30, 60)'
),
lineStyle = list(opacity = 0.1, widh = 0.5, color = 'rgb(50, 50, 150)')
)
NA
LS0tCnRpdGxlOiAiSW50ZXJhY3RpdmUgYW5kIER5bmFtaWMgQW5pbWF0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpJbiB0aGlzIFIgTm90ZWJvb2sgd2Ugd2lsbCB0cnkgb3V0IHNvbWUgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbnMgYW5kIHNvbWUgZHluYW1pYyB2aXN1YWxpemF0aW9ucyB0aGF0IGFyZSBwb3NzaWJsZSB1c2luZyBSLgoKYGBge3J9CmxpYnJhcnkocGFjbWFuKQpwX2xvYWQodGlkeXZlcnNlLCBwdXJyciwgZ2FwbWluZGVyLCBwbG90bHksIHRyZWxsaXNjb3BlanMsIGVjaGFydHM0ciwgZWNoYXJ0czRyLmFzc2V0cywgZ2dhbmltYXRlKQpgYGAKClVzaW5nIHRoZSBnYXBtaW5kZXIgZGF0YXNldC4KCmBgYHtyfQpnYXBtaW5kZXIKYGBgCgpQbG90IGJ5IGNvbnRpbmVudC4KCmBgYHtyfQpnYXBtIDwtIGdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50LCBjb3VudHJ5KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBnZHBQZXJjYXAsIHkgPSBsaWZlRXhwLCAKICAgICAgICAgICAgIHNpemUgPSBwb3AsIGNvbG91ciA9IGNvdW50cnkgKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeF9sb2cxMCgpICsgCiAgZmFjZXRfd3JhcCh+Y29udGluZW50KSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpnYXBtCmBgYApgYGB7cn0KZ2dwbG90bHkoZ2FwbSkKYGBgCgpVc2luZyBmYWNldF90cmVsbGlzY29wZSBjYW4gYmUgdXNlZCB0byBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgY29sbGVjdGlvbiBvZiBwbG90cy4gIENhbiB5b3UgaW1hZ2UgbWFraW5nIGEgbWlsbGlvbiBwbG90cz8KCmBgYHtyfQpnYXBtaW5kZXIgJT4lIGdncGxvdChhZXMoeCA9IGxvZzEwKGdkcFBlcmNhcCksIHkgPSBsaWZlRXhwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gY291bnRyeSwgc2l6ZSA9IHBvcCkpICsgIAogIGdlb21fcG9pbnQoKSArIAogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGZhY2V0X3RyZWxsaXNjb3BlKH4gY29udGluZW50LCAKICAgICAgICAgICAgICAgICAgICBucm93ID0gMiwgbmNvbCA9IDMpCmBgYAoKYGBge3J9CmdhcG1fdHJlbCA8LSBnYXBtaW5kZXIgJT4lIAogIGdncGxvdChhZXMoeCA9IGxvZzEwKGdkcFBlcmNhcCksIHkgPSBsaWZlRXhwLAogICAgICAgICAgICAgY29sb3IgPSBjb3VudHJ5LCBzaXplID0gcG9wKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZmFjZXRfdHJlbGxpc2NvcGUofiBjb3VudHJ5ICsgY29udGluZW50LCAKICAgICAgICAgICAgICAgICAgICBucm93ID0gMiwgbmNvbCA9IDMpIAoKZ2FwbV90cmVsCmBgYAoKVHJ5IGVjaGFydHMuCgpgYGB7cn0KdGl0bGVzIDwtIG1hcCh1bmlxdWUoZ2FwbWluZGVyJHllYXIpLCBmdW5jdGlvbih4KXsKICBsaXN0KAogICAgdGV4dCA9ICJHYXBtaW5kZXIiLAogICAgc3VidGV4dCA9IHgsCiAgICBsZWZ0ID0gImNlbnRlciIKICApCn0pCgpnYXBtaW5kZXIgJT4lIAogIGdyb3VwX2J5KHllYXIpICU+JSAKICBlX2NoYXJ0cyhnZHBQZXJjYXAsIHRpbWVsaW5lID0gVFJVRSkgJT4lIAogIGVfc2NhdHRlcihsaWZlRXhwLCBwb3AsIGNvdW50cnkpICU+JSAKICBlX3lfYXhpcyhtaW4gPSAyMCwgbWF4ID0gODUpICU+JSAKICBlX3hfYXhpcyh0eXBlID0gImxvZyIsIG1pbiA9IDEwMCwgbWF4ID0gMTAwMDAwKSAlPiUgCiAgZV90aW1lbGluZV9zZXJpZSh0aXRsZSA9IHRpdGxlcykgJT4lIAogIGVfdG9vbHRpcCgpICU+JSAKICBlX3RpbWVsaW5lX29wdHMoCiAgICBzaG93ID0gVFJVRSwKICAgIG9yaWVudCA9ICJ2ZXJ0aWNhbCIsCiAgICBzeW1ib2wgPSAibm9uZSIsCiAgICByaWdodCA9IDAsCiAgICB0b3AgPSAyMCwKICAgIGJvdHRvbSA9IDIwLAogICAgaGVpZ2h0ID0gTlVMTCwKICAgIHdpZHRoID0gNDUsCiAgICBpbnZlcnNlID0gVFJVRSwKICAgIHBsYXlJbnRlcnZhbCA9IDEwMDAsCiAgICBhdXRvUGxheSAgPSBUUlVFLAogICAgY29udHJvbFN0eWxlID0gbGlzdCgKICAgICAgc2hvd05leHRCdG4gPSBGQUxTRSwKICAgICAgc2hvd1ByZXZCdG4gPSBGQUxTRQogICAgKSwKICAgIGxhYmVsID0gbGlzdCgKICAgICAgZm9udFNpemUgPSA4CiAgICApCiAgKSAlPiUgCiAgZV90aGVtZSgiZGFyayIpCmBgYAoKCgpgYGB7cn0KZ2FwbWluZGVyICU+JSBnZ3Bsb3QoYWVzKHggPSBnZHBQZXJjYXAsIHkgPSBsaWZlRXhwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSBwb3AsIGNvbG91ciA9IGNvdW50cnkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY291bnRyeV9jb2xvcnMpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygyLCAxMikpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIGZhY2V0X3dyYXAofiBjb250aW5lbnQpCmBgYAoKVHJ5IGdnYW5pbWF0ZS4KCmBgYHtyfQpnYXBtaW5kZXIgJT4lIGdncGxvdChhZXMoeCA9IGdkcFBlcmNhcCwgeSA9IGxpZmVFeHAsIAogICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IHBvcCwgY29sb3VyID0gY291bnRyeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC43LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjb3VudHJ5X2NvbG9ycykgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDIsIDEyKSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZmFjZXRfd3JhcCh+IGNvbnRpbmVudCkgKwogICMgSGVyZSBjb21lcyB0aGUgZ2dhbmltYXRlIHNwZWNpZmljIGJpdHMKICBsYWJzKHRpdGxlID0gJ1llYXI6IHtmcmFtZV90aW1lfScsIAogICAgICAgeCA9ICdHRFAgcGVyIGNhcGl0YScsIHkgPSAnbGlmZSBleHBlY3RhbmN5JykgKwogIHRyYW5zaXRpb25fdGltZSh5ZWFyKSArCiAgZWFzZV9hZXMoJ2N1YmljLWluLW91dCcpCmBgYAoKCgoKYGBge3IgZXZhbD1GQUxTRX0KZ29vIDwtIGdhcG1pbmRlciAlPiUgZ2dwbG90KGFlcyh4ID0gZ2RwUGVyY2FwLCB5ID0gbGlmZUV4cCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gcG9wLCBjb2xvdXIgPSBjb3VudHJ5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjcsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGNvdW50cnlfY29sb3JzKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMiwgMTIpKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBmYWNldF93cmFwKH5jb250aW5lbnQpICsKICAjIEhlcmUgY29tZXMgdGhlIGdnYW5pbWF0ZSBzcGVjaWZpYyBiaXRzCiAgbGFicyh0aXRsZSA9ICdZZWFyOiB7ZnJhbWVfdGltZX0nLCAKICAgICAgIHggPSAnR0RQIHBlciBjYXBpdGEnLCB5ID0gJ2xpZmUgZXhwZWN0YW5jeScpICsKICB0cmFuc2l0aW9uX3RpbWUoeWVhcikgKwogIGVhc2VfYWVzKCdjdWJpYy1pbi1vdXQnKQoKYW5pbV9zYXZlKCJnb28uZ2lmIiwgZ29vKQpgYGAKCiFbXShnb28uZ2lmKQoKVHJ5IGVjaGFydHMuCgpgYGB7cn0KYWlycG9ydHMgPC0gcmVhZC5jc3YoCiAgcGFzdGUwKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGxvdGx5L2RhdGFzZXRzLyIsCiAgICAgICAgICJtYXN0ZXIvMjAxMV9mZWJydWFyeV91c19haXJwb3J0X3RyYWZmaWMuY3N2IikKKQoKYWlycG9ydHMgJT4lIAogIGVfY2hhcnRzKGxvbmcpICU+JSAKICBlX2dsb2JlKAogICAgZW52aXJvbm1lbnQgPSBlYV9hc3NldCgic3RhcmZpZWxkIiksCiAgICBiYXNlX3RleHR1cmUgPSBlYV9hc3NldCgid29ybGQiKSwgCiAgICBnbG9iZU91dGVyUmFkaXVzID0gMTAwCiAgKSAlPiUgCiAgZV9zY2F0dGVyXzNkKGxhdCwgY250LCBjb29yZF9zeXN0ZW0gPSAiZ2xvYmUiLCBibGVuZE1vZGUgPSAnbGlnaHRlcicpICU+JSAKICBlX3Zpc3VhbF9tYXAoaW5SYW5nZSA9IGxpc3Qoc3ltYm9sU2l6ZSA9IGMoMSwgMTApKSkKYGBgCgoKYGBge3J9CiMgZG93bmxvYWQKZmxpZ2h0cyA8LSBqc29ubGl0ZTo6ZnJvbUpTT04oImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hcGFjaGUvaW5jdWJhdG9yLWVjaGFydHMvbWFzdGVyL3Rlc3QvZGF0YS9mbGlnaHQuanNvbiIpCgoKCiMgYWlycG9ydHMKYWlycG9ydHMgPC0gYXMuZGF0YS5mcmFtZShmbGlnaHRzJGFpcnBvcnRzKQpuYW1lcyhhaXJwb3J0cykgPC0gZmxpZ2h0cyRhaXJwb3J0c0ZpZWxkcwoKYWlycG9ydHMgPC0gYWlycG9ydHMgJT4lIAogIHNlbGVjdChuYW1lLCBsb25naXR1ZGUsIGxhdGl0dWRlKSAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIklEIikgJT4lIAogIG11dGF0ZShJRCA9IGFzLmludGVnZXIocGFzdGUwKElEKSkpCgojIHJvdXRlcwpyb3V0ZXMgPC0gYXMuZGF0YS5mcmFtZShmbGlnaHRzJHJvdXRlcykKbmFtZXMocm91dGVzKSA8LSBjKCJJRCIsICJmcm9tIiwgInRvIikKCiMgYWlybGluZXMKYWlybGluZXMgPC0gYXMuZGF0YS5mcmFtZShmbGlnaHRzJGFpcmxpbmVzKSAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIklEIikgJT4lIAogIG11dGF0ZShJRCA9IGFzLmludGVnZXIocGFzdGUoSUQpKSkgJT4lIAogIHNlbGVjdChJRCwgYWlybGluZSA9IFYxLCBjb3VudHJ5ID0gVjIpICU+JQogIGZpbHRlcihjb3VudHJ5ID09ICJVbml0ZWQgU3RhdGVzIikKCiMgYmluZApkYXRhIDwtIHJvdXRlcyAlPiUgCiAgaW5uZXJfam9pbihhaXJwb3J0cywgYnkgPSBjKCJmcm9tIiA9ICJJRCIpKSAlPiUgCiAgaW5uZXJfam9pbihhaXJwb3J0cywgYnkgPSBjKCJ0byIgPSAiSUQiKSwgc3VmZml4ID0gYygiLnN0YXJ0IiwgIi5lbmQiKSkgJT4lCiAgaW5uZXJfam9pbihhaXJsaW5lcywgYnkgPSAiSUQiKSAlPiUgCiAgc2VsZWN0KGFpcmxpbmUsIGxvbmdpdHVkZS5zdGFydCwgbGF0aXR1ZGUuc3RhcnQsIGxvbmdpdHVkZS5lbmQsIGxhdGl0dWRlLmVuZCkgCgojIGluaXRpYWxpc2UgcGxvdCAgCmRhdGEgJT4lCiAgZ3JvdXBfYnkoYWlybGluZSkgJT4lIAogIGVfY2hhcnRzKCkgJT4lIAogIGVfZ2xvYmUoCiAgICBiYXNlX3RleHR1cmUgPSBlYV9hc3NldCgid29ybGQgZGFyayIpLAogICAgZW52aXJvbm1lbnQgPSBlYV9hc3NldCgic3RhcmZpZWxkIiksCiAgICBkaXNwbGFjZW1lbnRTY2FsZSA9IDAuMSwKICAgIGRpc3BsYWNlbWVudFF1YWxpdHkgPSAiaGlnaCIsCiAgICBzaGFkaW5nID0gInJlYWxpc3RpYyIsCiAgICByZWFsaXN0aWNNYXRlcmlhbCA9IGxpc3QoCiAgICAgIHJvdWdobmVzcyA9IC4yLAogICAgICBtZXRhbG5lc3MgPSAwCiAgICApLAogICAgcG9zdEVmZmVjdCA9IGxpc3QoCiAgICAgIGVuYWJsZSA9IFRSVUUsCiAgICAgIGRlcHRoT2ZGaWVsZCA9IGxpc3QoCiAgICAgICAgZW5hYmxlID0gRkFMU0UKICAgICAgKQogICAgKSwKICAgIHRlbXBvcmFsU3VwZXJTYW1wbGluZyA9IGxpc3QoCiAgICAgIGVuYWJsZSA9IFRSVUUKICAgICksCiAgICBsaWdodCA9IGxpc3QoCiAgICAgIGFtYmllbnQgPSBsaXN0KAogICAgICAgIGludGVuc2l0eSA9IDEKICAgICAgKSwKICAgICAgbWFpbiA9IGxpc3QoCiAgICAgICAgaW50ZW5zaXR5ID0gLjEsCiAgICAgICAgc2hhZG93ID0gRkFMU0UKICAgICAgKQogICAgKSwKICAgIHZpZXdDb250cm9sID0gbGlzdChhdXRvUm90YXRlID0gRkFMU0UpCiAgKSAlPiUgCiAgZV9sZWdlbmQoCiAgICBzZWxlY3RlZE1vZGUgPSAic2luZ2xlIiwgCiAgICBsZWZ0ID0gImxlZnQiLAogICAgdGV4dFN0eWxlID0gbGlzdChjb2xvciA9ICIjZmZmIiksCiAgICBvcmllbnQgPSAidmVydGljYWwiCiAgKSAlPiUgCiAgZV9saW5lc18zZCgKICAgIGxvbmdpdHVkZS5zdGFydCwgbGF0aXR1ZGUuc3RhcnQsIGxvbmdpdHVkZS5lbmQsIGxhdGl0dWRlLmVuZCwgCiAgICBjb29yZF9zeXN0ZW0gPSAiZ2xvYmUiLCAKICAgIGVmZmVjdCA9IGxpc3QoCiAgICAgIHNob3cgPSBUUlVFLAogICAgICB0cmFpbFdpZHRoID0gMiwKICAgICAgdHJhaWxMZW5ndGggPSAwLjE1LAogICAgICB0cmFpbE9wYWNpdHkgPSAxLAogICAgICB0cmFpbENvbG9yID0gJ3JnYigzMCwgMzAsIDYwKScKICAgICksCiAgICBsaW5lU3R5bGUgPSBsaXN0KG9wYWNpdHkgPSAwLjEsIHdpZGggPSAwLjUsIGNvbG9yID0gJ3JnYig1MCwgNTAsIDE1MCknKQogICkKCmBgYAoK