ggplot2 + sf + 高德地图数据绘制中国地图
楚新元 / 2026-06-20
最近在小红书上看到了 2021 年中国艾滋病发病率地图,尽管有个别细节上的瑕疵,但是瑕不掩瑜,这幅图总体上比较专业,一时技痒,打算复刻这幅图。下面我们一窥这幅图全貌。

这幅图有几个亮点:
布局方面。他把南海插图放在的左下角,把图例放在上外蒙古,布局很合理,而一般看到的图是把图例放在左小角,而右下角放置南海插图。
颜色方面。很醒目,从浅粉色到黑色分别映射到每个区间。注意他的区间是不连续的,这个很有意思。
标签方面。他把靠海的部分不方便在图上显示的省区放在了外面,比如海南、澳门、香港、台湾、上海、天津,用黑色标记,而其他省区是根据数值大小分成了两类,数值小于 3 的因为映射较浅的颜色,他用了黑色标签,数值大于等于 3 的因为映射较深的颜色,如果还用黑色标签就看不太清楚了,他用了白色标签。标签的位置总体是在各省区质心的基础上个别调整,例如北京、天津、甘肃、陕西和内蒙古等。
这幅图几个小小的瑕疵:
南海插图。官方声明范围南纬最低点在 3°,这个插图是紧贴着南海九段线最下边,导致地图不全。
缺失值问题。
NA表示数据确实存在,但我们没有收集到 / 缺失了。N/A表示数据根本不存在,或者该项指标在此处不适用。这里明显是前一种情况,应该用NA。
下面我们开始复刻这幅图。
中国地图做法
统计之都上有很多关于 R 语言画中国地图的文章和讨论。黄湘云的《R 语言画中国地图》一文中较全面介绍了中国地图的好几种做法。
首先从数据来源看,国家基础地理信息中心的数据是最权威的,也是普遍使用的数据,但是目前目前此网站已经不可用,统计之都上可以下载到数据,可酌情使用。自然资源部标准地图服务系统和国家地理信息公共服务平台。阿里云 DataV 数据可视化平台也提供了地图数据,它来自高德开放平台。
从地图数据处理工具看,sf 包是 R 语言现代主流矢量地理空间数据处理包,替代老旧的 sp 包,专门处理 GIS 矢量数据,底层封装三大 GIS 工业库,其中:GDAL 负责读写 shp、geojson、mdb 等地理文件,GEOS 负责平面几何运算(相交、缓冲、裁剪、质心),PROJ 负责坐标系转换、投影变换,经纬度球面运算自动调用 s2 包,精度更高。
从绘图工具来看,自然是 R 语言最著名的绘图包 ggplot2,此处不再赘述。当然了,leaflet 也是很棒的工具,值得一试。
先跑通整个工具链
从数据的可获得性、完整性和权威性三个维度看,高德平台的地图数据最容易获取,也比较完整,自带南海诸岛、钓鱼岛及其附属岛屿,无需额外添加南海诸岛和九段线图层,另外该地图数据还贴心的加入了各省区的行政中心和地理位置中心的经纬度,权威性自然与官方权威部门的数据不能比,但是这个数据有大厂背书,背后有无数的高德地图用户,还是值得信赖的,作为学习和交流使用完全没有问题,因此本次中国地图绘制使用高德平台地图数据。
# ------------------------------
# 加载 R 包
# ------------------------------
library(sf)
library(ggplot2)
# ------------------------------
# 获取数据地图
# ------------------------------
# 数据来源:https://datav.aliyun.com/portal/school/atlas/area_selector
# 数据版本:areas_v3
# 行政区划范围:中华人民共和国
# 数据粒度:省
url = "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
# 下载数据方便离线使用
if (!dir.exists("data")) dir.create("data")
dest = "./data/中华人民共和国省级地图数据.json"
if (!file.exists(dest)) download.file(url, dest)
# ------------------------------
# 地图数据和艾滋病数据并合并
# ------------------------------
# 高德的中国行政区划 GeoJSON 是平面投影适配的矢量边界
# 禁用 s2 球面几何,使用传统 GEOS 平面笛卡尔几何
sf_use_s2(FALSE)
# 读取中国省级地图数据
cn_raw = read_sf(dest)
# ------------------------------
# 生成最终地图
# ------------------------------
ggplot() +
geom_sf(data = cn_raw)

