疫情可视化(后续)

  • 疫情可视化(后续)已关闭评论
  • 93 次浏览
  • A+
所属分类:Web前端
摘要

这是疫情可视化最开始的文章,有需要了解的可前往查看:https://www.cnblogs.com/xi12/p/16690119.html。 本来说有时间就把这个项目完结了的,结果后面一直有事拖着,直到现在十一月份了才搞完。老样子,先看成果。


前言

这是疫情可视化最开始的文章,有需要了解的可前往查看:https://www.cnblogs.com/xi12/p/16690119.html。 本来说有时间就把这个项目完结了的,结果后面一直有事拖着,直到现在十一月份了才搞完。老样子,先看成果。

修改与添加

后面可能审美疲劳了,越看越丑,就干脆直接用dataV(这可不是阿里的那个dataV)修饰页面了。这是项目改动后的样子:
疫情可视化(后续)
疫情可视化(后续)
疫情可视化(后续)
疫情可视化(后续)
疫情可视化(后续)
主要做了如下改动:

  1. 添加首页的3d粒子球体。
  2. 完成“省内分析”模块。
  3. 完成当地疫情报告生成与下载。
  4. 使用dataV修饰页面。

粒子球体

粒子球体也是用three.js完成的,放大仔细看会发现其实是有三层构成的:内层透明球体,中间点状球体,外层病毒球体。
疫情可视化(后续)

具体实现过程是这样的:

  1. 先绘制一个内层透明球体,记得将球体材质的transparent设为true,然后设置不透明度opacity值。
//创建斑点球体 async function createSpotSphere() {   let globeBufferGeometry = new THREE.SphereGeometry(earthSize - 1, 50, 50);//球体几何体   let globeInnerMaterial = new THREE.MeshBasicMaterial({     color: new THREE.Color(dvColor[0]),//颜色     // blending: THREE.AdditiveBlending,//纹理融合的叠加方式     // side: THREE.FrontSide,//前面显示     transparent: true,//透明     // depthWrite: false,//深度写入     // depthTest: false,//黑洞效果     opacity: .3,//不透明度   });   let globeInnerMesh = new THREE.Mesh(     globeBufferGeometry,     globeInnerMaterial   );   earthGroup.add(globeInnerMesh); //将网格放入地球组   cre 
  1. 创建点状球体。这里需要使用一张黑白的世界地图。
let img = new Image(); img.src = earthGrayscale; //黑白地图 

将图片使用onload加载到项目中后,利用canvas绘制一遍该图,再使用getImageData获取到像素点数据canData。

let canvas = document.createElement("canvas");     canvas.width = img.width; //使得canvas尺寸与图片尺寸相同     canvas.height = img.height;     (canvas.getContext("2d") as any).drawImage(img, 0, 0, img.width, img.height);//canvas绘制图片     let canData = (canvas.getContext("2d") as any).getImageData(0, 0, canvas.width, canvas.height);//获取画布像素数据 

利用canData .data中的rgba信息生成缓冲几何顶点数组globeCloudVerticesArray。

let globeCloudBufferGeometry = new THREE.BufferGeometry(); //设置缓冲几何体     let globeCloudVerticesArray = []; //地球云缓冲几何体顶点     let o = null; //数组处理时的计数     for (o = 0; o < canData.data.length; o += 4) {       let r = (o / 4) % canvas.width,         i = (o / 4 - r) / canvas.width;       if ((o / 4) % 2 == 1 && i % 2 == 1)         if (0 === canData.data[o]) {           let n = r,             longitude = (i / (canvas.height / 180) - 90) / -1, //经度             latitude = n / (canvas.width / 360) - 180; //维度           let s = latLongToVector3(longitude, latitude, earthSize, .1); //经纬度变换           globeCloudVerticesArray.push(s); //将变换后的顶点放入数组         }     } 

然后再使用three中的BufferAttribute生成属性position与color。

