Interactive map - pan, zoom, slider filter and tooltips are enabled.
Latest measurement of Internet speed around the world with ranking by country.
Live data from speedtest.net. Inspiration from visualcapitalist.
#' rvest + echarty demo
library(dplyr); library(rvest); library(xml2); library(echarty)
wp <- read_html('https://www.speedtest.net/global-index')
asof <- wp |> html_nodes('.month') |> head(1) |> xml_text()
e <- rng <- list(); etype <- c('Mobile','Cable'); erank <- c('rkmob','rkcab')
for(i in 1:2) {
wt <- wp |> html_nodes('.country-results') |> magrittr::extract2(i+2) |>
html_table(header=TRUE) |> na.omit()
wt <- wt[,-2]
names(wt) <- c(erank[i],'country',etype[i])
e[[i]] <- wt
rng[[i]] <- c(min(wt[etype[i]]), max(wt[etype[i]]))
}
tmp <- full_join(e[[1]], e[[2]])
for(cry in c('Dem. Rep. Korea','Iceland','Greenland','Niger','Chad','Guinea','Central African Rep.','S. Sudan'))
tmp <- tmp |> rbind(c('?',cry,0,'?',0)) # from world.js add some missing ones
tmp <- tmp |> mutate(country = case_when( # change mismatching
country=='Dominican Republic' ~'Dominican Rep.',
country=='Laos' ~'Lao PDR',
country=='South Korea' ~'Korea',
country=='Republic of the Union of Myanmar' ~'Myanmar',
country=='Western Sahara' ~'W. Sahara',
country=='Equatorial Guinea' ~'Eq. Guinea',
country=='DR Congo' ~'Dem. Rep. Congo',
country=='Czechia' ~'Czech Rep.',
country=='North Macedonia' ~'Macedonia',
country=='Bosnia and Herzegovina' ~'Bosnia and Herz.',
TRUE ~ country)) |>
mutate(rkmob = ifelse(is.na(rkmob), '?', rkmob)) |>
mutate(Mobile = ifelse(is.na(Mobile), 0, Mobile)) |>
mutate(Mobile = as.numeric(unname(Mobile)), Cable = as.numeric(unname(Cable)))
for(i in 1:2) {
p <- ec.init(load='world')
p$x$opts$title <- list(show=TRUE, text=paste('Global',etype[i],'Speed'), subtext=paste('as of',asof))
p$x$opts$backgroundColor <- 'LightSteelBlue'
p$x$opts$geo$zoom <- 1.5
p$x$opts$series[[1]]$data <- lapply(ec.data(tmp, 'names'), function(d)
list(name=d$country, value=unlist(unname(d[etype[i]])), rank=unlist(unname(d[erank[i]]))) )
p$x$opts$visualMap <- list(type='continuous', calculable=TRUE, text=c('Mbps',''),
range=rng[[i]], min=0, max=max(tmp[etype[i]]),
inRange= list(color=c('darkviolet', 'orangered', 'yellow')),
outOfRange= list(color='Grey') )
p$x$opts$tooltip <- list(
backgroundColor= 'rgba(30,30,30,0.5)', textStyle= list(color='#eee'),
formatter=ec.clmn('%@<br /><b>%@</b> Mbps, rank <b>%@</b>','name','value','rank') )
p$x$opts$toolbox <- list(feature= list(saveAsImage= list(show= TRUE),
dataView= list(readOnly= TRUE)) )
p$x$opts$name <- etype[i] # for radio-button selection
e[[i]] <- p
}
e[[1]]$x$o2 <- e[[2]]$x$opts # save 2nd plot
e[[1]] # show 1st
Bypassing Shiny Server again. Here is the JavaScript code
var chopt = {p1:null, p2:null};
document.addEventListener("DOMContentLoaded", function() {
const pp = getData();
chopt.p1 = pp.x.opts;
chopt.p2 = pp.x.o2;
// widgets do Eval just once on init, so we need to do it here 'manually'
chopt.p1.tooltip.formatter = chopt.p2.tooltip.formatter = eval('('+chopt.p1.tooltip.formatter+')');
});
function getData() {
try {
// find the htmlwidget
const inlineJsonElement = document.querySelector(
`script[type="application/json"][data-for^=htmlwidget-]`
);
const data = JSON.parse(inlineJsonElement.textContent);
return data;
} catch (err) {
console.error(`Couldn't read JSON data from htmlwidget`, err);
}
}
function rlick(myRadio) {
let selectedValue = myRadio.value;
let wt = document.querySelector("[id^=htmlwidget-]");
// get the ECharts object and replace its options
let p = get_e_charts(wt.id);
let oprev = p.getOption();
let opti = null;
switch(selectedValue) {
case chopt.p1.name:
opti = chopt.p1;
break;
case chopt.p2.name:
opti = chopt.p2;
break;
default:
alert('selection not found');
}
if (opti!=null) {
// preserve user's zoom/center values
opti.geo.zoom = oprev.geo[0].zoom;
opti.geo.center = oprev.geo[0].center;
p.setOption(opti, true);
}
};