Search code examples
rggplot2gridextrar-grid

How to plot barchart onto ggplot2 map


I have found similar answers to questions like this one, but most of them are using packages rworldmap, ggmap, ggsubplot or geom_subplot2d. See for example here or here.

I'd like to know how I can plot other ggplot-objects such as a bar-chart onto a map, that is created from a shapefile. The one I'm using can be downloaded here.

EDIT

As @beetroot correctly pointed out, the new file which can be downloaded under the link posted above has changed significantly. Therefore the names of the shapefile etc. are adjusted.

library(rgdal)
library(ggplot2)
library(rgeos)
library(maptools)

map.det<- readOGR(dsn="<path to your directory>/swissBOUNDARIES3D100216/swissBOUNDARIES3D/V200/SHAPEFILE_LV03", layer="VECTOR200_KANTONSGEBIET")
map.kt <- map.det[map.det@data$KANTONSNUM=="CH01000000"|map.det@data$KANTONSNUM=="CH19000000",]


#get centroids
map.test.centroids <- gCentroid(map.kt, byid=T)
map.test.centroids <- as.data.frame(map.test.centroids)
map.test.centroids$KANTONSNR <- row.names(map.test.centroids)

#create df for ggplot
kt_geom <- fortify(map.kt, region="KANTONSNUM")

#Plot map
map.test <- ggplot(NULL)+
        geom_polygon(data=kt_geom, aes(long, lat, group=group), fill="white")+
        coord_fixed()+
        geom_path(data=kt_geom, color="gray48", mapping=aes(long, lat, group=group), size=0.2)+
        geom_point(data=map.test.centroids, aes(x=x, y=y), size=9, alpha=6/10)

mapp

This results in such a map. So far so good. enter image description here

However, I'm having difficulties combining two plots such as the map map.test and, for example, this one:

geo_data <- data.frame(who=rep(c(1:2), each=2),
                   value=as.numeric(sample(1:100, 4, replace=T)),
                   KANTONSNR=rep(c(1,19), 2))

bar.testplot <- ggplot()+
     geom_bar(data=geo_data, aes(factor(id),value,group=who),position='dodge',stat='identity')

The barcharts should lie at the center of the two polygons, i.e. where the two points are. I could produce the barcharts and plot them onto the map separately, if that makes things easier.


Solution

  • I've modified a little your code to make the example more illustrative. I'm plotting not only 2 kantons, but 47.

    library(rgdal)
    library(ggplot2)
    library(rgeos)
    library(maptools)
    library(grid)
    library(gridExtra)
    
    map.det<- readOGR(dsn="c:/swissBOUNDARIES3D/V200/SHAPEFILE_LV03", layer="VECTOR200_KANTONSGEBIET")
    map.kt <- map.det[map.det$ICC=="CH" & (map.det$OBJECTID %in% c(1:73)),]
    
    # Merge polygons by ID
    map.test <- unionSpatialPolygons(map.kt, map.kt@data$OBJECTID)
    
    #get centroids
    map.test.centroids <- gCentroid(map.test, byid=T)
    map.test.centroids <- as.data.frame(map.test.centroids)
    map.test.centroids$OBJECTID <- row.names(map.test.centroids)
    
    #create df for ggplot
    kt_geom <- fortify(map.kt, region="OBJECTID")
    
    #Plot map
    map.test <- ggplot(kt_geom)+
      geom_polygon(aes(long, lat, group=group), fill="white")+
      coord_fixed()+
      geom_path(color="gray48", mapping=aes(long, lat, group=group), size=0.2)+
      geom_point(data=map.test.centroids, aes(x=x, y=y), size=2, alpha=6/10)
    
    map.test
    

    initial plot

    Let's generate data for barplots.

    set.seed(1)
    geo_data <- data.frame(who=rep(c(1:length(map.kt$OBJECTID)), each=2),
                           value=as.numeric(sample(1:100, length(map.kt$OBJECTID)*2, replace=T)),
                           id=rep(c(1:length(map.kt$OBJECTID)), 2))
    

    Now making 47 barplots which should be plotted at center-points later.

    bar.testplot_list <- 
      lapply(1:length(map.kt$OBJECTID), function(i) { 
        gt_plot <- ggplotGrob(
          ggplot(geo_data[geo_data$id == i,])+
            geom_bar(aes(factor(id),value,group=who), fill = rainbow(length(map.kt$OBJECTID))[i],
                     position='dodge',stat='identity', color = "black") +
            labs(x = NULL, y = NULL) + 
            theme(legend.position = "none", rect = element_blank(),
                  line = element_blank(), text = element_blank()) 
        )
        panel_coords <- gt_plot$layout[gt_plot$layout$name == "panel",]
        gt_plot[panel_coords$t:panel_coords$b, panel_coords$l:panel_coords$r]
        })
    

    Here we convert ggplots into gtables and then crop them to have only panels of each barplot. You may modify this code to keep scales, add legend, title etc.

    We can add this barplots to the initial map with the help of annotation_custom.

    bar_annotation_list <- lapply(1:length(map.kt$OBJECTID), function(i) 
      annotation_custom(bar.testplot_list[[i]], 
                        xmin = map.test.centroids$x[map.test.centroids$OBJECTID == as.character(map.kt$OBJECTID[i])] - 5e3,
                        xmax = map.test.centroids$x[map.test.centroids$OBJECTID == as.character(map.kt$OBJECTID[i])] + 5e3,
                        ymin = map.test.centroids$y[map.test.centroids$OBJECTID == as.character(map.kt$OBJECTID[i])] - 5e3,
                        ymax = map.test.centroids$y[map.test.centroids$OBJECTID == as.character(map.kt$OBJECTID[i])] + 5e3) )
    
    result_plot <- Reduce(`+`, bar_annotation_list, map.test)
    

    enter image description here