实际数据映射到地图上
- 先把艾滋病发病率数据合并到地图数据里
# 艾滋病发病率数据(1/10万)
prov_aids = tibble::tribble(
~name, ~value,
"北京市", 2.59,
"天津市", 2.28,
"河北省", 1.36,
"山西省", 1.92,
"内蒙古自治区", 1.66,
"辽宁省", 2.30,
"吉林省", 2.78,
"黑龙江省", 1.70,
"上海市", 1.60,
"江苏省", 1.83,
"浙江省", 2.64,
"安徽省", 1.83,
"福建省", 2.58,
"江西省", 3.85,
"山东省", 1.01,
"河南省", 3.05,
"湖北省", 2.57,
"湖南省", 4.54,
"广东省", 3.61,
"广西壮族自治区", 14.19,
"海南省", 2.32,
"重庆市", 11.99,
"四川省", 13.95,
"贵州省", 10.48,
"云南省", 9.12,
"西藏自治区", 0.82,
"陕西省", 2.23,
"甘肃省", 2.00,
"青海省", 3.19,
"宁夏回族自治区", 1.47,
"新疆维吾尔自治区", 5.43,
"台湾省", 6.09,
"香港特别行政区", NA,
"澳门特别行政区", NA
)
# 合并数据(地图数据 + 艾滋病数据)
cn_raw_prov_aids = merge(cn_raw, prov_aids, by = "name", all.x = TRUE)
- 根据发病率数据确定所属区间
# 将发病率拆分为区间
cn_raw_prov_aids = within(cn_raw_prov_aids, {
interval = cut(
value,
breaks = c(
0.82, 1.02, 1.36, 1.93, 2.00, 2.79, 3.05, 4.55,
5.43, 9.13, 10.48, 12.00, 13.95, 14.20
),
right = FALSE,
labels = c(
"0.82 - 1.01", NA, "1.36 - 1.92", NA,
"2.00 - 2.78", NA, "3.05 - 4.54", NA,
"5.43 - 9.12", NA, "10.48 - 11.99", NA,
"13.95 - 14.19"
)
)
})
- 处理后的数据转回空间几何绘图数据
# 生成空间几何绘图数据
cn_prov_aids_data = st_as_sf(
cn_raw_prov_aids,
sf_column_name = "geometry"
)
- 设置填充颜色和标签位置、内容、颜色
# 加载必要的 dplyr 函数
use("dplyr", c("mutate", "select", "case_when"))
# 定义每个区间的颜色
interval_colors = c(
"0.82 - 1.01" = "#fbcdcd", # 浅粉
"1.36 - 1.92" = "#f38b88", # 深粉
"2.00 - 2.78" = "#f8413b", # 红色
"3.05 - 4.54" = "#c00203", # 深红
"5.43 - 9.12" = "#790102", # 暗红
"10.48 - 11.99" = "#340100", # 黑红
"13.95 - 14.19" = "#0d0101" # 黑色
)
# 几何质心处标签位置微调
adjust_dict = tibble::tribble(
~short_name, ~lon_offset, ~lat_offset,
"甘肃", 0.3, -1.1,
"内蒙古", -4.0, -3.5,
"陕西", 0.0, -1.0,
"河北", 1.0, 0.0,
"北京", 0.0, 1.2,
"天津", 1.2, 0.0,
"辽宁", 0.7, 0.0,
"吉林", 0.0, -0.6,
"黑龙江", 1.0, -1.5,
"云南", 0.0, -0.5,
"海南", -2.5, -0.8,
"澳门", -0.8, -1.8,
"香港", 0.8, -1.4,
"台湾", 2.5, -0.5,
"上海", 1.5, 0.7
)
# 确定标签位置、内容和颜色
cn_raw_prov_aids |>
subset(level == "province") |>
mutate(
c_lon = vapply(center, `[`, numeric(1), 1),
c_lat = vapply(center, `[`, numeric(1), 2),
geo_lon = vapply(centroid, `[`, numeric(1), 1),
geo_lat = vapply(centroid, `[`, numeric(1), 2)
) |>
st_drop_geometry() |>
select(name, value, c_lon, c_lat, geo_lon, geo_lat) |>
mutate(
# 根据全称生成简称
short_name = gsub("(省|自治区|市|特别行政区)$", "", name),
short_name = gsub("(壮族|回族|维吾尔)", "", short_name),
# 河北、甘肃几何质心缺失,先用行政中心补齐
geo_lon = ifelse(short_name %in% c("河北", "甘肃"), c_lon, geo_lon),
geo_lat = ifelse(short_name %in% c("河北", "甘肃"), c_lat, geo_lat),
) |>
# 匹配微调参数并计算
merge(adjust_dict, by = "short_name", all.x = TRUE) |>
mutate(
geo_lon = geo_lon + ifelse(is.na(lon_offset), 0, lon_offset),
geo_lat = geo_lat + ifelse(is.na(lat_offset), 0, lat_offset)
) |>
select(-lon_offset, -lat_offset) |>
# 生成标签内容并确定颜色
mutate(
label = paste(short_name, format(value, nsmall = 2), sep = "\n"),
type = ifelse(
# 标签在外部的一律用黑色;在内部的,数值小颜色浅用黑色,数值大颜色深用白色
value < 3 | short_name %in% c("海南", "澳门", "香港", "台湾", "上海", "天津"),
"black_font", "white_font"
)
) -> prov_center
- 生成省区填充色映射艾滋病数据的地图
ggplot() +
# 绘制中国地图最底层图层
geom_sf(
data = cn_prov_aids_data,
aes(fill = interval),
color = "gray60", size = 0.25
) +
# 标注各省区简称和艾滋病发病率
geom_text(
data = prov_center,
aes(x = geo_lon, y = geo_lat, label = label, color = type),
size = 3, family = "SimHei", hjust = 0.5
) +
scale_color_manual(
values = c("black_font" = "black", "white_font" = "white"),
guide = "none"
) +
# 各省区艾滋病发病率数值按区间填充颜色
scale_fill_manual(
values = interval_colors,
na.value = "white", # 缺失治省区填充白色
na.translate = FALSE, # 隐藏缺失值图例
) +
# 设置坐标系和主题
coord_sf() +
theme_void()

