In this post, I will show how to make an animated choropleth map where we want to show GDP growth (annual %) from 1961-2021. The chart shows major events or crisis i.e. Asian Financial Crisis (1997-1998), Global Financial Crisis (2008-2009) and Covid-19 Crisis (2019-2020), etc.
Next, we correct country codes (ISO_A3) of world_sf object, remove goemtry and save it to world_df.
world_df <- world_sf |>st_drop_geometry() |>mutate(ISO_A3 =case_when( NAME =="France"~"FRA", NAME =="Norway"~"NOR", NAME =="Kosovo"~"XKX",TRUE~ ISO_A3 ) )
Lastly, we add country names used by map geojson, so when we create a map we can use country name to link between data and map. We do this by joining df_country with world_df using iso3c column from df_country table and ISO_A3 column from world_df table.
We initialize an echart with timeline using echarts4r and group_by and assign to p variable. This will create an empty canvas with time slider. Note that we map NAME to country (we will refer to NAME when create a map).
p <- df_country |>group_by(year) |>e_charts(NAME, timeline =TRUE)p
Step 3: Make a choropleth map
Now, we make a choropleth map using e_map where we map gdp_growth to color intensity. We use custom geojson map, we need to register the map with e_map_register. Note that nameProperty is important argument because it is a key to link between echarts and geojson object. aspectScale is set to 1 for better map scaling.
p <- p |>e_map_register("World", world_json) |># register geojson for echart mape_map( gdp_growth,map ="World", # refer to registered mapnameProperty ="NAME", # properties for mapaspectScale =1,roam =TRUE, # enable zoomemphasis =list(focus ="self"),select =list(disabled =TRUE) ) |>e_grid(left ="0%", right ="0%", bottom ="5%")p
Step 4: Visual map color to GDP Growth
As gdp_growth can be negative and positive, so we use diverging colors scheme. I use red as negative growth and blue for positive growth. First, I use e_visual_map but I found that it automatically compute min and max for us. So, it is very difficult for implementing diverging color scheme. As a result, I create a modified function called e_visual_map2_ where it basically turn off min and max auto-calculation.
e_visual_map2_ where _ means that variables used in the function call must be a character.
Apply e_visual_map2_
p <- p |>e_visual_map2_("gdp_growth",min =-20,max =20,calculable =TRUE, # use visual map for filtering data realtime =TRUE, # hovered country shown in visual mapleft ="10%", # locationbottom ='20%', # locationinRange =list(color =list("#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0" ) ) )p
Step 5: Customize time slider and animation
We can customize behavior and apperance of time slider using e_timeline_opts function and we adjust animation effect using e_animation function.
p <- p |>e_timeline_opts(axisType ="category",autoPlay =FALSE,orient ="horizontal",playInterval =500,symbolSize =8,left ="center",width ="90%",loop =FALSE ) |>e_animation(duration.update =500,easing.update ="linear" )p
Step 6: Add tooltip
We add information popup when we hover on a map. We can add tooltip information using e_tooltip together with JS function from htmlwidgets package.
We will annotate each time frame with information about year of the data.
We add toolbox for saving image and restore (reset zoom effect) using e_toolbox_feature.
Again, We follow the approach from my previous post using custom e_title_timeline function to assign title for each time frame. We create three lists including
main title
year
e_title_timeline <-function(e, title) {# loop over group_by datafor (i in1:length(e$x$opts$options)) {# append original title with new title e$x$opts$options[[i]][["title"]] <-append( e$x$opts$options[[i]][["title"]], title[i] ) } e}# create main titletitle_main <-map(as.character(df_country$year) |>unique(),function(x) {list(text =paste0("GDP Growth (annual %)"),subtext ="(Data Source: World Bank)",left ="center",top ="5%",textStyle =list(fontSize =20) ) })# create time title for annotationtitle_year <-map(as.character(df_country$year) |>unique(),function(x) {list(text = x,right ="10%",top ="15%",textStyle =list(fontSize =32) ) })
Add titile and toolbox to the chart.
p <- p |>e_title_timeline(title = title_main) |>e_title_timeline(title = title_year) |>e_toolbox_feature(feature =c("saveAsImage", "restore"))p
Put it all together
# load libraries library(WDI) # gapminder datalibrary(countrycode) # for mapping continentlibrary(echarts4r) # make echarts using Rlibrary(sf) # handle map objectlibrary(jsonlite)library(dplyr, warn.conflicts =FALSE) # data manipulationlibrary(tidyr, warn.conflicts =FALSE) # handling nalibrary(purrr, warn.conflicts =FALSE) # functional programminglibrary(listviewer) # view nested listlibrary(jsonlite) # read json file# data preparationdf <-WDI(indicator ="NY.GDP.MKTP.KD.ZG", extra =TRUE) |># GDP growthrename(gdp_growth = NY.GDP.MKTP.KD.ZG) |>as_tibble() |>arrange(year)df_country <- df |>filter(region !="Aggregates") |>drop_na(gdp_growth) |>left_join(codelist[, c("iso2c", "country.name.en")], by ="iso2c")url <-"https://raw.githubusercontent.com/piyayut-ch/mapthai/master/data-raw/geojson/world.geojson"world_json <-read_json(url)world_json[["features"]][[173]] <-NULLworld_sf <-read_sf(url)world_df <- world_sf |>st_drop_geometry() |>mutate(ISO_A3 =case_when( NAME =="France"~"FRA", NAME =="Norway"~"NOR", NAME =="Kosovo"~"XKX",TRUE~ ISO_A3 ) )df_country <- df_country |>select(iso3c, country, gdp_growth, year) |>left_join( world_df |>select(NAME, ISO_A3),by =c("iso3c"="ISO_A3") )# define helper functionse_visual_map2_ <-function( e, serie =NULL, min, max, calculable =TRUE,type =c("continuous", "piecewise"), scale =NULL, ...){if (missing(e)) {stop("must pass e", call. =FALSE) }if (!length(e$x$opts$visualMap)) { e$x$opts$visualMap <-list() } vm <-list(...) vm$calculable <- calculable vm$type <- type[1] vm$min <- min vm$max <- maxif (!e$x$tl) { e$x$opts$visualMap <-append(e$x$opts$visualMap, list(vm)) }else { e$x$opts$baseOption$visualMap <-append(e$x$opts$baseOption$visualMap,list(vm)) } e}e_title_timeline <-function(e, title) {# loop over group_by datafor (i in1:length(e$x$opts$options)) {# append original title with new title e$x$opts$options[[i]][["title"]] <-append( e$x$opts$options[[i]][["title"]], title[i] ) } e}# create main titletitle_main <-map(as.character(df_country$year) |>unique(),function(x) {list(text =paste0("GDP Growth (annual %)"),subtext ="(Data Source: World Bank)",left ="center",top ="5%",textStyle =list(fontSize =20) ) })# create time title for annotationtitle_year <-map(as.character(df_country$year) |>unique(),function(x) {list(text = x,right ="10%",top ="15%",textStyle =list(fontSize =32) ) })# make a chartp <- df_country |>group_by(year) |>e_charts(NAME, timeline =TRUE) |>e_map_register("World", world_json) |># register geojson for echart mape_map( gdp_growth,map ="World", # refer to registered mapnameProperty ="NAME", # properties for mapaspectScale =1,roam =TRUE, # enable zoomemphasis =list(focus ="self"),select =list(disabled =TRUE) ) |>e_grid(left ="0%", right ="0%", bottom ="5%") |>e_visual_map2_("gdp_growth",min =-20,max =20,calculable =TRUE, # use visual map for filtering data realtime =TRUE, # hovered country shown in visual mapleft ="10%", # locationbottom ='20%', # locationinRange =list(color =list("#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0" ) ) )e_timeline_opts(axisType ="category",autoPlay =FALSE,orient ="horizontal",playInterval =500,symbolSize =8,left ="center",width ="90%",loop =FALSE ) |>e_animation(duration.update =500,easing.update ="linear" ) |>e_tooltip(trigger ="item",formatter = htmlwidgets::JS(" function(params){ return( '<strong>' + params.name + '</strong><br />' + 'GDP growth: ' + params.value.toLocaleString( 'en-US', {maximumFractionDigits: 2}) + '%' ) } ") ) |>e_title_timeline(title = title_main) |>e_title_timeline(title = title_year) |>e_toolbox_feature(feature =c("saveAsImage", "restore"))