Problem Solved - Thanks to sjewo!
Using sjewos solution I was able to produce the cartograms for Upper Austria. Here they are, FYI.
If you want to run the source code, make sure to adapt the working directory within the script to your desired path. The map of election districts should download automatically.
The election results are chosen randomly because they are hard to download and handle programmatically.
I produce both uniformly coloured cartograms as well as cartograms with a colour scale.
The resulting maps
I hold the election results of three districts (Wels, Linz and Mondsee) constant. Notice how they stick out - especially on maps with a colour scale. Here are some examples:
OEVP_Colour_Constant
OEVP_Colour_Scale
FPOE_Colour_Constant
FPOE_Colour_Scale
SPOE_Colour_Constant
SPOE_Colour_Scale
NEOS_Colour_Constant
NEOS_Colour_Scale
The source code
There are numerous inline comments - I hope they are sufficient explanation of what happens!
# Cartograms - how to scale multiple maps to the same benchmark?
#
# Non-contiguous cartograms
#
# Dendron's question:
# https://stackoverflow.com/questions/68685129
#
# Implementing sjewo's answer:
# https://stackoverflow.com/a/68716489
# load packages
library("sf")
library("dplyr")
library("rgdal")
library("maptools")
library("cartogram")
library("foreach")
library("doParallel")
library("graphics")
library("s2dv")
library("rgeos")
# Settings
ext <- 'png' # file type
a <- 1 # alpha
gren <- "grey" # colour for border line
wd <- '/path/to/your/working/directory'
setwd(wd)
file1 <- paste(wd,'GEMEINDEGRENZEN_GEN', 'GEMEINDEGRENZEN_GEN.shp', sep = '/')
par_1 <- 0.7 # scaling factor for fonts
par_2 <- c(0.1,0.1,0.2,0.1) # margins for multi-panel-plotting
par_3 <- c(0.05,0.85,0.05,0.95) # borders for panels
kk <- 1 # expansion factor
# Download & unzip .shp file
link <- "https://e-gov.ooe.gv.at/at.gv.ooe.dorisdaten/DORIS_Basisdaten/GEMEINDEGRENZEN_GEN.zip"
file3 <- paste(wd,'GEMEINDEGRENZEN_GEN.zip',sep='/')
download.file(link,file3)
unzip(file3, exdir = 'GEMEINDEGRENZEN_GEN')
# Import Upper Austria's election districts
map <- read_sf(dsn = file1)
# Choose some parties to participate in the election
part <- c("oevp", "fpoe", "spoe", "gruene", "neos")
# Invent election results
set.seed(20210823)
map[['oevp']] <- runif(nrow(map),0,1)
foreach(g=2:length(part))%do%{
# Make each party's results less than the previous'
map[[part[g]]] <- map[[part[g-1]]]/2
# Keep some values constant for comparison
map[[part[g]]][map$GEM_NAME=="Wels"] = 1
map[[part[g]]][map$GEM_NAME=="Mondsee"] = 0.5
map[[part[g]]][map$GEM_NAME=="Linz"] = 0
}
# Summarise all election results into one variable
tmp <- map
tmp$votes <- tmp[[part[1]]]
names(tmp$votes) <- 'votes'
tmp$part <- part[1]
for(v in part[2:length(part)]) {
tt <- map
tt$votes <- tt[[v]]
tt$part <- v
tmp <- rbind(tmp, tt)
}
# Hand-pick colours and colour scales which match the parties' branding
farb <- c("#64c4d2","#044ee1","#ff0000","#00d600","#ff4ccf","#8C0307","#000000")
tuerk <- c("#FFFFFF", "#F7FCFD", "#F0F9FB", "#E8F6F8", "#E0F3F6", "#D8F0F4",
"#D1EDF2", "#C9EAEF", "#C1E7ED", "#B9E4EB", "#B2E2E9", "#AADFE6",
"#A2DCE4", "#9AD9E2", "#93D6E0", "#8BD3DD", "#83D0DB", "#7BCDD9",
"#74CAD7", "#6CC7D4", "#64C4D2")
blau <- c("#FFFFFF", "#F2F6FE", "#E6EDFC", "#D9E4FB", "#CDDCF9", "#C0D3F8",
"#B4CAF6", "#A7C1F5", "#9BB8F3", "#8EAFF2", "#82A7F0", "#759EEF",
"#6895ED", "#5C8CEC", "#4F83EA", "#437AE9", "#3671E7", "#2A69E6",
"#1D60E4", "#1157E3", "#044EE1")
rot <- c("#FFFFFF", "#FFF2F2", "#FFE6E6", "#FFD9D9", "#FFCCCC", "#FFBFBF",
"#FFB3B3", "#FFA6A6", "#FF9999", "#FF8C8C", "#FF8080", "#FF7373",
"#FF6666", "#FF5959", "#FF4D4D", "#FF4040", "#FF3333", "#FF2626",
"#FF1A1A", "#FF0D0D", "#FF0000")
gruen <- c("#FFFFFF", "#F2FDF2", "#E6FBE6", "#D9F9D9", "#CCF7CC", "#BFF5BF",
"#B3F3B3", "#A6F1A6", "#99EF99", "#8CED8C", "#80EB80", "#73E873",
"#66E666", "#59E459", "#4DE24D", "#40E040", "#33DE33", "#26DC26",
"#1ADA1A", "#0DD80D", "#00D600")
pink <- c("#FFFFFF", "#FFF6FD", "#FFEDFA", "#FFE4F8", "#FFDBF5", "#FFD2F3",
"#FFC9F1", "#FFC0EE", "#FFB7EC", "#FFAEE9", "#FFA6E7", "#FF9DE5",
"#FF94E2", "#FF8BE0", "#FF82DD", "#FF79DB", "#FF70D9", "#FF67D6",
"#FF5ED4", "#FF55D1", "#FF4CCF")
purp <- c("#FFFFFF", "#F9F2F3", "#F4E6E6", "#EED9DA", "#E8CDCD", "#E2C0C1",
"#DDB3B5", "#D7A7A8", "#D19A9C", "#CB8E8F", "#C68183", "#C07477",
"#BA686A", "#B45B5E", "#AF4F51", "#A94245", "#A33539", "#9D292C",
"#981C20", "#921013", "#8C0307")
schw <- c("#FFFFFF", "#F2F2F2", "#E6E6E6", "#D9D9D9", "#CCCCCC", "#BFBFBF",
"#B3B3B3", "#A6A6A6", "#999999", "#8C8C8C", "#808080", "#737373",
"#666666", "#595959", "#4D4D4D", "#404040", "#333333", "#262626",
"#1A1A1A", "#0D0D0D", "#000000")
# Combine colour maps
pally <- cbind(tuerk, blau, rot, gruen, pink, purp, schw)
# Choose breaks for colour scale
brks <- seq(0, 1, length.out = length(rot)+1)
# Choose sensible ticks for colour bar
ll <- seq(min(brks),max(brks), length.out = 3)
# Create base map
base <- st_geometry(map)
# Calculate Cartogram
ooe_scal <- cartogram_ncont(tmp, 'votes', k=kk, inplace = TRUE)
# Setup parallel cluster
cores=detectCores()
# Do not overload your computer
cl <- makeCluster(cores[1]-1)
registerDoParallel(cl)
# Loop over parties
foreach(i=1:length(part),
.packages = c("cartogram","sf","foreach","s2dv","graphics","rgdal","rgeos"))%dopar%{
# ----- WITH COLOUR SCALE ----- #
# Pick title/filename
tt <- paste(part[i], 'scale', sep = '_')
# start recording plot
png(file=paste(tt, ext, sep = '.'))
# Reset plotting device
layout(1)
par(cex=par_1, mai=par_2)
par(fig=par_3)
# Background map
plot(base, axes = FALSE, border = gren)
# Generate colour palette
pp <- colorRampPalette(pally[,i], space = "rgb", interpolate = "linear")
# Visualise
cc <- which(colnames(ooe_scal)==part[i], arr.ind = TRUE)
plot(ooe_scal[cc][ooe_scal$part==part[i],], pal = pp,
axes = FALSE, border = gren, add = TRUE, alpha = a, breaks = brks)
# Add description
title(tt)
# Visualise colour scale on bar
par(fig=c(0.9,1,0.2,0.8), new=TRUE)
ColorBar(brks = brks, cols = pally[,i], plot = TRUE,
vertical = TRUE, label_digits = 2, extra_labels = ll)
# Save output
dev.off()
# ----- WITH CONSTANT COLOURS ----- #
# Pick title/filename
tt <- paste(part[i], 'const', sep = '_')
# start recording plot
png(file=paste(tt, ext, sep = '.'))
# Reset plotting device
layout(1)
# Background map
plot(base, axes = FALSE, border = gren)
# Visualise
cc <- which(colnames(ooe_scal)==part[i], arr.ind = TRUE)
plot(ooe_scal[cc][ooe_scal$part==part[i],], col = c(farb[i]),
axes = FALSE, border = gren, add = TRUE, alpha = a, breaks = brks)
# Add description
title(tt)
# Save output
dev.off()
}
# stop cluster
stopCluster(cl)
# <EOF>