let l = new Float32Array(3 * globeCloudVerticesArray.length); //创建顶点数组长度     for (o = 0; o < globeCloudVerticesArray.length; o++) {       l[3 * o] = globeCloudVerticesArray[o].x;//设置顶点数组数据       l[3 * o + 1] = globeCloudVerticesArray[o].y;       l[3 * o + 2] = globeCloudVerticesArray[o].z;     }     let positionVal = new THREE.BufferAttribute(l, 3); //设置缓冲区属性值     globeCloudBufferGeometry.setAttribute("position", positionVal); //给缓冲几何体添加位置属性     let globeCloudMaterial = new THREE.PointsMaterial({       color: new THREE.Color(dvColor[1]),//颜色       fog: true,       size: 1,     });//球面斑点材质     let d = new Float32Array(3 * globeCloudVerticesArray.length), c = [];     for (o = 0; o < globeCloudVerticesArray.length; o++) {       c[o] = new THREE.Color(dvColor[1]);//球面斑点颜色       d[3 * o] = c[o].r;//设置地球云数组rgb颜色       d[3 * o + 1] = c[o].g;       d[3 * o + 2] = c[o].b;     }     let color_val = new THREE.BufferAttribute(d, 3);     globeCloudBufferGeometry.setAttribute("color", color_val);//给缓冲几何体添加颜色属性,修改颜色直接修改globeCloudBufferGeometry的setAttribute 

最后再使用THREE.Points创建球面的点,将position与color属性添加到点的几何体BufferGeometry中。

