2020 年初始至今,冠状病毒肆虐,好在国内疫情已经逐步减少,武汉也解封了,而国外疫情却日益严重,不容乐观。

做个可视化地图模拟展示一下疫情

我们可以看到,各大 APP 都有实时显示全国乃至世界的疫情数据的地图表,那么它们是如何实现的呢?

让我们以中国为例,实现一下展示效果。由于平时用 Vue 较多,可视化框架之前在项目中用过 echarts,就用这两个来演示。(注:本次数据都是随机生成,非官方真实数据)

用 vue-cli 快速搭建

1
vue create vue-echarts-map-china

引入 echarts

1
yarn add echarts -D

如何展示中国各省数据

ProvinceChart.vue

1
2
3
<template>
<div class="chart" ref="chart" style="width: 100%; height: 800px"></div>
</template>

我们首先需要给 echarts 一个容器

引用 js/json 地图数据

echats 内置了 china.js 和 china.json 及其省市地图包,china.js 引入即可使用 china.json 需要注册到 echarts,两种方式任选

1
2
3
4
5
6
7
8
9
import echarts from "echarts";
import "echarts/theme/macarons"; // echarts 主题

// 1. china.js 直接使用
import china from "echarts/map/js/china";

// 2. china.json 需注册
import china from "echarts/map/json/china";
echarts.registerMap("china", china);

主要结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default {
name: "chart",
data() {
return {
chart: null
};
},
mounted() {
if (!this.chart) {
this.drawChinaMap();
}
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
drawChinaMap(){
...
}
}
};

drawChinaMap 方法详解

首先找到我们的容器用 echarts.init() 初始化 echarts

1
2
3
4
this.chart = echarts.init(document.querySelector(".chart"), "macarons");
this.chart.setOption({
...
})

通过 setOption 传入我们需要设定的选项参数

图表类型及参数

最主要的是设置图表类型了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
series: [
{
name: "数据",
type: "map",
mapType: "china", // 对应我们注册好的 "china"
roam: true, // 是否可缩放
label: { // 文本标签
normal: {
show: true //省份名称
},
emphasis: {
show: false
}
},
// 地图默认样式
itemStyle: {
normal: {
show: true,
areaColor: "#CECECE",
borderColor: "#FCFCFC",
borderWidth: "1"
},
emphasis: {
show: true,
areaColor: "#C8A5DF"
}
},
data: [
...
]
}
]