再出发,美化地图
完成了上面的图,剩下的工作除了南海九段线插图外,都没什么技术含量,但是参数调整比较耗费时间。我们先修改图例位置和顺序、加入注释说明、砍掉信息密度极低的下半部分、调整图片尺寸。
#| fig.width: 9
#| fig.height: 8
ggplot() +
# 绘制中国地图最底层图层
geom_sf(
data = cn_prov_aids_data,
aes(fill = interval),
color = "gray60", size = 0.25
) +
# 标注各省区简称和艾滋病发病率
geom_text(
data = prov_center,
aes(x = geo_lon, y = geo_lat, label = label, color = type),
size = 4, family = "SimHei", hjust = 0.5
) +
scale_color_manual(
values = c("black_font" = "black", "white_font" = "white"),
guide = "none"
) +
# 各省区艾滋病发病率数值按区间填充颜色
scale_fill_manual(
values = interval_colors,
na.value = "white", # 缺失治省区填充白色
na.translate = FALSE, # 隐藏缺失值图例
) +
# 设置地图标题、图例、范围和坐标系
labs(title = "全国艾滋病总计发病率") +
guides(fill = guide_legend(reverse = TRUE, title = NULL)) +
coord_sf(xlim = c(73, 135), ylim = c(16, 54)) +
# 正上方添加图例注解
annotate(
"text", x = 106, y = 53,
label = "艾 滋 病 发 病 率(1/10万)",
family = "SimHei", size = 6
) +
annotate(
"text", x = 97, y = 51,
label = "HIV/AIDS",
family = "SimHei", size = 4
) +
# 左小角添加注解说明
annotate(
"text", x = 81.5, y = 24,
label = "全国总计发病率\n4.27",
family = "SimHei", size = 4
) +
annotate(
"text", x = 81.5, y = 20,
label = "2022年(2021年数据)\n中国卫生健康统计年鉴\n香港、澳门数据缺失",
family = "SimHei", size = 3.5
) +
# 设置地图主题
theme_void() +
theme(
plot.title = element_text(
family = "Noto Serif CJK SC", color = "#31415b",
size = 30, face = "bold", hjust = 0.5, vjust = 1.5,
),
legend.background = element_blank(),
legend.position = c(0.53, 0.80),
legend.key.size = unit(0.50, "cm"),
legend.text = element_text(family = "SimHei", size = 11),
)

