In "R" during plotting "tmap" library places labels outside plotting region (partially) - r

I am importing shapefile into R and try to plot it with labels. Unfortunately some labels overlap. That's why I have to use parameter "auto.placement = T" for the "tm_text" function. But this parameter places some labels outside plotting region (partially). Position of labels on every plot is random. Sometimes labels are withing plotting region, but most of the times are not (cutted).
As you can see on screenshot "Palangos m." is cutted down to "angos m." and "Klaipedos m." is cutted down to "aipedos m.".
Screenshot: map
tm_shape(area_r1) +
tm_fill("winner", title = "Winner", style = "cat",
palette = c("#FFFFB3", "#1F78B4", "#1A9850", "#E7298A") ) +
tm_legend(text.size = 0.75) +
tm_layout("", legend.position = c("left", "bottom")) +
tm_borders("grey60") +
tm_layout(frame = F) +
tm_text("savivald", size = .65, col = "black", auto.placement = T)
What can I do in order to fit this labels into plotting region?

Controlling the randomness of auto.placement = T is difficult (though setting seed may help).
What you can do is adjusting the bounding box of your tmap object oh so slightly, so that there is more room on the left for the two or so missing letters.
Increasing the bbox by a half is probably an exaggeration, but you can tune it as required.
bbox_new <- st_bbox(area_r1) # current bounding box
xrange <- bbox_new$xmax - bbox_new$xmin # range of x values
yrange <- bbox_new$ymax - bbox_new$ymin # range of y values
bbox_new[1] <- bbox_new[1] - (0.5 * xrange) # xmin - left
# bbox_new[3] <- bbox_new[3] + (0.5 * xrange) # xmax - right
# bbox_new[2] <- bbox_new[2] - (0.5 * yrange) # ymin - bottom
# bbox_new[4] <- bbox_new[4] + (0.5 * yrange) # ymax - top
bbox_new <- bbox_new %>% # take the bounding box ...
st_as_sfc() # ... and make it a sf polygon
tm_shape(area_r1, bbox = bbox_new) +
tm_fill("winner", title = "Winner", style = "cat",
palette = c("#FFFFB3", "#1F78B4", "#1A9850", "#E7298A") ) +
tm_legend(text.size = 0.75) +
tm_layout("", legend.position = c("left", "bottom")) +
tm_borders("grey60") +
tm_layout(frame = F) +
tm_text("savivald", size = .65, col = "black", auto.placement = T)
I wrote a blog post summarizing the technique a while back. https://www.jla-data.net/eng/adjusting-bounding-box-of-a-tmap-map/
Your example is not exactly reproducible, but this image (making more space for the "big fat, title" on North Carolina map) should give you idea.

#Jinda Lacko has a creative solution using bbox , but for some reason it did not work for me. An easy solution is to use main.title in tm_layout, for example tm_layout(main.title = "Big Fat Title"). See this question for more details.

Related

ggplotly display ggplot choropleth map ugly