最重要的是 34 个省份的数据,这里我们模拟一下(注意格式和 name 是固定的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
data: [
{ name: "北京", value: Math.round(Math.random() * 1000) },
{ name: "天津", value: Math.round(Math.random() * 1000) },
{ name: "上海", value: Math.round(Math.random() * 1000) },
{ name: "重庆", value: Math.round(Math.random() * 1000) },
{ name: "河北", value: Math.round(Math.random() * 1000) },
{ name: "河南", value: Math.round(Math.random() * 1000) },
{ name: "云南", value: Math.round(Math.random() * 1000) },
{ name: "辽宁", value: Math.round(Math.random() * 1000) },
{ name: "黑龙江", value: Math.round(Math.random() * 1000) },
{ name: "湖南", value: Math.round(Math.random() * 1000) },
{ name: "安徽", value: Math.round(Math.random() * 1000) },
{ name: "山东", value: Math.round(Math.random() * 1000) },
{ name: "新疆", value: Math.round(Math.random() * 1000) },
{ name: "江苏", value: Math.round(Math.random() * 1000) },
{ name: "浙江", value: Math.round(Math.random() * 1000) },
{ name: "江西", value: Math.round(Math.random() * 1000) },
{ name: "湖北", value: 9999 },
{ name: "广西", value: Math.round(Math.random() * 1000) },
{ name: "甘肃", value: Math.round(Math.random() * 1000) },
{ name: "山西", value: Math.round(Math.random() * 1000) },
{ name: "内蒙古", value: Math.round(Math.random() * 1000) },
{ name: "陕西", value: Math.round(Math.random() * 1000) },
{ name: "吉林", value: Math.round(Math.random() * 1000) },
{ name: "福建", value: Math.round(Math.random() * 1000) },
{ name: "贵州", value: Math.round(Math.random() * 1000) },
{ name: "广东", value: Math.round(Math.random() * 1000) },
{ name: "青海", value: Math.round(Math.random() * 1000) },
{ name: "西藏", value: Math.round(Math.random() * 1000) },
{ name: "四川", value: Math.round(Math.random() * 1000) },
{ name: "宁夏", value: Math.round(Math.random() * 1000) },
{ name: "海南", value: Math.round(Math.random() * 1000) },
{ name: "台湾", value: Math.round(Math.random() * 1000) },
{ name: "香港", value: Math.round(Math.random() * 1000) },
{ name: "澳门", value: Math.round(Math.random() * 1000) }
] //数据

其他选项设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
backgroundColor: "#FFFFFF",
title: {
text: "全国省市地图大数据",
subtext: "虚构数据",
x: "center"
},
tooltip: {
trigger: "item"
},
toolbox: { // 右侧复位下载
show: true,
orient: "vertical",
right: "20",
top: "center",
feature: {
mark: { show: true },
restore: { show: true },
saveAsImage: { show: true }
}
},

// 左侧小导航图标
visualMap: {
show: true,
x: "left",
y: "center",
splitList: [
{ start: 10000 },
{ start: 1000, end: 9999 },
{ start: 500, end: 999 },
{ start: 100, end: 499 },
{ start: 10, end: 99 },
{ start: 0, end: 9 }
],
color: [
"#a50026",
"#d73027",
"#f46d43",
"#fdae61",
"#fee090",
"#ffffbf",
"#f0f0f0"
]
},

效果如下:

如何展示中国各市数据

如果要展示各市单独数据较为简单,只需要把地图和对应数据变成省即可

1
2
3
4
5
6
7
8
import hubei from "echarts/map/json/province/hubei";
echarts.registerMap("hubei", hubei);

series:[
...
mapType: "hubei",
...
]

你还可以做到点击省切换成市的效果,网上也有相应教程,实现方法也简单,这里不作详细介绍。

但是我想要看整个中国地图下的市的疫情情况怎么办?

合并省市地图

由于官方只提供了中国地图和省市分开的地图 json 或 js 包,而没有合并在一起的,我们没有现成的数据。

然而我注意到,china 和各省的 json 包只是 features 不同,如下图所示:

1
china.features = [...new Set([...china.features, ...hubei.features])];

最终,这种方法被我放弃了,原因如下:

  1. 视觉效果差:

如下图,有字根本看不清,没字也会很密集

  1. 渲染效果差:

目前只实现了一个省,可预见的是加上所有省份数据量很大(features 长度最终会是 533 ),渲染会卡顿

  1. 有引入延迟问题:

由于需要将 34 个省 JSON 包分别引入后合并到 china 内部,通常 echarts 组件已经渲染完毕

使用中国地图配合市散点图

我找到官方提供的scatter-visualMap-piecewise示例,感觉这种方式也能满足我的要求,且呈现效果更好

选项变化

与原选项主要区别是 series 使用 scatter 散点类型,同时引入 geo 地理坐标系组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
geo: {
map: "china",
roam: true,
itemStyle: {
// 定义样式
normal: {
// 普通状态下的样式
areaColor: "#323c48",
borderColor: "#111"
},
emphasis: {
// 高亮状态下的样式
areaColor: "#2a333d"
}
}
},
series: [
{
name: "确诊人数",
symbolSize: 10, // 点坐标大小
type: "scatter",
data: this.cityData,
coordinateSystem: "geo"
}
]

其数据格式如下

1
2
3
4
5
6
// this.cityData
{
...
{ name: "武汉", value: [114.31, 30.52, 8888 }], // { name: name, value: [x, y, value}],
{ name: "大庆", value: [125.03, 46.58, Math.round(Math.random() * 100) }]
}

示例小问题

需要注意的是,官方示例中 tooltip 的悬浮数据是有问题的,它不是 pm2.5 的 value,而是极坐标的 Y 的值

想要其展示正确的数据需要在 tooltip 设置 formatter:以回调函数格式设置数据为 data 的第三个数据

1
2
3
4
5
6
7
8
tooltip: {
trigger: "item",
formatter: params => {
return (
params.seriesName + "<br/>" + params.name + ":" + params.data.value[2]
);
}
}

最终效果如下:

总结

至此,我们完成了国内各省市疫情模拟数据的可视化,如果想要展示世界数据也是类似,只需找到相应地图包替换,并处理对应数据即可。echarts 提供了完整的文档,按需查找对应选项即可完成相应需求,当然还有很多不错的库和方法等我们去探究。

如需查看完整代码:请点这里

看到这里了,不点个赞吗 😄!