注意:为了追求视觉效果,南海下半部分被砍了,但是必须保证地图完整,这就需要在地图上加一个插图专门显示南海岛礁和九段线等。
添加南海九段线插图
- 南海插图参数设置和矢量坐标转换
# 插图画布尺寸和主图放置起点(经纬度坐标)
inset_w = 9
inset_h = 9
inset_x0 = 87
inset_y0 = 17
# 能囊括完整南海四至的范围(包含全部岛礁、九段线)
nh_lon_min = 104.5
nh_lon_max = 122.5
nh_lat_min = 3.0 # 官方声明范围南纬 3°
nh_lat_max = 23.5
# 提取全部几何坐标矩阵
all_coords = as.data.frame(st_coordinates(cn_raw_prov_aids))
colnames(all_coords) = c("lon", "lat", "L1", "L2", "L3")
# 筛选能囊括南海范围的坐标点
nanhai_raw = subset(
all_coords,
lon >= nh_lon_min & lon <= nh_lon_max &
lat >= nh_lat_min & lat <= nh_lat_max
)
# 生成南海插图地图数据
nanhai_df = data.frame(
# 坐标映射:南海真实经纬度 → 插图坐标
lon = (nanhai_raw$lon - nh_lon_min) /
(nh_lon_max - nh_lon_min) * inset_w + inset_x0,
lat = (nanhai_raw$lat - nh_lat_min) /
(nh_lat_max - nh_lat_min) * inset_h + inset_y0,
# 属性映射与多边形分组
interval = cn_raw_prov_aids$interval[nanhai_raw$L3],
group = paste(nanhai_raw$L1, nanhai_raw$L2, nanhai_raw$L3, sep = "_")
)
- 生成完整的艾滋病发病率可视化地图
#| fig.width: 9
#| fig.height: 8
ggplot() +
# 绘制中国地图最底层图层
geom_sf(
data = cn_prov_aids_data,
aes(fill = interval),
color = "gray60", size = 0.25
) +
# 绘制南海插图背景
geom_rect(
aes(
xmin = inset_x0, xmax = inset_x0 + inset_w + 0.3,
ymin = inset_y0 - 0.3, ymax = inset_y0 + inset_h),
fill = "white", color = "black", linewidth = 0.25
) +
# 插图内绘制南海诸岛
geom_polygon(
data = nanhai_df,
aes(x = lon, y = lat, group = group, fill = interval),
color = "gray60", linewidth = 0.25
) +
# 标注各省区简称和艾滋病发病率
geom_text(
data = prov_center,
aes(x = geo_lon, y = geo_lat, label = label, color = type),
size = 4, family = "SimHei", hjust = 0.5
) +
scale_color_manual(
values = c("black_font" = "black", "white_font" = "white"),
guide = "none"
) +
# 各省区艾滋病发病率数值按区间填充颜色
scale_fill_manual(
values = interval_colors,
na.value = "white", # 缺失值省区填充白色
na.translate = FALSE, # 隐藏缺失值图例
) +
# 设置地图标题、图例、范围和坐标系
labs(title = "全国艾滋病总计发病率") +
guides(fill = guide_legend(reverse = TRUE, title = NULL)) +
coord_sf(xlim = c(73, 135), ylim = c(16, 54)) +
# 正上方添加图例注解
annotate(
"text", x = 106, y = 53,
label = "艾 滋 病 发 病 率(1/10万)",
family = "SimHei", size = 6
) +
annotate(
"text", x = 97, y = 51,
label = "HIV/AIDS",
family = "SimHei", size = 4
) +
# 左小角添加注解说明
annotate(
"text", x = 81.5, y = 24,
label = "全国总计发病率\n4.27",
family = "SimHei", size = 4
) +
annotate(
"text", x = 81.5, y = 20,
label = "2022年(2021年数据)\n中国卫生健康统计年鉴\n香港、澳门数据缺失",
family = "SimHei", size = 3.5
) +
# 设置地图主题
theme_void() +
theme(
plot.title = element_text(
family = "Noto Serif CJK SC", color = "#31415b",
size = 30, face = "bold", hjust = 0.5, vjust = 1.5,
),
legend.background = element_blank(),
legend.position = c(0.53, 0.80),
legend.key.size = unit(0.50, "cm"),
legend.text = element_text(family = "SimHei", size = 11),
)

