结对第二次作业——某次疫情统计可视化的实现
这个作业属于哪个课程 | 2020春|S班(福州大学) |
---|---|
这个作业要求在哪里 | 结对第二次作业 |
结对学号 | 221701336、221701331 |
这个作业的目标 | 某次疫情统计可视化的实现 |
作业正文 | 结对第二次作业博客 |
其他参考文献 | 《腾讯web代码规范》、Echarts官方文档及教程、CSDN |
一、Github仓库与代码规范
二、成品展示
点击链接
三、结对讨论
最开始我们讨论并学习了Echarts的用法
然后对是否使用爬虫技术获得数据进行了讨论
获得了json数据文件
需求讨论阶段
后续编码过程
四、设计实现
功能结构图
- 数据部分
- 此次实践的数据来自助教提供的文件,借助寒假第二次作业中实现的数据统计工具(进行了一些修改,并辅以手工调整),生成了上文第三部分中符合我们约定格式的json文件。由于纯前端无法方便的访问本地文件,我们把json格式的数据改写成js文件,这样页面就可以通过<script>标签直接访问数据。
- 出于方便的考虑,我们用了两个工具函数
getSpecifiedData
和getStatisticData
(见下文第五部分)进行数据读取,提升了语义化。
- 前端部分
- 我们采用的是
jQuery
库原生html+css+js来编写网页。可视化的重任放在了优秀的图标库Echarts
上,通过引入中国地图的china.js
文件并在echarts的option.geo.map声明使用,实现了中国地图可视化部分的显示。有关可视化的细节请移步第五部分。 - 以生成的中国地图作为首页,点击省份后跳转到该省份的趋势图页面。后来考虑到用户体验的问题,我们决定使用layer弹层,借助弹出窗口展示各省份的趋势图,优化用户操作的连贯性。
- 用户可以通过切换时间查看不同时刻的疫情地图和统计数据,为此我们绑定了change/click事件,触发时自动取数据并更新到地图/趋势图上。得益于echarts可以异步加载或更新数据,这一过程还是比较轻松的。
- 同样出于用户体验的考虑,在查看历史记录时,我们动态限制了比例尺的上限,避免了一个省份颜色太深而其他省份太浅的状况。
- 最后完善样式和页面时,模仿了浏览器的标签页进行类型切换,并使用css3的transition,让切换变得更加平滑。
- 我们采用的是
- 后端&爬虫
- 暂未实现
五、代码说明
首先是地图,主要还是依靠导入echarts.js以及China.js,然后对相应的类和属性注入数据
- tooltip实现了鼠标悬浮在各省上,显示相应数据
- visualMap则设置了页面左下角的颜色标注块的各项属性
- geo设定了type为中国地图,并对各类缩放及文本格式进行设置
let option = {
title: {
text: '2020-02-02累计确诊情况地图'
},
tooltip: {
formatter: function (params, ticket, callback) {
return params.seriesName + '<br />' + params.name + ':' + params.value
} //数据格式化
},
visualMap: {
min: 0,
max: 1500,
left: 'left',
top: 'bottom',
text: ['高', '低'], //取值范围的文字
inRange: {
color: ['#ffffff', '#974236'] //取值范围的颜色
},
show: true //图注
},
geo: {
map: 'china',
roam: false, //不开启缩放和平移
zoom: 1.23, //视角缩放比例
label: {
normal: {
show: true,
fontSize: '10',
color: 'rgba(0,0,0,0.7)'
}
},
itemStyle: {
normal: {
borderColor: 'rgba(0, 0, 0, 0.2)'
},
emphasis: {
areaColor: '#F3B329', //鼠标选择区域颜色
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 20,
borderWidth: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
},
series: [{
name: '确诊人数',
type: 'map',
geoIndex: 0,
data: dataList
}]
}
这一部分则是实现了点击省份弹窗曲线图窗口
myChart.on('click', function (params) {
console.log(params)
const href = './province.html?province=' + params.data.name
layer.open({
type: 2,
title: params.data.name + '省疫情详细情况',
shadeClose: true,
shade: 0.8,
area: ['840px', '750px'],
content: href
})
})
这是地图页面的时间选择部分,通过监听下拉菜单的选取事件,调用getSpecifiedData函数获取数据生成dataList并更新进echarts
$('#date-picker').on('change', e => {
const newDate = e.target.value,
type = $('.type-btn--active')[0].dataset.index
typeText = $('.type-btn--active')[0].innerText
dataList = provinces.map(provinceName => ({
name: provinceName,
value: getSpecifiedData(type, provinceName, newDate).infected
}))
option.series[0].data = dataList
let max = Math.max(...dataList.map(v => v.value), 50)
max = max > 2000 ? 2000 : max // 避免差异过大
option.visualMap.max = max
option.title.text = newDate + typeText + '情况地图'
myChart.setOption(option)
})
通过该部分代码,在曲线图页面鼠标悬浮显示相应数据,重点是掌握param的各项参数,常用的包含有:
- param.name:X轴的值
- param.data:Y轴的值
- param.value:Y轴的值
- param.type:点击事件均为click
- param.seriesName:legend的名称(即'累计确诊''新增确诊''累计治愈''累计死亡')
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
},
formatter: function(params) {
let html = params[0].name + '<br>'
for (const param of params) {
html += param.seriesName + ' ' + param.data + '<br>'
}
return html
}
},
曲线图数据分配
以湖北省新增感染为例:通过getStatisticData函数获得数据存储在augmentInfectedRecord里,并通过echarts的data属性获取数据
const augmentInfectedRecord = getStatisticData(2, 'infected', province)
data = [{
name: '新增确诊',
type: 'line',
data: augmentInfectedRecord
},
两个用于读取数据的函数
/**
* 获取指定类型、日期、省份的数据(无合法性校验)
* @param {String|Number} scale 1:累计, 2:当日
* @param {String} province 省份名,默认全国
* @param {String} date YYYY-MM-DD格式的日期(某日或截至某日的数量),默认最新
* @returns {Object}
*/
function getSpecifiedData (scale, province = '全国', date = '2020-02-02') {
scale = scale == 1 ? 'aggregate' : 'augment'
let result = {}
const empty = {
"infected": 0,
"suspect": 0,
"cure": 0,
"death": 0
}
try {
result = log[scale][date][province]
} catch(e) {
console.warn(e)
result = empty
}
return result || empty
}
/**
* 获取指定省份、类型的所有数据(无合法性校验)
* @param {String|Number} scale 1:累计, 2:当日
* @param {String} type 数据类型,infected|suspect|death|cure
* @param {String} province 省份名,默认全国
* @returns {Array}
*/
function getStatisticData (scale, type, province = '全国') {
scale = scale == 1 ? 'aggregate' : 'augment'
let result = []
Object.values(log[scale]).forEach(day => {
if (day[province]) {
result.push(day[province][type])
} else {
result.push(0)
}
})
return result
}
统计数据的格式
const log = Object.freeze({
"aggregate": {
"2020-01-19": {
"广东": {
"infected": 1,
"suspect": 0,
"cure": 0,
"death": 0
},
...
},
...
}
"augment": {...}
}
六、心路历程和收获&评价结对队友
此次实践比我预想的要复杂一些,原型工工具制作的很轻松,但是实际上手之后,发现有不少知识是不会的。学了echarts中国地图的导入,曲线图的导入,各个图表type的各属性用法,然后还学习了后面相关的一些前端的知识。由于不是很懂json文件的数据是如何导出的,也学习了一下。我们组讨论的比较早,但是由于手头都还有作业,前期只是进行了知识的储备,算是比较晚开工的,之前我做了页面demo,编写了代码规范,一起跟队友讨论了具体需求。后续确定路线后才开工。
我的队友是个西二的前端大佬,很靠谱,也很让人安心。我的代码比较差,前端简单做做两个页面的demo,将图表框架建立起来,然后学习了他的代码后敲了些数据传输和样式表的内容。因为我个人也是偏向于学习前端的技术,这次实践从他那学到了很多。