依稀记得2020年前后,搞定了标准中国地图(包括九段线)的分层设色图的绘制,非常得意地写了教程。但是最近,又需要做一个类似的任务,突然发现,原来的方法过时且老套了,遂用一个新的教程补充。
我们最后要达到的效果如下图所示:
第一步:不用 shp 文件!找到好读取的标准地图文件
以前用的是老版的 shp
地理数据文件,还要分为多边形的省份地图和线条形的九段线地图。现在想要找标准中国地图,可以直接在阿里云的数据可视化平台上面找到。点击下图中红色箭头指向的位置,就可以下载到一个为 json
格式的地理数据文件了。
第二步:读取地图文件和必要的文件
使用 sf
包里的 st_read
就可以直接读取了,确实比原来方便多了。而且我们等会儿还会用到 sf
包里面的其他函数。
library(sf)
# Import Map file
china_map <- st_read("map_file/中华人民共和国.json")
关于要可视化的数据,就不详细展示读取和清洗的过程了。大概可以这么理解,总共有 9 次 Inspection,每次 Inspection 都需要在地图上高亮若干个特定的省份。
第三步:合并地图数据和需要可视化的数据,建立基础地图
- 合并地图数据:合并地图数据也只需要
dplyr
包里经常用到的left_join
就可以; - 宽数据转长数据:非常重要的一步。如果想要用 ggplot 绘制多组数据的时候,这一步经常是必不可少的;
- 绘制基础地图:以前用
geom_polygon
,现在我们直接用geom_sf
; - 调整地图坐标系投影:如果使用了
geom_sf
,调整坐标系投影不能用以前的方法,而是必须使用coord_sf
,参数用我下面给的就可以; - 按变量分组绘制:使用
facet_wrap
,非常常用的一个指定分组绘制的方法,这里用到之后,9 个小图,按照 3×3 进行排列。
代码大致如下:
library(tidyverse)
# Main Map Plot with fill color
p1 <- china_map %>%
left_join(df, by=c("name"="NAME")) %>%
pivot_longer(
insp_1:insp_9,
names_to = "Var",
values_to = "Val"
) %>%
ggplot() +
geom_sf(aes(fill=Val), col="grey20") +
coord_sf(ylim = c(-2387082,1654989), crs='+proj=laea +lat_0=40 +lon_0=104') +
scale_fill_manual(values=c("white", "#41a5ee")) +
facet_wrap(
~ Var, ncol=3,
labeller = labeller(Var=plot_labels)) +
theme_void() +
theme(
strip.text=element_text(size=12, face="bold", family = "Times"),
plot.margin=margin(1, 1, 1, 1, "cm"),
legend.position="none")
p1
这样画好之后长这样:
已经初具形状了,只是少了九段线。接下来我们就加上九段线:
第四步:绘制九段线小图
这里的思路还是先绘制整个地图,然后根据地图想要显示的范围,进行裁切,得到想要的小图即可。裁切使用的是 coord_sf
,你可以看到我们通过设定经纬度范围来裁切。
这里需要强调一下,最后的 theme
里面设置边距设定为 -1,即不留任何边框,不然加在底图上时候会有一条白边边框。
# Mini map with 9 lines in the corner
china_mini <- ggplot(data = china_map) +
geom_sf(fill="white", col="grey20") +
coord_sf( xlim = c(105, 125),
ylim = c(4, 26),
expand = FALSE, datum = NA)+
theme_few() +
theme(plot.margin=margin(-1, -1, -1, -1, "pt"))
china_mini
这一步绘制的小图长这样:
第五步:将九段线小图覆盖在底图上
说实话,这一步是比较粗笨的,也是我稍微不太满意的一步。这里我研究了好久,目前还是只能用 cowplot
包里面的 draw_plot
来实现。因为我们有 9 个底图,因此,也需要加上 9 个九段线的小图:
# Figure arguments for position and ratio
ns_ratio <- .03
x_coords <- c(.308, .617, .928)
y_coords <- c(.655, .352, .045)
# Overlay 9 subplots on main map plot
ggdraw() +
draw_plot(p1) +
draw_plot(china_mini, x=x_coords[1], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[2], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[3], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[1], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[2], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[3], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[1], y=y_coords[3], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[2], y=y_coords[3], width=ns_ratio, height=ns_ratio) +
draw_plot(china_mini, x=x_coords[3], y=y_coords[3], width=ns_ratio, height=ns_ratio)
# Save pdf to local file
ggsave("final_plot.pdf", dpi=300, width=11, height=10)
这里比较丑陋的点主要在于:
- 9 个同样的
draw_plot
语句,一点也不优雅; x_coords
和y_coords
的坐标参数,不是算出来的,是一点点调整出来的,直接用数字摆在这里,可能会让第一次看到的读者迷惑。
不过还好,最后还是绘制出来了想要的效果:
总的来说
其实这次完成这个绘图任务还是比较简单的,最费时间的地方在于调整九段线小图在底图上的位置坐标。诸位读者如果不是非得要在 R 里输出成品,真的可以考虑在 Photoshop 甚至是 Powerpoint 里把九段线小图放置在合适的位置上。这样会节省大把的时间和精力。当然,如果各位有更好的方法能解决最后这一步,就更完美了。
另外,博客荒废多月,但是这几个月整体来说,相当充实,尤其是在 R 语言上又精进了许多,学会了很多很实用的包和技巧。之后会找时间和大家一一分享。