Thematic Interactive Map

[This article was first published on Jkunst - R category, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Last week all the rstatsosphere see the beautiful
Timo Grossenbacher
work.

The last month, yep, the past year I’ve working on create maps
easily with highcharter.
When I saw this chart I took as challege to replicate this nice
map in using highcharter:

So…

giphy gif source

Challenge accepted. it’s gonna be legen…
Me.

<span class="c1"># packages ----------------------------------------------------------------
</span><span class="n">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">rmapshaper</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">maptools</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">highcharter</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">geojsonio</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">readr</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">viridis</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span><span class="w">
</span>

Data

The data used fhor this chart is the same using by Timo. But the workflow
was slightly modified:

  1. Read the shapefile with maptools::readShapeSpatial.
  2. Simplify the shapefile (optional step) using rmapshaper::ms_simplify.
  3. Then transform the map data to geojson using geojsonio::geojson_list.
<span class="c1"># data --------------------------------------------------------------------
</span><span class="n">folder</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="s2">"data/thematicmap/"</span><span class="w">

</span><span class="n">map</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">readShapeSpatial</span><span class="p">(</span><span class="n">file.path</span><span class="p">(</span><span class="n">folder</span><span class="p">,</span><span class="w"> </span><span class="s2">"gde-1-1-15.shp"</span><span class="p">))</span><span class="w">
</span><span class="n">map</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">ms_simplify</span><span class="p">(</span><span class="n">map</span><span class="p">,</span><span class="w"> </span><span class="n">keep</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w"> </span><span class="c1"># because ms_simplify fix the ü's
</span><span class="n">map</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">geojson_list</span><span class="p">(</span><span class="n">map</span><span class="p">)</span><span class="w">

</span><span class="n">str</span><span class="p">(</span><span class="n">map</span><span class="p">,</span><span class="w"> </span><span class="n">max.level</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
## List of 2
##  $ type    : chr "FeatureCollection"
##  $ features:List of 2324
##   .. [list output truncated]
##  - attr(*, "class")= chr "geo_list"
##  - attr(*, "from")= chr "SpatialPolygonsDataFrame"
<span class="n">str</span><span class="p">(</span><span class="n">map</span><span class="o">$</span><span class="n">features</span><span class="p">[[</span><span class="m">7</span><span class="p">]],</span><span class="w"> </span><span class="n">max.level</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">5</span><span class="p">)</span><span class="w">
</span>
## List of 4
##  $ type      : chr "Feature"
##  $ id        : int 6
##  $ properties:List of 3
##   ..$ Secondary_  : chr "Knonau"
##   ..$ BFS_ID      : int 7
##   ..$ rmapshaperid: int 6
##  $ geometry  :List of 2
##   ..$ type       : chr "MultiPolygon"
##   ..$ coordinates:List of 1
##   .. ..$ :List of 1
##   .. .. ..$ :List of 7
##   .. .. .. ..$ : num [1:2] 676327 230767
##   .. .. .. ..$ : num [1:2] 675655 233007
##   .. .. .. ..$ : num [1:2] 676458 233252
##   .. .. .. ..$ : num [1:2] 679285 230257
##   .. .. .. ..$ : num [1:2] 679590 229402
##   .. .. .. ..$ : num [1:2] 678887 229152
##   .. .. .. ..$ : num [1:2] 676327 230767
<span class="c1"># this was to put the name on the tooltip
</span><span class="n">map</span><span class="o">$</span><span class="n">features</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">map</span><span class="p">(</span><span class="n">map</span><span class="o">$</span><span class="n">features</span><span class="p">,</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">x</span><span class="o">$</span><span class="n">properties</span><span class="o">$</span><span class="n">name</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">x</span><span class="o">$</span><span class="n">properties</span><span class="p">[[</span><span class="s2">"Secondary_"</span><span class="p">]]</span><span class="w"> 
  </span><span class="n">x</span><span class="w">
</span><span class="p">})</span><span class="w">

</span><span class="n">data</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">read_csv</span><span class="p">(</span><span class="n">file.path</span><span class="p">(</span><span class="n">folder</span><span class="p">,</span><span class="w"> </span><span class="s2">"avg_age_15.csv"</span><span class="p">))</span><span class="w">
</span><span class="n">data</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">select</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="n">X</span><span class="m">1</span><span class="p">)</span><span class="w">
</span><span class="n">data</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">rename</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">avg_age_15</span><span class="p">)</span><span class="w">

</span><span class="c1"># colors
</span><span class="n">no_classes</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="m">6</span><span class="w">