let globeCloud = new THREE.Points(//球面的象素点       globeCloudBufferGeometry,       globeCloudMaterial     ); 

这是需要用到的坐标转换方法:

//经纬度坐标变换(传入e:纬度、a经度、t球半径、o球额外距离) function latLongToVector3(e: any, a: any, t: any, o: any) {   var r = (e * Math.PI) / 180,     i = ((a - 180) * Math.PI) / 180,     n = -(t + o) * Math.cos(r) * Math.cos(i),     s = (t + o) * Math.sin(r),     l = (t + o) * Math.cos(r) * Math.sin(i);   return new THREE.Vector3(n, s, l); //计算三维向量 }; 
  1. 创建最外层的病毒球体。每一个病毒都是一个THREE.Sprite,材质是THREE.SpriteMaterial,利用Sprite.position.set设置具体坐标位置,最后将Sprite添加到球体组中。病毒的颜色是由当前点确诊值决定的,颜色值在colors数组中取出。
//创建病毒 function createVirus(data: any, earthSize: any) {   let colors = [     new THREE.Color(0xf9b8b8),     new THREE.Color(0xfe4242),     new THREE.Color(0xff0000),   ]; //病毒颜色列表   let virSize = 4; //病毒大小   let list = JSON.parse(JSON.stringify(data));   list.forEach((e: { value: number; color: any; position: any[]; }) => {     e.value >= 10000000 && (e.color = colors[2]); //根据病毒数赋予不同颜色     e.value >= 500000 && e.value < 10000000 && (e.color = colors[1]);     e.value < 500000 && (e.color = colors[0]);     if (e.position) {       let virusMaterial = new THREE.SpriteMaterial({         color: e.color,         map: new THREE.TextureLoader().load(virusImg),         side: THREE.FrontSide, //只显示前面       }); //病毒材质       let Sprite = new THREE.Sprite(virusMaterial); //点精灵材质       Sprite.scale.set(virSize, virSize, 1); //点大小       let lat = e.position[1]; //纬度       let lon = e.position[0]; //经度       let s = latLongToVector3(lat, lon, earthSize, 1); //坐标转换       Sprite.position.set(s.x, s.y, s.z); //设置点的位置       Sprite.dotData = e; //将点的数据添加到dotData属性中       Sprite.name = "病毒";       earthGroup.add(Sprite); //将病毒添加进球体组中     }   }); }; 

省内分析

  1. 省内数据概况。
    疫情可视化(后续)
    该数值在加载时是有增加动画的,是利用vue响应式原理完成的,在上一篇文章中有提到。
  2. 省内各地疫情柱状图。
    疫情可视化(后续)
    这是具体的echart配置:
let option = {         title: {             text: provinceBaseData.value.province + "各地数据",             left: "center",             top: '5%',             textStyle: {                 color: "#fff",             },         },         tooltip: {             trigger: 'axis',             axisPointer: {                 type: 'shadow'             }         },         dataZoom: [             {                 type: 'inside',             },             {                 show: true,                 yAxisIndex: 0,                 filterMode: 'empty',                 width: 25,                 height: '70%',                 showDataShadow: false,                 left: '3%',                 top: "center"             }         ],         legend: {             data: ['累计数', '治愈数', '确诊数', '较昨日新增', '死亡数'],             orient: "vertical",             top: "15%",             right: "2%",             textStyle: {                 color: "#fff"             },         },         grid: {             left: '3%',             right: '15%',             bottom: '10%',             containLabel: true         },         xAxis: {             type: 'category',             data: echartData.cityName,             axisLabel: {                 interval: 0,                 rotate: 50,                 color: "#fff"             },         },         yAxis: {             type: 'value',             axisLabel: {                 color: "#fff",             },         },         series: [             {                 name: '累计数',                 type: 'bar',                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#f59158'                 },                 data: echartData.conNum             },             {                 name: '治愈数',                 type: 'bar',                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#48c56b'                 },                 data: echartData.cureNum             },             {                 name: '确诊数',                 type: 'bar',                 stack: 'total',                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#ffd889'                 },                 data: echartData.econNum             },             {                 name: '较昨日新增',                 type: 'bar',                 stack: 'total',                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#794ebd'                 },                 data: echartData.asymptomNum             },             {                 name: '死亡数',                 type: 'bar',                 stack: 'total',                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#ff6a6a'                 },                 data: echartData.deathNum             },         ]     }; 
  1. 省份历史疫情数据图表。
    疫情可视化(后续)
    这是该图表配置:
let option = {         title: {             text: provinceBaseData.value.province + "历史数据",             left: "center",             top: '5%',             textStyle: {                 color: "#fff",             },         },         tooltip: {             trigger: 'axis',             axisPointer: {                 type: 'cross',                 label: {                     backgroundColor: '#6a7985'                 }             }         },         legend: {             data: ['累计数', '确诊数', '较昨日新增', '治愈数', '死亡数'],             orient: "vertical",             top: "15%",             right: "2%",             textStyle: {                 color: "#fff"             },         },         grid: {             left: '8%',         },         xAxis: [             {                 type: 'category',                 boundaryGap: false,                 axisLabel: {                     color: "#fff",                 },                 data: echatrData.time             }         ],         yAxis: [             {                 type: 'value',                 axisLabel: {                     color: "#fff",                 },             }          ],         dataZoom: [             {                 startValue: ''             },             {                 type: 'inside'             }         ],         series: [             {                 name: '累计数',                 type: 'line',                 stack: 'Total',                 smooth: true,                 lineStyle: {                     width: 0                 },                 showSymbol: false,                 areaStyle: {                     opacity: 0.8,                     color: "#f59158"                 },                 emphasis: {                     focus: 'series'                 },                 itemStyle: {                     color: '#f59158'                 },                 data: echatrData.conNum             },             {                 name: '确诊数',                 type: 'line',                 stack: 'Total',                 smooth: true,                 lineStyle: {                     width: 0                 },                 showSymbol: false,                 areaStyle: {                     opacity: 0.8,                     color: "#ffd889"                 },                 itemStyle: {                     color: '#ffd889'                 },                 emphasis: {                     focus: 'series'                 },                 data: echatrData.econNum             },             {                 name: '较昨日新增',                 type: 'line',                 stack: 'Total',                 smooth: true,                 lineStyle: {                     width: 0                 },                 showSymbol: false,                 label: {                     show: true,                     position: 'top'                 },                 areaStyle: {                     opacity: 0.8,                     color: "#794ebd"                 },                 itemStyle: {                     color: '#794ebd'                 },                 emphasis: {                     focus: 'series'                 },                 data: echatrData.asymptomNum             },             {                 name: '治愈数',                 type: 'line',                 stack: 'Total',                 smooth: true,                 lineStyle: {                     width: 0                 },                 showSymbol: false,                 areaStyle: {                     opacity: 0.8,                     color: "#48c56b"                 },                 itemStyle: {                     color: '#48c56b'                 },                 emphasis: {                     focus: 'series'                 },                 data: echatrData.cureNum             },             {                 name: '死亡数',                 type: 'line',                 stack: 'Total',                 smooth: true,                 lineStyle: {                     width: 0                 },                 showSymbol: false,                 areaStyle: {                     opacity: 0.8,                     color: "#ff6a6a"                 },                 itemStyle: {                     color: '#ff6a6a'                 },                 emphasis: {                     focus: 'series'                 },                 data: echatrData.deathNum             },         ]     }; 

报告生成与下载

报告生成利用的是docxtemplater,这是前端生成word比较方便的一个插件,具体使用方法可以看这里:https://www.cnblogs.com/xi12/p/16863383.html。

  • 这是生成的word报告示例:
    疫情可视化(后续)

dataV修饰

结语

  • 最后我发现人的审美真的变化太快了。
  • 项目刚搞完:嗯不错!真漂亮!
  • 过去一周后:什么玩意!丑得一批!
  • 如果还有后续的话,我可能就要考虑添加自定义主题配色了。