附上完整代码方便重现
#'
#' 科学绘制中国地图
#'
#' @author 楚新元
#' @date 2026-06-21
#'
# ------------------------------
# 加载 R 包
# ------------------------------
library(sf)
library(ggplot2)
use("dplyr", c("mutate", "select", "case_when"))
# ------------------------------
# 获取数据地图
# ------------------------------
# 数据来源:https://datav.aliyun.com/portal/school/atlas/area_selector
# 数据版本:areas_v3
# 行政区划范围:中华人民共和国
# 数据粒度:省
url = "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
# 下载数据方便离线使用
if (!dir.exists("data")) dir.create("data")
dest = "./data/中华人民共和国省级地图数据.json"
if (!file.exists(dest)) download.file(url, dest)
# ------------------------------
# 地图数据和艾滋病数据并合并
# ------------------------------
# 高德的中国行政区划 GeoJSON 是平面投影适配的矢量边界
# 禁用 s2 球面几何,使用传统 GEOS 平面笛卡尔几何
sf_use_s2(FALSE)
# 读取中国省级地图数据
cn_raw = read_sf(dest)
# 艾滋病发病率数据(1/10万)
prov_aids = tibble::tribble(
~name, ~value,
"北京市", 2.59,
"天津市", 2.28,
"河北省", 1.36,
"山西省", 1.92,
"内蒙古自治区", 1.66,
"辽宁省", 2.30,
"吉林省", 2.78,
"黑龙江省", 1.70,
"上海市", 1.60,
"江苏省", 1.83,
"浙江省", 2.64,
"安徽省", 1.83,
"福建省", 2.58,
"江西省", 3.85,
"山东省", 1.01,
"河南省", 3.05,
"湖北省", 2.57,
"湖南省", 4.54,
"广东省", 3.61,
"广西壮族自治区", 14.19,
"海南省", 2.32,
"重庆市", 11.99,
"四川省", 13.95,
"贵州省", 10.48,
"云南省", 9.12,
"西藏自治区", 0.82,
"陕西省", 2.23,
"甘肃省", 2.00,
"青海省", 3.19,
"宁夏回族自治区", 1.47,
"新疆维吾尔自治区", 5.43,
"台湾省", 6.09,
"香港特别行政区", NA,
"澳门特别行政区", NA
)
# 合并数据(地图数据 + 艾滋病数据)
cn_raw_prov_aids = merge(cn_raw, prov_aids, by = "name", all.x = TRUE)
# 将发病率拆分为区间
cn_raw_prov_aids = within(cn_raw_prov_aids, {
interval = cut(
value,
breaks = c(
0.82, 1.02, 1.36, 1.93, 2.00, 2.79, 3.05, 4.55,
5.43, 9.13, 10.48, 12.00, 13.95, 14.20
),
right = FALSE,
labels = c(
"0.82 - 1.01", NA, "1.36 - 1.92", NA,
"2.00 - 2.78", NA, "3.05 - 4.54", NA,
"5.43 - 9.12", NA, "10.48 - 11.99", NA,
"13.95 - 14.19"
)
)
})
# 生成空间几何绘图数据
cn_prov_aids_data = st_as_sf(
cn_raw_prov_aids,
sf_column_name = "geometry"
)
# ------------------------------
# 设置填充颜色和标签位置、内容、颜色
# ------------------------------
# 定义每个区间的颜色
interval_colors = c(
"0.82 - 1.01" = "#fbcdcd", # 浅粉
"1.36 - 1.92" = "#f38b88", # 深粉
"2.00 - 2.78" = "#f8413b", # 红色
"3.05 - 4.54" = "#c00203", # 深红
"5.43 - 9.12" = "#790102", # 暗红
"10.48 - 11.99" = "#340100", # 黑红
"13.95 - 14.19" = "#0d0101" # 黑色
)
# 几何质心处标签位置微调
adjust_dict = tibble::tribble(
~short_name, ~lon_offset, ~lat_offset,
"甘肃", 0.3, -1.1,
"内蒙古", -4.0, -3.5,
"陕西", 0.0, -1.0,
"河北", 1.0, 0.0,
"北京", 0.0, 1.2,
"天津", 1.2, 0.0,
"辽宁", 0.7, 0.0,
"吉林", 0.0, -0.6,
"黑龙江", 1.0, -1.5,
"云南", 0.0, -0.5,
"海南", -2.5, -0.8,
"澳门", -0.8, -1.8,
"香港", 0.8, -1.4,
"台湾", 2.5, -0.5,
"上海", 1.5, 0.7
)
# 确定标签位置、内容和颜色
cn_raw_prov_aids |>
subset(level == "province") |>
mutate(
c_lon = vapply(center, `[`, numeric(1), 1),
c_lat = vapply(center, `[`, numeric(1), 2),
geo_lon = vapply(centroid, `[`, numeric(1), 1),
geo_lat = vapply(centroid, `[`, numeric(1), 2)
) |>
st_drop_geometry() |>
select(name, value, c_lon, c_lat, geo_lon, geo_lat) |>
mutate(
# 根据全称生成简称
short_name = gsub("(省|自治区|市|特别行政区)$", "", name),
short_name = gsub("(壮族|回族|维吾尔)", "", short_name),
# 河北、甘肃几何质心缺失,先用行政中心补齐
geo_lon = ifelse(short_name %in% c("河北", "甘肃"), c_lon, geo_lon),
geo_lat = ifelse(short_name %in% c("河北", "甘肃"), c_lat, geo_lat),
) |>
# 匹配微调参数并计算
merge(adjust_dict, by = "short_name", all.x = TRUE) |>
mutate(
geo_lon = geo_lon + ifelse(is.na(lon_offset), 0, lon_offset),
geo_lat = geo_lat + ifelse(is.na(lat_offset), 0, lat_offset)
) |>
select(-lon_offset, -lat_offset) |>
# 生成标签内容并确定颜色
mutate(
label = paste(short_name, format(value, nsmall = 2), sep = "\n"),
type = ifelse(
# 标签在外部的一律用黑色;在内部的,数值小颜色浅用黑色,数值大颜色深用白色
value < 3 | short_name %in% c("海南", "澳门", "香港", "台湾", "上海", "天津"),
"black_font", "white_font"
)
) -> prov_center
# ------------------------------
# 南海插图参数设置和矢量坐标转换
# ------------------------------
# 插图画布尺寸和主图放置起点(经纬度坐标)
inset_w = 9
inset_h = 9
inset_x0 = 87
inset_y0 = 17
# 能囊括完整南海四至的范围(包含全部岛礁、九段线)
nh_lon_min = 104.5
nh_lon_max = 122.5
nh_lat_min = 3.0 # 官方声明范围南纬 3°
nh_lat_max = 23.5
# 提取全部几何坐标矩阵
all_coords = as.data.frame(st_coordinates(cn_raw_prov_aids))
colnames(all_coords) = c("lon", "lat", "L1", "L2", "L3")
# 筛选能囊括南海范围的坐标点
nanhai_raw = subset(
all_coords,
lon >= nh_lon_min & lon <= nh_lon_max &
lat >= nh_lat_min & lat <= nh_lat_max
)
# 生成南海插图地图数据
nanhai_df = data.frame(
# 坐标映射:南海真实经纬度 → 插图坐标
lon = (nanhai_raw$lon - nh_lon_min) /
(nh_lon_max - nh_lon_min) * inset_w + inset_x0,
lat = (nanhai_raw$lat - nh_lat_min) /
(nh_lat_max - nh_lat_min) * inset_h + inset_y0,
# 属性映射与多边形分组
interval = cn_raw_prov_aids$interval[nanhai_raw$L3],
group = paste(nanhai_raw$L1, nanhai_raw$L2, nanhai_raw$L3, sep = "_")
)
# ------------------------------
# 生成最终地图
# ------------------------------
ggplot() +
# 绘制中国地图最底层图层
geom_sf(
data = cn_prov_aids_data,
aes(fill = interval),
color = "gray60", size = 0.25
) +
# 绘制南海插图背景
geom_rect(
aes(
xmin = inset_x0, xmax = inset_x0 + inset_w + 0.3,
ymin = inset_y0 - 0.3, ymax = inset_y0 + inset_h),
fill = "white", color = "black", linewidth = 0.25
) +
# 插图内绘制南海诸岛
geom_polygon(
data = nanhai_df,
aes(x = lon, y = lat, group = group, fill = interval),
color = "gray60", linewidth = 0.25
) +
# 标注各省区简称和艾滋病发病率
geom_text(
data = prov_center,
aes(x = geo_lon, y = geo_lat, label = label, color = type),
size = 4, family = "SimHei", hjust = 0.5
) +
scale_color_manual(
values = c("black_font" = "black", "white_font" = "white"),
guide = "none"
) +
# 各省区艾滋病发病率数值按区间填充颜色
scale_fill_manual(
values = interval_colors,
na.value = "white", # 缺失值省区填充白色
na.translate = FALSE, # 隐藏缺失值图例
) +
# 设置地图标题、图例、范围和坐标系
labs(title = "全国艾滋病总计发病率") +
guides(fill = guide_legend(reverse = TRUE, title = NULL)) +
coord_sf(xlim = c(73, 135), ylim = c(16, 54)) +
# 正上方添加图例注解
annotate(
"text", x = 106, y = 53,
label = "艾 滋 病 发 病 率(1/10万)",
family = "SimHei", size = 6
) +
annotate(
"text", x = 97, y = 51,
label = "HIV/AIDS",
family = "SimHei", size = 4
) +
# 左小角添加注解说明
annotate(
"text", x = 81.5, y = 24,
label = "全国总计发病率\n4.27",
family = "SimHei", size = 4
) +
annotate(
"text", x = 81.5, y = 20,
label = "2022年(2021年数据)\n中国卫生健康统计年鉴\n香港、澳门数据缺失",
family = "SimHei", size = 3.5
) +
# 设置地图主题
theme_void() +
theme(
plot.title = element_text(
family = "Noto Serif CJK SC", color = "#31415b",
size = 30, face = "bold", hjust = 0.5, vjust = 1.5,
),
legend.background = element_blank(),
legend.position = c(0.53, 0.80),
legend.key.size = unit(0.50, "cm"),
legend.text = element_text(family = "SimHei", size = 11),
)
- 注意 2026-06-21
1.南海的最南端必须按照官方声明范围南纬 3° 来,其它参数可以设置的相对宽松点,只要确保将南海九段线包括进来即可;2.南海插图矢量坐标转换这一步,将空间几何绘图数据转为数据框完全没必要,最优雅的方式应该是利用 sf 包的函数直接处理,结果仍然是空间几何绘图数据,而不是数据框;3.上面的代码生成的图作为日常学习交流使用完全没问题,但是在学术论文和正式场合,建议还是使用官方权威数据来源,另外,我们会发现它和国内权威媒体通常使用的地图形状有些差异,我们的图看起来比较扁,这其实是因为投影不同而造成的,权威媒体地图采用的投影方式为兰伯特正方位等积投影(Lambert azimuthal equal-area projection)。
环境信息
xfun::session_info()
#> R version 4.6.0 (2026-04-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Arch Linux, Positron 2026.06.0
#>
#> Locale:
#> LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> LC_TIME=zh_CN.UTF-8 LC_COLLATE=en_US.UTF-8
#> LC_MONETARY=zh_CN.UTF-8 LC_MESSAGES=en_US.UTF-8
#> LC_PAPER=zh_CN.UTF-8 LC_NAME=C
#> LC_ADDRESS=C LC_TELEPHONE=C
#> LC_MEASUREMENT=zh_CN.UTF-8 LC_IDENTIFICATION=C
#>
#> Package version:
#> base64enc_0.1.6 blogdown_1.24 bookdown_0.47 bslib_0.11.0
#> cachem_1.1.0 class_7.3-23 classInt_0.4-11 cli_3.6.6
#> compiler_4.6.0 cpp11_0.5.5 DBI_1.3.0 digest_0.6.39
#> dplyr_1.2.1 e1071_1.7-17 evaluate_1.0.5 farver_2.1.2
#> fastmap_1.2.0 fontawesome_0.5.3 fs_2.1.0 generics_0.1.4
#> ggplot2_4.0.3 glue_1.8.1 graphics_4.6.0 grDevices_4.6.0
#> grid_4.6.0 gtable_0.3.6 highr_0.12 htmltools_0.5.9
#> httpuv_1.6.17 isoband_0.3.0 jpeg_0.1-11 jquerylib_0.1.4
#> jsonlite_2.0.0 KernSmooth_2.23-26 knitr_1.51 labeling_0.4.3
#> later_1.4.8 lifecycle_1.0.5 magrittr_2.0.5 MASS_7.3.65
#> memoise_2.0.1 methods_4.6.0 mime_0.13 otel_0.2.0
#> pillar_1.11.1 pkgconfig_2.0.3 promises_1.5.0 proxy_0.4-29
#> R6_2.6.1 rappdirs_0.3.4 RColorBrewer_1.1-3 Rcpp_1.1.1-1.1
#> rlang_1.2.0 rmarkdown_2.31 s2_1.1.11 S7_0.2.2
#> sass_0.4.10 scales_1.4.0 servr_0.33 sf_1.1-1
#> stats_4.6.0 tibble_3.3.1 tidyselect_1.2.1 tinytex_0.60
#> tools_4.6.0 units_1.0-1 utf8_1.2.6 utils_4.6.0
#> vctrs_0.7.3 viridisLite_0.4.3 withr_3.0.3 wk_0.9.5
#> xfun_0.59 yaml_2.3.12