</span><span class="n">colors</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">magma</span><span class="p">(</span><span class="n">no_classes</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="m">2</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">rev</span><span class="p">()</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">head</span><span class="p">(</span><span class="m">-1</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">tail</span><span class="p">(</span><span class="m">-1</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">gsub</span><span class="p">(</span><span class="s2">"FF$"</span><span class="p">,</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="n">.</span><span class="p">)</span><span class="w">

</span><span class="c1"># brks <- quantile(data$value, probs = seq(0, 1, length.out = no_classes + 1))
</span><span class="n">brks</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">value</span><span class="p">),</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">40</span><span class="p">,</span><span class="m">42</span><span class="p">,</span><span class="m">44</span><span class="p">,</span><span class="m">46</span><span class="p">,</span><span class="m">48</span><span class="p">),</span><span class="w"> </span><span class="nf">max</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">value</span><span class="p">))</span><span class="w">
</span><span class="n">brks</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">ifelse</span><span class="p">(</span><span class="m">1</span><span class="o">:</span><span class="p">(</span><span class="n">no_classes</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">no_classes</span><span class="p">,</span><span class="w"> </span><span class="nf">floor</span><span class="p">(</span><span class="n">brks</span><span class="p">),</span><span class="w"> </span><span class="nf">ceiling</span><span class="p">(</span><span class="n">brks</span><span class="p">))</span><span class="w">
</span>

Map

Create the raw map is straightforward. The main challege was replicate the
relief feature of the orignal map. This took some days to figure
how add the backgound image. I almost loose the hope but you know, new year,
so I tried a little more and it was possible :):

  1. First I searched a way to transform the tif image to geojson. I wrote
    a mail @frzambra a geoRexpert :D. and
    he kindly said me that I was wrong. And he was right. NEXT!
  2. I tried with use divBackgroundImage but with this the image use all the
    container… so… NEXT.
  3. Finally surfing in the web I met plotBackgroundImage argument in highcharts
    which is uesd to put and image only in plot container (inside de axis) and
    it works nicely. It was necessary hack the image using the preserveAspectRatio
    (html world) to center the image but nothing magical.
<span class="n">urlimage</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="s2">"https://raw.githubusercontent.com/jbkunst/r-posts/master/061-beautiful-thematic-maps-with-ggplot2-highcharter-version/02-relief-georef-clipped-resampled.jpg"</span><span class="w">

</span><span class="n">highchart</span><span class="p">(</span><span class="n">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"map"</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="c1"># data part
</span><span class="w">  </span><span class="n">hc_add_series</span><span class="p">(</span><span class="n">mapData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">map</span><span class="p">,</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"map"</span><span class="p">,</span><span class="w">
                </span><span class="n">joinBy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="s2">"BFS_ID"</span><span class="p">,</span><span class="w"> </span><span class="s2">"bfs_id"</span><span class="p">),</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"value"</span><span class="p">,</span><span class="w">
                </span><span class="n">borderWidth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">hc_colorAxis</span><span class="p">(</span><span class="n">dataClasses</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">color_classes</span><span class="p">(</span><span class="n">brks</span><span class="p">,</span><span class="w"> </span><span class="n">colors</span><span class="p">))</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="c1"># functionality
</span><span class="w">  </span><span class="n">hc_tooltip</span><span class="p">(</span><span class="n">headerFormat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
             </span><span class="n">pointFormat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"{point.name}: {point.value}"</span><span class="p">,</span><span class="w">
             </span><span class="n">valueDecimals</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">2</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">hc_legend</span><span class="p">(</span><span class="n">align</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"right"</span><span class="p">,</span><span class="w"> </span><span class="n">verticalAlign</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bottom"</span><span class="p">,</span><span class="w"> </span><span class="n">layout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"vertical"</span><span class="p">,</span><span class="w">
            </span><span class="n">floating</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">hc_mapNavigation</span><span class="p">(</span><span class="n">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">FALSE</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> </span><span class="c1"># if TRUE to zoom the relief image dont zoom.
</span><span class="w">  </span><span class="c1"># info
</span><span class="w">  </span><span class="n">hc_title</span><span class="p">(</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Switzerland's regional demographics"</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">hc_subtitle</span><span class="p">(</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Average age in Swiss municipalities, 2015"</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">hc_credits</span><span class="p">(</span><span class="n">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w">
             </span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Map CC-BY-SA; Author: Joshua Kunst (@jbkunst) based mostly on Timo Grossenbacher (@grssnbchr) work, Geometries: ThemaKart, BFS; Data: BFS, 2016; Relief: swisstopo, 2016"</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="c1"># style
</span><span class="w">  </span><span class="n">hc_chart</span><span class="p">(</span><span class="n">plotBackgroundImage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">urlimage</span><span class="p">,</span><span class="w">
           </span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"transparent"</span><span class="p">,</span><span class="w">
           </span><span class="n">events</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="w">
             </span><span class="n">load</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">JS</span><span class="p">(</span><span class="s2">"function(){ $(\"image\")[0].setAttribute('preserveAspectRatio', 'xMidYMid') }"</span><span class="p">)</span><span class="w">
           </span><span class="p">))</span><span class="w">
</span>

open

DARY! Legendary.
Me.

Same as the original/ggplot2 version. I’m very happy with the result.
But anyway, there are some details:

  1. The image/relief need to be accesible in web. I don’t know how
    to add images as dependencies yet. I tried econding the image but didn’t work.
  2. I could not do the legend same as the original. So I used dataClasses
    instead of stops in hc_colorAxis.

To leave a comment for the author, please follow the link and comment on their blog: Jkunst - R category.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)