疫情可视化
申明:学习自B站up:小满zs(https://space.bilibili.com/99210573),仅记录一下学习笔记
数据分析
在控制台网络中查看Fetch/XHR里的list 即为疫情数据
初始化项目
服务器端
npm install ts-node -g
npm init -y
npm install @types/node -D //node 申明文件
npm install express -S
npm install @types/express -D //express 申明文件
npm install axios -S
Index.ts
import express,{Express,Router,Request,Response} from 'express'
import axios from 'axios'
const app:Express = express()
// 设置跨域允许
app.use('*',(req,res,next) => {
res.header('Access-Control-Allow-Origin','*')
next();
})
const router:Router = express.Router()
app.use('/api',router)
router.get('/list',async (req:Request, res:Response) => {
const result = await axios.post('https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=localCityNCOVDataList,diseaseh5Shelf')
res.json({
data:result.data
})
})
app.listen(3333,()=>{
console.log('server listening on 3333!')
})
疫情数据接口:http://localhost:3333/api/list(个人的)
添加运行脚本
前端
安装库
npm init vue@latest
npm install
要同时安装less和lessload,其实在vite中只需要安装less即可,另一个可以不用安装,vite官网有说明.sass同理
npm install less
1.基本样式:App.vue
<script setup lang="ts">
// 由于assets文件夹编译后会不见,因此用import
import bg from "./assets/bg.jpg"
</script>
<template>
<div class="box" :style="{background:`url(${bg})`}">
<div class="box-left"></div>
<div class="box-center"></div>
<div class="box-right"></div>
</div>
</template>
<style lang="less">
* {
padding: 0px;
margin: 0px;
}
html,
body,
#app {
height: 100%;
overflow: hidden;
}
.box {
height: 100%;
display: flex;
overflow: hidden;
&-left {
width: 400px;
height: 100%;
}
&-center {
flex: 1;
}
&-right {
width: 400px;
height: 100%;
}
}
</style>
2.安装axios 创建文件夹
npm install axios
import axios from 'axios'
const server = axios.create({
baseURL: "http://localhost:3333",
})
export const getApiList = () => server.get('/api/list').then(res => res.data)
3.修改stores文件夹
import { defineStore } from 'pinia'
import {getApiList} from '../server'
export const useStore = defineStore({
id:'counter',
state:()=>({
list:{},
}),
actions:{
async getList(){
const result = await getApiList()
console.log(result)
}
}
})
3.在app.vue调用
import {useStore} from "./stores"
const store = useStore();
store.getList()
绘制地图
前期准备
npm install echarts --save-dev
引入china.js包
包我放在了源代码里,可自行在最下面源代码里获取
新建geoMap.ts:
export const geoCoordMap: Record<string, Array<number>> = {
'台湾': [121, 23],
'黑龙江': [127, 48],
'内蒙古': [110.3467, 41.4899],
"吉林": [125.8154, 44.2584],
'北京': [116.4551, 40.2539],
"辽宁": [123.1238, 42.1216],
"河北": [114.4995, 38.1006],
"天津": [117.4219, 39.4189],
"山西": [112.3352, 37.9413],
"陕西": [109.1162, 34.2004],
"甘肃": [103.5901, 36.3043],
"宁夏": [106.3586, 38.1775],
"青海": [99.4038, 36.8207],
"新疆": [87.9236, 43.5883],
"西藏": [88.388277, 31.56375],
"四川": [103.9526, 30.7617],
"重庆": [108.384366, 30.439702],
"山东": [117.1582, 36.8701],
"河南": [113.4668, 34.6234],
"江苏": [118.8062, 31.9208],
"安徽": [117.29, 32.0581],
"湖北": [114.3896, 30.6628],
"浙江": [119.5313, 29.8773],
"福建": [119.4543, 25.9222],
"江西": [116.0046, 28.6633],
"湖南": [113.0823, 28.2568],
"贵州": [106.6992, 26.7682],
"云南": [102.9199, 25.4663],
"广东": [113.12244, 23.009505],
"广西": [108.479, 23.1152],
"海南": [110.3893, 19.8516],
'上海': [121.4648, 31.2891],
'香港': [114.30, 22.9],
'澳门': [113.5, 22.2]
};
App.vue:绘制地图
<script setup lang="ts">
// 由于assets文件夹编译后会不见,因此用import
import {onMounted } from "vue"
import bg from "./assets/bg.jpg"
import {useStore} from "./stores"
import * as echarts from "echarts"
import './assets/china'
import {geoCoordMap} from "./assets/geoMap"
import type {Children} from "./stores/types"
onMounted(async()=>{
const store = useStore();
//处理数据
await store.getList()
const city = store.list.diseaseh5Shelf.areaTree[0].children
const data = city.map((v:Children)=>{
return {name: v.name,value:geoCoordMap[v.name].concat(v.total.confirm)}
})
const chart = echarts.init(document.querySelector('#china') as HTMLElement)
//生成地图
chart.setOption({
geo: {
map: "china",
aspectScale: 0.8,
layoutCenter: ["50%", "50%"],
layoutSize: "120%",
itemStyle: {
normal: {
areaColor: {
type: "linear-gradient",
x: 0,
y: 1200,
x2: 1000,
y2: 0,
colorStops: [
{
offset: 0,
color: "#152E6E", // 0% 处的颜色
},
{
offset: 1,
color: "#0673AD", // 50% 处的颜色
},
],
global: true, // 缺省为 false
},
shadowColor: "#0f5d9d",
shadowOffsetX: 0,
shadowOffsetY: 15,
opacity: 0.5,
},
emphasis: {
areaColor: "#0f5d9d",
},
},
regions: [
{
name: "南海诸岛",
itemStyle: {
areaColor: "rgba(0, 10, 52, 1)",
borderColor: "rgba(0, 10, 52, 1)",
// normal: {
opacity: 0,
label: {
show: true,
color: "#009cc9",
},
// },
},
label: {
show: false,
color: "#FFFFFF",
fontSize: 12,
},
},
],
},
series: [
{
type: "map",
// selectedMode: "multiple",
mapType: "china",
aspectScale: 0.8,
layoutCenter: ["50%", "50%"], //地图位置
layoutSize: "120%",
zoom: 1, //当前视角的缩放比例
// roam: true, //是否开启平游或缩放
scaleLimit: {
//滚轮缩放的极限控制
min: 1,
max: 2,
},
label: {
show: false,
color: "#FFFFFF",
fontSize: 16,
},
itemStyle: {
areaColor: "#0c3653",
borderColor: "#1cccff",
borderWidth: 1.8,
emphasis: {
areaColor: "#56b1da",
label: {
show: true,
color: "#fff",
},
},
},
data: data,
},
{
name: 'Top 5',
type: 'scatter',
coordinateSystem: 'geo',
symbol: 'pin',
symbolSize: [45,45],
symbolOffset:[0, '-40%'] ,
label: {
show: true,
// 修改pin起泡展示的数据
formatter(v:any){
// console.log(v)
return v.data.value[2]
},
},
itemStyle: {
// normal: {
color: '#73d13d', //标志颜色
// }
},
data: data,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke'
},
zlevel: 1
},
],
})
})
</script>
解决stores 空对象全局变量的报错问题
使用vscode插件json2ts,将数据放入index.json后 ctrl + alt + v
,即可根据数据自动生成接口
然后在stroes文件夹里引入,即可解决Typescript报错问题,因为如果定义空对象,会默认下面没有其他属性而报错。
绘制表格
1.修改store
添加childrenList数据,用于存储列表数据
2.点击事件
为图表添加监听事件,点击打印相关数据,这里点击将store的childrenLIst进行修改,后续用于列表的数据
chart.on('click',(event:any)=>{
// console.log(city)
console.log(event)
store.childrenList = event.data.source.children
console.log(store.childrenList)
})
3.绘制表格
html:
<table id="customers">
<tr>
<th>名称</th>
<th>治愈</th>
<th>确诊</th>
<th>本土确诊</th>
</tr>
<tr v-for="(item,index) in store.childrenList" :key="index">
<td>{
{item.name}}</td>
<td>{
{item.total.confirm}}</td>
<td>{
{item.today.confirm}}</td>
<td>{
{item.today.local_confirm_add}}</td>
</tr>
</table>
css:
#customers {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#customers td, #customers th {
border: 1px solid #ddd;
padding: 8px;
}
#customers tr:nth-child(even){background-color: #f2f2f2;}
#customers tr:hover {background-color: #ddd;}
#customers tr {background-color: #ddd;text-align: center}
#customers th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4CAF50;
// color: rgb(255, 255, 255);
}
4.添加动画
安装animate.css: npm install animate.css -S
引入:import 'animate.css'
设置群体动画一定要设置key,且唯一,因为index重复后,有的动画不会展示,因此使用uuid解决唯一值
assets文件夹新建getUUID.ts:
export function getUuid () {
if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
const callback = (c) => {
const num = Number(c);
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
};
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, callback);
}
}
let timestamp = new Date().getTime();
let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16;
if (timestamp > 0) {
random = (timestamp + random) % 16 | 0;
timestamp = Math.floor(timestamp / 16);
} else {
random = (perforNow + random) % 16 | 0;
perforNow = Math.floor(perforNow / 16);
}
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
});
};
引入:import {getUuid} from "./assets/getUUID"
修改点击事件:
chart.on('click',(event:any)=>{
// console.log(city)
// console.log(event)
event.data.source.children.map((child:any)=>{
child['index']= getUuid()
})
store.childrenList = event.data.source.children
// console.log(store.childrenList)
})
html:
<transition-group enter-active-class="animate__animated animate__backInRight" tag="tbody">
<tr v-for="(item) in store.childrenList" :key="item.index">
<td>{
{item.name}}</td>
<td>{
{item.total.confirm}}</td>
<td>{
{item.today.confirm}}</td>
<td>{
{item.today.local_confirm_add}}</td>
</tr>
</transition-group>
其他和源代码
其他图表实现原理一样,因此就不展示了,走了一遍流程。