I am trying to make my beautiful ggplot map interactive with a tooltip using ggplotly. But the map rendered with ggploty is not beautiful.
Here is a picture of my map with only ggplot:
Here is a picture of my map when using ggplotly. It removes the legend and make the map ugly:
Is there another way of making my ggplot map interactive with a tooltip? And also ggplotly takes some time to render the interactive map:
Here is my sample code for my ggplot:
ggplot(data = sdpf_f, aes( fill = n,x = long, y = lat, group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top", label.position = "bottom", keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_map()
Thanks & kind regards,
Akshay
I don't have your data and this isn't exactly the same, but it's fairly close to what I think you're expecting.
The libraries:
I called tidyverse for the plotting and piping. I called maps for the data I used and plotly for the Plotly graph.
I used a function that is derived from one of the ways ggplot sets the aspect ratio. I know I found this function on SO, but I don't remember who wrote it.
library(tidyverse)
library(maps)
library(plotly)
map_aspect = function(x, y) {
x.center <- sum(range(x)) / 2
y.center <- sum(range(y)) / 2
x.dist <- ggplot2:::dist_central_angle(x.center + c(-0.5, 0.5), rep(y.center, 2))
y.dist <- ggplot2:::dist_central_angle(rep(x.center, 2), y.center + c(-0.5, 0.5))
y.dist / x.dist
}
I had to create data as your question is not reproducible. I figured I would include it, so that my answer was reproducible.
ms <- map_data("state") %>%
mutate(n = ifelse(str_detect(region, "^a"), 1.0,
ifelse(str_detect(region, "^o"), 1.5,
ifelse(str_detect(region, "^t"), 2.0,
ifelse(str_detect(region, "^s"), 2.5,
ifelse(str_detect(region, "^w"),
3.0,
NA))))))
I modified your ggplot call. The only change is coord_fixed instead of coord_map. I did this so that Plotly could interpret the aspect ratio correctly. Within coord_fixed, I used the function map_aspect.
gp <- ggplot(data = ms, aes(fill = n, x = long, y = lat,
group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top",
label.position = "bottom",
keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),
na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_fixed(with(ms, map_aspect(long, lat)))
Then I created a Plotly object. I set some requirements for the layout, as well (horizontal legend at the bottom, with the legend title above the elements—similar to the legend in your ggplot call).
pp <- ggplotly(gp) %>%
layout(legend = list(orientation = "h", valign = "bottom",
title = list(side = "top"),
x = .02),
xaxis = list(showgrid = F), yaxis = list(showgrid = F),
showlegend = T)
Next, I needed to add the information for the legend. I chose to name the traces (which are split by color). I started by creating a vector of the names of the traces (which is what you see in the legend). I added a "" at the end, so that the NA-colored states wouldn't have a named trace (so they won't show in the legend).
This is likely something you'll need to change for your data.**
# the color values and the last one, which is for NAs, no legend
nm <- seq(1, 3, by = .5) %>% sprintf(fmt = "%.1f") %>% append(., "")
Then I wanted to ensure that showlegend was true for all but the NA (light gray) states. I created a vector of T and F.
legs <- c(rep(TRUE, 5), FALSE)
Then I added these to the plot.
invisible(lapply(1:length(pp$x$data),
function(i){
pp$x$data[[i]]$name <<- nm[i]
pp$x$data[[i]]$showlegend <<- legs[i]
}))

Non-rectangular legend in ggplot

How could I produce a legend that is not a rectangle with ggplot?
For example, how could I get round(ish) corners instead of these sharp corners?
Everybody knows them, but here is one example:
ggplot(baseball, aes(year, r)) +
geom_point(aes(col=g))
Would something similar to this: Rounded corners in ggplot2? maybe work?
The particular strategy mentioned in the linked post is likely going to fail because the colourbar isn't parameterised as a rectangle but rather as a raster. The alternative strategy then is to draw white, inverted quarter circles near the corners.
One way of doing that is to define your own class of colour bar, and add some code that draws these white corners. The code below should work for vertical colour bars.
library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.0.5
library(grid)
# Custom constructor, just tags on another class
my_round_colourbar <- function(...) {
guide <- guide_colourbar()
class(guide) <- c("my_round_colourbar", class(guide))
guide
}
# Customised drawing code that adds the corners
guide_gengrob.my_round_colourbar <- function(...) {
# Use the usual drawing method
grob <- NextMethod()
# Find the bar from the normal legend
is_bar <- grep("bar", grob$layout$name)[[1]]
bar <- grob$grobs[[is_bar]]
# Measure the bar
width <- convertUnit(bar$width, "cm", valueOnly = TRUE)
height <- convertUnit(bar$height, "cm", valueOnly = TRUE)
minor <- min(width, height)
# Draw 'caps': 2 inverted corner pieces
t <- seq(0, pi, length.out = 180)
cap <- data.frame(
x = c((cos(t) / 2), -0.5, 0.5) * minor,
y = c((-sin(t) / 2) + 0.5, 0, 0) * minor
)
# Draw a polygon for each cap
bottom <- polygonGrob(x = unit(cap$x, "cm") + bar$x,
y = unit(cap$y, "cm") + bar$y -
unit(height * 0.5, "cm"),
gp = gpar(fill = "white",col = NA),
default.units = "cm")
top <- polygonGrob(x = unit(cap$x, "cm") + bar$x,
y = unit(-cap$y, "cm") + bar$y + unit(height * 0.5, "cm"),
gp = gpar(fill = "white", col = NA),
default.units = "cm")
# Add polygon to bar
bar <- grobTree(bar, top, bottom)
grob$grobs[[is_bar]] <- bar
return(grob)
}
ggplot(mtcars, aes(wt, mpg)) +
geom_point(aes(colour = drat)) +
guides(colour = my_round_colourbar())
Created on 2021-07-05 by the reprex package (v1.0.0)

Why is my ggmap scale bar showing up off screen/not at all?

I'm trying to add a 20km scale bar to my map but every solution I've tried either adds the scale bar off screen (you can only see the bottom of the "km") or doesn't add it at all.
The closest I've come has been using scalebar(), which adds a scale bar off screen but doesn't allow me to move it to be fully visible. I've also tried making a bar from scratch with geom_line etc but that did not plot at all.
Here is a reproducible map without an attempt to make a scale bar and a small set of coordinates
library(ggmap)
library(ggsn)
wd <- getwd()
Latitude <- c(34.1365, 34.14435, 34.05111, 34.17605)
Longitude <- c(-117.92391, -117.85036, -118.31712, -118.31712)
graphingdata <- cbind.data.frame(Latitude,Longitude)
# compute the bounding box
bc_bbox <- make_bbox(lat = as.numeric(graphingdata$Latitude), lon = as.numeric(graphingdata$Longitude))
bc_bbox
# grab the map from google
site_map <- get_stamenmap(bc_bbox, zoom = 10, maptype = "terrain")
#create and save the map
png(file=paste0(wd,"stack-ex-graph.png"))
map <- ggmap(site_map, legend = "bottom") +
geom_point(data = graphingdata, aes(x = as.numeric(Longitude),
y = as.numeric(Latitude)), color = "red", size = 2) +
ggtitle(paste0("This is the map title"),
subtitle = paste0("This is the subtitle"))
print(map)
dev.off()
I ended up being able to use the anchor argument to shift the location of the scale bar. Because of the scope of the project I made the anchor and bounding of the map flexible.
#Create the data frame
Latitude <- c(34.1365, 34.14435, 34.05111, 34.17605)
Longitude <- c(-117.92391, -117.85036, -118.31712, -118.31712)
graphingdata <- cbind.data.frame(Latitude,Longitude)
#Set up bounding box
height <- max(graphingdata$Latitude) - min(graphingdata$Latitude)
width <- max(graphingdata$Longitude) - min(graphingdata$Longitude)
sac_borders <- c(bottom = min(graphingdata$Latitude) - 0.1 * height,
top = max(graphingdata$Latitude) + 0.1 * height,
left = min(graphingdata$Longitude) - 0.1 * width,
right = max(graphingdata$Longitude) + 0.1 * width)
#Get the site map
map <- get_stamenmap(sac_borders, zoom = 10, maptype = "terrain")
map <- ggmap(site_map, legend = "bottom")+
scalebar(x.min = sac_borders[[3]]-1, x.max = sac_borders[[4]]+1,y.min = sac_borders[[1]]-1, y.max = sac_borders[[2]]+1,transform = TRUE,
dist = 20,dist_unit = "km",model = "WGS84",anchor = c(x=sac_borders[[3]]+0.6,y=sac_borders[[1]]+0.25), st.size = 2, border.size = 0.5)+
geom_point(data = graphingdata, aes(x = as.numeric(Longitude),
y = as.numeric(Latitude)), color = "red", size = 2) +
ggtitle(paste0("This is the map title"),
subtitle = paste0("This is the subtitle"))
Example output map. Scale bar can be moved with the anchor argument. Zoom can be modified in get_stamenmap().

How to create base R plot 'type = b' equivalent in ggplot2?

Base plot() functionality allows one to set type='b' and get a combined line and point plot in which the points are offset from the line segments
plot(pressure, type = 'b', pch = 19)
I can easily create a ggplot with lines and points as follows.
ggplot(pressure, aes(temperature, pressure)) +
geom_line() +
geom_point()
The lines, however, run right up to the points. I can envision a way that I might hack together something like type='b' functionality using other geoms (e.g. geom_segment()?), but I am wondering if there is a more direct way to accomplish this with geom_line() and geom_point().
A slightly hacky way of doing this is to overplot a small black point on a larger white point:
ggplot(pressure, aes(temperature, pressure)) +
geom_line() +
geom_point(size=5, colour="white") +
geom_point(size=2) +
theme_classic() +
theme(panel.background = element_rect(colour = "black"))
In addition, following Control point border thickness in ggplot, in version 2.0.0 of ggplot2 it's possible to use the stroke argument of geom_point to control the border thickness, so the two geom_points can be replaced by just (e.g.) geom_point(size=2, shape=21, fill="black", colour="white", stroke=3), eliminating the need to overlay the points.
One option which is less hacky than manually matching the stroke color with the panel background is to get the panel background beforehand, either from theme_get for the default theme, or with a specific theme that you'll be using. Using a stroked shape like 21 lets you make the inner circle black and the stroke the same color as the background.
library(ggplot2)
bgnd <- theme_get()$panel.background$fill
ggplot(pressure, aes(x = temperature, y = pressure)) +
geom_line() +
geom_point(shape = 21, fill = "black", size = 2, stroke = 1, color = bgnd)
A couple SO questions (here's one) deal with the math behind shortening segments between points. It's simple but tedious geometry. But in the time since this question was first posted, the lemon package has come out, which has a geom to do this. It's got arguments for how to calculate the shortening, which probably require just some simple tweaking.
library(lemon)
ggplot(pressure, aes(x = temperature, y = pressure)) +
geom_pointline()
Ok I have an implementation of a geom, that does not rely on hardcoding and should not have wierd offsets. It's essentialy a geom_point() implementation, that draws a path* between points, draws a larger background point with colours set to the panel background and then the normal points.
*note that path's behaviour is not to connect points along the x-axis, but along row-order in the data.frame that is given to ggplot. You can sort your data beforehand if you want geom_line() behaviour.
The main problem for me was to get the inner workings of the geom drawing code to retrieve the theme of the current plot to extract the background colour of the panel. Due to this, I'm very unsure how stable this would be (and would welcome any tips), but at least it works.
EDIT: should be more stable now
Let's get to the, admittedly lengthy, ggproto object code:
GeomPointPath <- ggproto(
"GeomPointPath", GeomPoint,
draw_panel = function(self, data, panel_params, coord, na.rm = FALSE)
{
# bgcol <- sys.frame(4)$theme$panel.background$fill
# if (is.null(bgcol)) {
# bgcol <- theme_get()$panel.background$fill
# }
# EDIT: More robust bgcol finding -----------
# Find theme, approach as in https://github.com/tidyverse/ggplot2/issues/3116
theme <- NULL
for(i in 1:20) {
env <- parent.frame(i)
if("theme" %in% names(env)) {
theme <- env$theme
break
}
}
if (is.null(theme)) {
theme <- theme_get()
}
# Lookup likely background fills
bgcol <- theme$panel.background$fill
if (is.null(bgcol)) {
bgcol <- theme$plot.background$fill
}
if (is.null(bgcol)) {
bgcol <- theme$rect$fill
}
if (is.null(bgcol)) {
# Default to white if no fill can be found
bgcol <- "white"
}
# END EDIT ------------------
if (is.character(data$shape)) {
data$shape <- ggplot2:::translate_shape_string(data$shape)
}
coords <- coord$transform(data, panel_params)
# Draw background points
bgpoints <- grid::pointsGrob(
coords$x, coords$y, pch = coords$shape,
gp = grid::gpar(
col = alpha(bgcol, NA),
fill = alpha(bgcol, NA),
fontsize = (coords$size * .pt + coords$stroke * .stroke/2) * coords$mult,
lwd = coords$stroke * .stroke/2
)
)
# Draw actual points
mypoints <- grid::pointsGrob(
coords$x, coords$y, pch = coords$shape,
gp = grid::gpar(
col = alpha(coords$colour, coords$alpha),
fill = alpha(coords$fill, coords$alpha),
fontsize = coords$size * .pt + coords$stroke * .stroke/2,
lwd = coords$stroke * .stroke/2
)
)
# Draw line
myline <- grid::polylineGrob(
coords$x, coords$y,
id = match(coords$group, unique(coords$group)),
default.units = "native",
gp = grid::gpar(
col = alpha(coords$colour, coords$alpha),
fill = alpha(coords$colour, coords$alpha),
lwd = (coords$linesize * .pt),
lty = coords$linetype,
lineend = "butt",
linejoin = "round", linemitre = 10
)
)
# Place graphical objects in a tree
ggplot2:::ggname(
"geom_pointpath",
grid::grobTree(myline, bgpoints, mypoints)
)
},
# Set some defaults, assures that aesthetic mappings can be made
default_aes = aes(
shape = 19, colour = "black", size = 1.5, fill = NA, alpha = NA, stroke = 0.5,
linesize = 0.5, linetype = 1, mult = 3,
)
)
Observant people may have noticed the line bgcol <- sys.frame(4)$theme$panel.background$fill. I could not find another way to access the current plot's theme, without having to adjust at least several other functions to pass the theme as an argument. In my version of ggplot (3.1.0), the 4th sys.frame() is the environment of the ggplot2:::ggplot_gtable.ggplot_built call wherein the geom drawing code is evaluated. It's quite easy to imagine that this function can be updated in the future -which can change the scoping- hence the stability warning. As a backup, it defaults to the global theme settings when it can't find the current theme.
EDIT: should now be more stable
Onwards to the layer wrapper which is pretty much self-explanatory:
geom_pointpath <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", ..., na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE)
{
layer(data = data, mapping = mapping, stat = stat, geom = GeomPointPath,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...))
}
Adding it to a ggplot should be a familiar thing. Just setting the theme to the default theme_gray() to test that it indeed takes the current plot's theme.
theme_set(theme_gray())
g <- ggplot(pressure, aes(temperature, pressure)) +
geom_pointpath() +
theme(panel.background = element_rect(fill = "dodgerblue"))
Of course, this method will obscure grid lines with the background points, but that is the tradeoff I was willing to make to prevent wonkyness due to line path shortening. Line sizes, line types, and the relative size of the background points can be set with aes(linesize = ..., linetype = ..., mult = ...) or per the ... argument in geom_pointpath(). It inherits the other aesthetics from GeomPoint.
I'm sorry for answering twice, but this seems sufficiently different to merit a different answer.
I've given this question some more thought and I'll concede that a geometric approach is indeed the better approach over the point-over-point approach. However, the geometric approach comes with its own set of problems, namely that any attempt at pre-computing coordinates before draw-time is going to give you some skew in one way or another (see a follow up question from #Tjebo).
It is next to impossible to know the aspect ratio or exact sizes of the plot a priori, except by setting an aspect ratio manually or using the space argument of facet_grid(). Because this is impossible, any precomputed set of coordinates is going to be inadequate if the plot is resized.
I've shamelessly stolen some good ideas from other people, so thanks to #Tjebo and #moody_mudskipper for the maths and credit to ggplot guru thomasp85 and the ggforce package for the calculating at drawtime inspiration.
On with it; first we'll define our ggproto as before, now making a custom grob class for our path. An important detail is that we convert our xy coordinates to formal units.
GeomPointPath <- ggproto(
"GeomPointPath", GeomPoint,
draw_panel = function(data, panel_params, coord, na.rm = FALSE){
# Default geom point behaviour
if (is.character(data$shape)) {
data$shape <- translate_shape_string(data$shape)
}
coords <- coord$transform(data, panel_params)
my_points <- pointsGrob(
coords$x,
coords$y,
pch = coords$shape,
gp = gpar(col = alpha(coords$colour, coords$alpha),
fill = alpha(coords$fill, coords$alpha),
fontsize = coords$size * .pt + coords$stroke * .stroke/2,
lwd = coords$stroke * .stroke/2))
# New behaviour
## Convert x and y to units
x <- unit(coords$x, "npc")
y <- unit(coords$y, "npc")
## Make custom grob class
my_path <- grob(
x = x,
y = y,
mult = (coords$size * .pt + coords$stroke * .stroke/2) * coords$mult,
name = "pointpath",
gp = grid::gpar(
col = alpha(coords$colour, coords$alpha),
fill = alpha(coords$colour, coords$alpha),
lwd = (coords$linesize * .pt),
lty = coords$linetype,
lineend = "butt",
linejoin = "round", linemitre = 10
),
vp = NULL,
### Now this is the important bit:
cl = 'pointpath'
)
## Combine grobs
ggplot2:::ggname(
"geom_pointpath",
grid::grobTree(my_path, my_points)
)
},
# Adding some defaults for lines and mult
default_aes = aes(
shape = 19, colour = "black", size = 1.5, fill = NA, alpha = NA, stroke = 0.5,
linesize = 0.5, linetype = 1, mult = 0.5,
)
)
Through the magic of object oriented programming, we can now write a new method for our new grob class. While that may be uninteresting in and of itself, it gets particularly interesting if we write this method for makeContent, which is called every time a grob is drawn. So, let's write a method that invokes the mathematical operations on the exact coordinates the graphics device is going to use:
# Make hook for drawing
makeContent.pointpath <- function(x){
# Convert npcs to absolute units
x_new <- convertX(x$x, "mm", TRUE)
y_new <- convertY(x$y, "mm", TRUE)
# Do trigonometry stuff
hyp <- sqrt(diff(x_new)^2 + diff(y_new)^2)
sin_plot <- diff(y_new) / hyp
cos_plot <- diff(x_new) / hyp
diff_x0_seg <- head(x$mult, -1) * cos_plot
diff_x1_seg <- (hyp - head(x$mult, -1)) * cos_plot
diff_y0_seg <- head(x$mult, -1) * sin_plot
diff_y1_seg <- (hyp - head(x$mult, -1)) * sin_plot
x0 = head(x_new, -1) + diff_x0_seg
x1 = head(x_new, -1) + diff_x1_seg
y0 = head(y_new, -1) + diff_y0_seg
y1 = head(y_new, -1) + diff_y1_seg
keep <- unclass(x0) < unclass(x1)
# Remove old xy coordinates
x$x <- NULL
x$y <- NULL
# Supply new xy coordinates
x$x0 <- unit(x0, "mm")[keep]
x$x1 <- unit(x1, "mm")[keep]
x$y0 <- unit(y0, "mm")[keep]
x$y1 <- unit(y1, "mm")[keep]
# Set to segments class
class(x)[1] <- 'segments'
x
}
Now all we need is a layer wrapper like before, which does nothing special:
geom_pointpath <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", ..., na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE)
{
layer(data = data, mapping = mapping, stat = stat, geom = GeomPointPath,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...))
}
The demonstration:
g <- ggplot(pressure, aes(temperature, pressure)) +
# Ribbon for showing no point-over-point background artefacts
geom_ribbon(aes(ymin = pressure - 50, ymax = pressure + 50), alpha = 0.2) +
geom_pointpath()
And this should be stable for any resized aspect ratio. You can supply aes(mult = ...) or just mult = ... to control the size of the gaps between segments. By default it is proportional to the point sizes, so varying the point size while keeping the gap contant is a challenge. Segments that are shorter than two times the gap are deleted.
This is now possible with the CRAN package {ggh4x}. Funny fact, the geom for this package saw the light of the day on this SO post :) Thanks teunbrand!
library(ggh4x)
#> Loading required package: ggplot2
ggplot(pressure, aes(temperature, pressure)) +
geom_pointpath()
Created on 2021-11-13 by the reprex package (v2.0.1)

R ggmap legend/guide issues with multiple layers

I have been trying to create a map of membership locations from postcodes across the UK as a project in learning R. I have achieved nearly the result I wanted, but it's proving very frustrating getting the glitches sorted. This image is my current best effort:
I still want to change:
get rid of the extraneous legend (the "0.16", "0.5" squares), which are coming from the size arg to geom_point. If I remove the size=0.16 arg the guide/legend disappears, but the geom size returns to the default too. This also happens for the "black" guide -- coming from a colour obviously -- but why?
properly clip the stat_density2d polygons, which are exhibiting undesireable behaviour when clipped (see bottom-right plot near the top)
have control over the line-width of the geom_path that includes the county boundaries: it's currently too thick (would like about 1/2 thickness shown) but all I can achieve by including 'size' values is to make the lines stupidly thick - so thick that they obscure the whole map.
The R code uses revgeocode() to find the placename closest to the centre point but I don't know how to include the annotation on the map. I would like to include it in a text-box over the North Sea (top right of UK maps), maybe with a line/arrow to the point itself. A simpler option could just be some text beneath the UK map, below the x-axis ... but I don't know how to do that. geom_rect/geom_text seem fraught in this context.
Finally, I wanted to export the map to a high-res image, but when I do that everything changes again, see:
which shows the high-res (~1700x1800px) image on the left and the Rstudio version (~660x720px) on the right. The proportions of the maps have changed and the geom_text and geom_point for the centre point are now tiny. I would be happy if the gap between the two map rows was always fairly small, too (rather than just small at high res).
Code
The basics: read list of members postcodes, join with mySociety table of postcode<>OSGB locations, convert locations to Lat/long with spTransform, calculate binhex and density layers, plot with ggmap.
The code for all this is somewhat lengthy so I have uploaded it as a Gist:
https://gist.github.com/rivimey/ee4ab39a6940c0092c35
but for reference the 'guts' of the mapping code is here:
# Get a stylised base map for the whole-of-uk maps.
map.bbox = c(left = -6.5, bottom = 49.5, right = 2, top = 58)
basemap.uk <- get_stamenmap(bb = map.bbox, zoom=calc_zoom(map.bbox), maptype="watercolor")
# Calculate the density plot - a continuous approximation.
smap.den <- stat_density2d(aes(x = lat, y = lon, fill = ..level.., alpha = ..level..),
data = membs.wgs84.df, geom = "polygon",
breaks=2/(1.5^seq(0,12,by=1)), na.rm = TRUE)
# Create a point on the map representing the centroid, and label it.
cmap.p <- geom_point(aes(x = clat, y = clon), show_guide = FALSE, data = centroid.df, alpha = 1)
cmap.t1 <- geom_text(aes(x = clat, y = clon+0.22, label = "Centre", size=0.16), data = centroid.df)
cmap.t2 <- geom_text(aes(x = clat, y = clon+0.1, label = "Centre", size=0.25), data = centroid.df)
# Create an alternative presentation, as binned hexagons, which is more true to the data.
smap.bin <- geom_hex(aes(x = lat, y = lon),
data = membs.wgs84.df, binwidth = c(0.15, 0.1), alpha = 0.7, na.rm = TRUE)
# Create a path for the county and country boundaries, to help identify map regions.
bounds <- geom_path(aes(x = long, y = lat, group = group, colour = "black"), show_guide = FALSE,
data = boundaries.subset, na.rm = TRUE)
# Create the first two actual maps: a whole-uk binned map, and a whole-uk density map.
map.bin <- ggmap(basemap.uk) + smap.bin + grad + cmap.p + cmap.t1
map.den <- ggmap(basemap.uk) + smap.den + alpha + cmap.p + cmap.t1
# Create a zoomed-in map for the south-east, to show greater detail. I would like to use this
# bbox but google maps don't respect it :(
map.lon.bbox = c(left = -1, bottom = 51, right = 1, top = 52)
# Get a google terrain map for the south-east, bbox roughly (-1.7,1.7, 50.1, 53)
basemap.lon <- get_map(location = c(0,51.8), zoom = 8, maptype="terrain", color = "bw")
# Create a new hexbin with more detail than earlier.
smap.lon.bin <- geom_hex(aes(x = lat, y = lon),
data = membs.wgs84.df, bins=26, alpha = 0.7, na.rm = TRUE)
# Noe create the last two maps: binned and density maps for London and the SE.
lonmap.bin <- ggmap(basemap.lon) + bounds + smap.lon.bin + grad + cmap.p + cmap.t2
lonmap.den <- ggmap(basemap.lon) + bounds + smap.den + alpha + cmap.p + cmap.t2
# Arrange the maps in 2x2 grid, and tell the grid code to let the first row be taller than the second.
multiplot(map.bin, lonmap.bin, map.den, lonmap.den, heights = unit( c(10,7), "null"), cols=2 )

Resources