前言
2020年开始就黑天鹅不断,美伊搞翻,英国脱欧,俄罗斯政改。对我们国内来说最要命的是眼瞅要过年了,又来了个新型冠状病毒。疫情发展迅速,比非典有过之而无不及。看着医护人员一个个冲锋在前,我们却只能靠在家里躺着为国家做贡献。N天过后,却发现整天葛优躺也会把人憋坏。既不能上前线,又闲的难受。不如活动活动,索性做个疫情监测系统。也给大家提供一条不同的了解疫情的途径。
在这之前,笔者也参与过不少医院信息化相关的项目,比如去年参与的上海某知名妇产科医院的智慧医院 3D 可视化系统。现在在国内新医改政策的推动下,各大医疗机构对医疗信息化和智能化的需求增长迅速。而像智慧医疗、智慧临床、云上医疗、AI 辅助诊断等的不断应用,即让医疗机构能够拥有更高的服务效率,更优的资源配置,更低的运营成本。同时也可以为患者提供更便捷和人性化的服务。
下面是上海某知名妇产科医院的效果:
结合之前在医疗可视化方面的经验,对于这次疫情地图,为了有别于现有诸多页面的千篇一律,让大家更直观方便的了解实时疫情信息。我这里在网页前端同时结合了 2D 和 3D。
先来看页面加载效果:
预览地址:http://www.hightopo.cn/demo/coronavirus/
系统介绍:
该系统总共包括两部分,分别是 2D 数据面板和 3D 地图。
- 左侧的每日统计数据,该数据显示最近一段时间每天的确诊人数,并根据疫情变化定时刷新。同时,该部分还与地图及右侧数据联动。切换不同的日期后,地图颜色及右侧详细信息会跟着显示历史数据。
- 表格详细信息,该表格用来显示各省及各市的疫情详细信息。包括疑似,确诊,治愈,死亡数据。该表格数据根地图及每日统计数据联动。
- 疫情增长柱状图,该柱状图由康复,确诊,死亡三部分组成。显示近7日的疫情数据。
- 各省颜色随动变化,即各省区域颜色根据该省确诊人数变化,确诊人数越多,颜色越深。
- 武汉地区人口输出动画,即武汉地区输出人口到各省的比例。
- 指定省份突出显示,用户点击某个省份后,该省份在地图上高亮显示,同时,右侧表格会显示当前省份的详细数据。点击背景恢复显示全国数据。
系统开发:
1. 寻找数据源
既然各大网站都提供疫情实时地图,我们就没必要在这上面多花时间。官方的如疾控中心 (CDC), 大厂如 BBA 三家。还有最近异军突起的丁香园。
- CDC: http://2019ncov.chinacdc.cn/2019-ncov/
- 百度:https://voice.baidu.com/act/newpneumonia/newpneumonia
- 腾讯:https://news.qq.com/zt2020/page/feiyan.htm
- 丁香园:https://ncov.dxy.cn/ncovh5/view/pneumonia
打开各家网站经过在 console 里面一顿查找,最终选中其中一家的数据作为来源。
这里的数据都是标准的 json 格式,具体怎么绑定到 2D 部分难度不大,不在此展开说明。下面主要介绍 3D 部分的几处关键代码。
2. 关键代码实现:
1) 根据每个地区疫情的不同情况,将该地区标记为不同颜色。
首先挑选合适的颜色,之后,由浅到深定义这几种颜色到对象 MyConst:
1 const MyConst = {
2 COLOR_0: 'rgb(235,235,235)',
3 COLOR_9: 'rgb(245,165,130)',
4 COLOR_99: 'rgb(190,30,10)',
5 COLOR_999: 'rgb(130,25,25)',
6 COLOR_9999: 'rgb(77,5,5)',
7 };
再根据每个省的确诊数量使用不同的颜色。this.areaDataObj 保存的是各个省的数据对象,其内容如下:
最后,根据每个地区的确诊人数(total_confirm)为该地区设置不同的颜色。代码如下:
1 refreshAreaColor() {
2 for (const key in this.areaDataObj) {
3 if (Object.prototype.hasOwnProperty.call(this.areaDataObj, key)) {
4 const dataNode = this.areaDataObj[key].data; // 省区域 Node
5 const total_confirm = this.areaDataObj[key].total_confirm; // 该省累计确诊数量
6 switch (true) {
7 case total_confirm == 0:
8 dataNode.s('shape3d.blend', MyConst.COLOR_0);
9 break;
10 case total_confirm <= 9:
11 dataNode.s('shape3d.blend', MyConst.COLOR_9);
12 break;
13 case total_confirm <= 99:
14 dataNode.s('shape3d.blend', MyConst.COLOR_99);
15 break;
16 case total_confirm <= 999:
17 dataNode.s('shape3d.blend', MyConst.COLOR_999);
18 break;
19 default:
20 dataNode.s('shape3d.blend', MyConst.COLOR_9999);
21 break;
22 }
23 }
24 }
25 }
2) 动画展示
根据官方数据,武汉地区总人口超过1400万,春节前流出人口有500多万。这些人员的去向也是大家关心的问题。这里增加了武汉到国内各省的人口流动动画。另外,每个城市点也增加了动画来进行位置展示。
上图总共包括三部分动画:
- 每个城市点的转动;
- 每个城市点圆柱增长动画;
- 城市飞线(人口流动)的动画;
这三个动画可以通过一个函数实现,代码如下。要注意 data.setRotationMode() 方法不同的坐标轴顺序会导致不同的显示效果。
1 start3DAnim() {
2 // 设置城市点旋转模式
3 this.cityPoints.forEach((data) => {
4 data.setRotationMode('zxy');
5 });
6
7 let uv_offset = 0;
8 let point_ang = 0;
9 let c_h = 2, wh_c_h = 2;
10 const wuhanCylinder = this.dm3d.getDataByTag('wuhanCylinder'); // 武汉地区单独处理
11 setInterval(()=> {
12 point_ang = point_ang + 0.01;
13 uv_offset = uv_offset + 0.01;
14
15 c_h = c_h + 0.2;
16 if(c_h > 20) {
17 c_h = 2;
18 }
19 wh_c_h = wh_c_h + 0.6;
20 if(wh_c_h > 60) {
21 wh_c_h = 2;
22 }
23 // 城市点旋转
24 this.cityPoints.forEach((val) => {
25 val.setRotationZ(point_ang);
26 });
27 // 城市点圆柱增长
28 this.cylinders.forEach((val) => {
29 val.setTall(c_h);
30 });
31 // 武汉地区单独处理
32 wuhanCylinder.setTall(wh_c_h);
33 // 城市间飞线
34 this.movingLines.forEach((val) => {
35 val.s('shape3d.uv.offset', [-uv_offset, 0]);
36 });
37
38 }, 30);
3) 鼠标移动到省区域之上时悬浮高亮
要实现悬浮高亮需要利用 onHover 或者 onEnter 和 onLeave 事件。由于 onHover 需要悬停一段事件才能触发,为了提高反应速度,这里通过检测 onEnter 和 onLeave 事件修改区域的边界颜色来突出某个省。this.proviences 为各个省名称的集合。this.proviences.indexOf(selectedProvience) >= 0 用来确保只有指定的省区域上面才执行悬浮高亮。
代码如下:
1 handleInteractive3d(e) {
2 let {
3 data, // 事件指向的对象
4 kind // 事件类型
5 } = e; // e为事件
6 if (!data) {
7 return;
8 }
9
10 const selectedProvience = data.getDisplayName(); // 省名称
11
12 // 悬浮高亮
13 const onEnterBorderColor = "rgb(250,250,87)";
14 const onLeaveBorderColor = "rgb(61,61,61)";
15 if (kind == 'onEnter' && this.proviences.indexOf(selectedProvience) >= 0) {
16 this.updateAreaBorder(data, onEnterBorderColor);
17 return;
18 }
19 if (kind == 'onLeave' && this.proviences.indexOf(selectedProvience) >= 0) {
20 this.updateAreaBorder(data, onLeaveBorderColor);
21 return;
22 }
23 ……
24 }
25
26 updateAreaBorder(data, color) {
27 data.s("wf.color", color); // 更新边框颜色
28 }
4) 点击某个省将其从地图拉高,同时弱化其他区域并更新表格内容。点击背景恢复原样。
思路是这样:拉高某个区域可通过改变其 y 方向的值。弱化其他区域可通过设置透明度来实现。代码如下:
1 handleInteractive3d(e) {
2 let {
3 data,
4 kind
5 } = e;
6 // 如果点击背景,则恢复区域颜色及y轴方向的值。
7 if (kind == 'clickBackground') {
8 this.updateAreaOpacity(1); //还原透明度
9 this.updateTable(); // 恢复表格内容为全国数据
10 return;
11 }
12
13 if (!data) {
14 return;
15 }
16
17 const selectedProvience = data.getDisplayName();
18
19 // 点击拉高
20 if (kind == 'clickData' && this.proviences.indexOf(selectedProvience) >= 0) {
21 this.showProvienceData(selectedProvience);
22 }
23 }
24 25
26 updateAreaOpacity(opacity) {
27 for (const key in this.areaDataObj) {
28 if (Object.prototype.hasOwnProperty.call(this.areaDataObj, key)) {
29 const dataNode = this.areaDataObj[key].data;
30 dataNode.s('shape3d.opacity', opacity); // 改变透明度
31 const [x, y, z] = dataNode.getPosition3d();
32 dataNode.setPosition3d(x, 0, z); // 恢复Y方向位置
33 }
34 }
35 }
36
37 showProvienceData(selectedProvience) {
38 const data = this.provAreas[selectedProvience];
39 this.updateAreaOpacity(0.3); // 通过设置透明度为0.3来弱化周围区域
40 data.s('shape3d.opacity', 1); // 设置透明度为1来突出选中区域
41
42 const [x, y, z] = data.getPosition3d();
43 if (y == 0) {
44 data.setPosition3d(x, 5, z); // 拉高该区域
45 } else {
46 data.setPosition3d(x, 0, z);
47 }
48 this.updateTable(selectedProvience); // 更新表格内容为该省数据
49 }
结语:
数据可视化越来越普及,在工业物联网、电信、智慧医疗、智能交通等行业都有广泛的应用。但依我看来,数据可视化不仅仅是将数据用某种图表展示出来。更重要的,是要给大家带来良好的用户体验。数据不但要展示,更要展示的优美、协调、重点突出。传统的 2D 普通页面已经成为过去式。WebGL 的出现给我们提供了更丰富的途径(3D)来展示原本呆板的数据。
正所谓,人们对美好事物的向往就是我们前端程序猿的奋斗目标。希望这次肺炎疫情能够尽快得到控制。武汉加油,中国加油!
来源:https://www.cnblogs.com/htdaydayup/p/12273402.html |