结对第二次作业——某次疫情统计可视化的实现
这个作业属于哪个课程 | <班级的链接> |
---|---|
这个作业要求在哪里 | <作业要求的链接> |
结对学号 | 221701208、21701225 |
这个作业的目标 | 1、进一步熟悉github操作 2、熟悉与团队成员配合的过程 3、实现简单的疫情统计可视化功能 |
作业正文 | https://www.cnblogs.com/ccqy/p/12507430.html |
其他参考文献 | 百度,CSDN博客 |
1、Github仓库地址
2、成品展示
- 后台删除疫情快讯
- 后台文章列表切换标签演示
- 后台新增疫情快讯演示
- 全国地图切换时间演示
- 全国地图跳转到省份演示
- 全国地图演示
- 全国地图按数据区间显示演示
- 省份切换日期演示
- 省份图表演示
3、结对讨论过程
拿到题目后,我们先确定了使用springboot框架开发并直接使用助教提供的log文件,而后开始分前后端并开始项目的开发。开发过程中主要遇到的困难及解决:
- 开发过程中发现项目中引入echarts所需的相关js文件时本地的js文件无法使用(可能是对框架不熟悉),百度之后遂改为使用url链接引用,使echarts得以显示。
- 在前后端数据交互过程中我们遇到了项目开始以来最令我们头疼的困难:echarts所需的数据的获取。在尝试过json和各种方法未果后,机智的队友换为使用thymelea,问题终于得以解决。
讨论截图:
4.、设计实现过程及功能结构图
- 设计实现过程:
后端读取log数据进行统计处理之后传给前端进行可视化显示,前端的图表数据显示工具为echarts。用户选择的日期和省份数据通过get方法传给后端,用户创建的疫情文章通过post方法传给后端存储。 - 功能结构图:
5、代码说明
- 前端核心代码展示:
前端除了页面布局的代码,最主要的代码便是echrts图表部分,下面是中国地图的配置,主要就是书写点击省份的页面跳转和echarts数据的赋值,都做了。
//引用china.js
<script src="http://gallery.echartsjs.com/dep/echarts/map/js/china.js" charset="utf-8"></script>
<script th:inline="javascript">
var myDate = new Date();
var china_chart = echarts.init(document.getElementById('china_map'));
var data =[[${mapData}]]/*核心数据,包括省的名称,感染/疑似/治愈/死亡人数*/
var yData = [];//省份
var barData = [];//状态条
var option = {
tooltip: {// 鼠标移到图里面的浮动提示框
show: true,
formatter: function(params) {/*显示每个省的数据*/
return params.name + ':\n' +"感染"+ params.data['value'] +"人"+
"疑似"+params.data['suspected']+"人\n"+
"死亡"+params.data['dead']+"人\n";
},
},
// visualMap的详细配置解析
visualMap: { // 左下角的颜色区域
type: 'piecewise', // 定义为分段型 visualMap
min: 0,
max: 1000,
itemWidth: 40,
bottom: 60,
left: 20,
pieces: [ // 将感染人数按数值区间分颜色显示,可以单独选择一个颜色查看某个数值区间有哪些省份
{gt: 10000, lte:50000 , label: '>=10000', color: '#64153e'}, // (900, 1000]
{gt: 1000, lte: 9999, label: '1000-9999', color: '#79316b'}, // (500, 900]
{gt: 100, lte: 999, label: '100-999', color: '#ff4874'}, // (310, 500]
{gt: 10, lte: 99, label: '10-99', color: '#ffbbb3'}, // (200, 300]
{gt: 1, lte: 9, label: '1-9', color: 'orangered'}, // (10, 200]
{value: 0, label: '0', color: '#eddfff'} // [0]
]
},
grid: {
right: 10,
top: 135,
bottom: 100,
width: '20%'
},
xAxis: {//不显示图表的x轴
show: false
},
yAxis: {//不显示图表的x轴
type: 'category',
inverse: true,
nameGap: 16,
axisLine: {
show: false,
lineStyle: {
color: '#ddd'
}
},
axisTick: {
show: false,
lineStyle: {
color: '#ddd'
}
},
axisLabel: {
interval: 0,
margin: 85,
textStyle: {
color: '#455A74',
align: 'left',
fontSize: 14
},
rich: {
a: {
color: '#fff',
backgroundColor: '#FAAA39',
width: 20,
height: 20,
align: 'center',
borderRadius: 2
},
b: {
color: '#fff',
backgroundColor: '#4197FD',
width: 20,
height: 20,
align: 'center',
borderRadius: 2
}
},
formatter: function(params) {
if (parseInt(params.slice(0, 1)) < 3) {
return [
'{a|' + (parseInt(params.slice(0, 1)) + 1) + '}' + ' ' + params.slice(1)
].join('\n')
} else {
return [
'{b|' + (parseInt(params.slice(0, 1)) + 1) + '}' + ' ' + params.slice(1)
].join('\n')
}
}
},
data: yData
},
// geo配置详解
geo: {// 地理坐标系组件用于地图的绘制
//roam: true,
map: 'china',//图表类型
left: 'left',
right: '300',
label: {//地图色块的label显示
show : true
},
itemStyle: {
BorderColor :'rgba(2,0,0,0.2)',
emphasis: {//高亮状态下的多边形和标签样式
shadowBlur:1,//阴影
areaColor: '#fff464'
}
}
},
series: [
{//浮动框,中国地图
name: 'mapSer',
type: 'map',
roam: false,
geoIndex: 0,
label: {
show: false,
},
data: data
},
{//最右边的条形图
name: 'barSer',
type: 'bar',
roam: false,
visualMap: false,
zlevel: 2,
barMaxWidth: 8,
barGap: 0,
itemStyle: {
normal: {
color: function(params) {
var colorList = [{
colorStops: [{
offset: 0,
color: '#FFD119' // 0% 处的颜色
}, {
offset: 1,
color: '#FFAC4C' // 100% 处的颜色
}]
},
{
colorStops: [{
offset: 0,
color: '#00C0FA' // 0% 处的颜色
}, {
offset: 1,
color: '#2F95FA' // 100% 处的颜色
}]
}
];
if (params.dataIndex < 3) {
return colorList[0]
} else {
return colorList[1]
}
},
barBorderRadius: 15
}
},
data: barData
}
]
};
china_chart.on('click', function (param){//定义点击某个省分时的动作
var selected = param.name;//获得被点击的省份的名字
if (selected) {//switch匹配省份
switch(selected){
case '四川':
location.href = "test1?provinceName=四川";//转到四川的省份页面
break;
case '云南':
location.href = "test1?provinceName=云南";
break;
case '西藏':
location.href = "test1?provinceName=西藏";
case '新疆':
location.href = "test1?provinceName=新疆";
break;
case '青海':
location.href = "test1?provinceName=青海";
break;
case '宁夏':
location.href = "test1?provinceName=宁夏";
break;
case '甘肃':
location.href = "test1?provinceName=甘肃";
break;
case '内蒙古':
location.href = "test1?provinceName=内蒙古";
break;
case '陕西':
location.href = "test1?provinceName=陕西";
break;
case '山西':
location.href = "test1?provinceName=山西";
break;
case '重庆':
location.href = "test1?provinceName=重庆";
break;
case '贵州':
location.href = "test1?provinceName=贵州";
break;
case '广西':
location.href = "test1?provinceName=广西";
break;
case '海南':
location.href = "test1?provinceName=河南";
break;
case '广东':
location.href = "test1?provinceName=广东";
break;
case '湖南':
location.href = "test1?provinceName=湖南";
break;
case '湖北':
location.href = "test1?provinceName=湖北";
break;
case '河南':
location.href = "test1?provinceName=河南";
break;
case '河北':
location.href = "test1?provinceName=河北";
break;
case '北京':
location.href = "test1?provinceName=北京";
break;
case '天津':
location.href = "test1?provinceName=天津";
break;
case '黑龙江':
location.href = "test1?provinceName=黑龙江";
break;
case '吉林':
location.href = "test1?provinceName=吉林";
break;
case '辽宁':
location.href = "test1?provinceName=辽宁";
break;
case '山东':
location.href = "test1?provinceName=山东";
break;
case '江苏':
location.href = "test1?provinceName=江苏";
break;
case '安徽':
location.href = "test1?provinceName=安徽";
break;
case '上海':
location.href = "test1?provinceName=上海";
break;
case '浙江':
location.href = "test1?provinceName=浙江";
break;
case '福建':
location.href = "test1?provinceName=福建";
break;
case '江西':
location.href = "test1?provinceName=江西";
break;
case '台湾':
location.href = "test1?provinceName=台湾";
break;
case '香港':
location.href = "test1?provinceName=香港";
break;
case '澳门':
location.href = "test1?provinceName=澳门";
break;
default:
break;
}
}
});
china_chart.setOption(option);
</script>
2.后端核心代码
业务层的InfectStatistic类(由上次个人作业修改而来)包装分析日志得到的各种数据,返回给Controller层
这里以返回省份所需的echarts数据为例
public List<Object> getChartData(String provinceName,LocalDate date){
Province province=country.getProvince(provinceName);
ArrayList<DailyInfo> dailyInfos=province.getAllDailyInfo();
ArrayList<String> xAxisDate=new ArrayList<>();
ArrayList<Integer> infected=new ArrayList<>();
ArrayList<Integer> suspected=new ArrayList<>();
ArrayList<Integer> dead=new ArrayList<>();
ArrayList<Integer> cured=new ArrayList<>();
for(DailyInfo dailyInfo:dailyInfos){
if(dailyInfo.date.isAfter(date)) {
break;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M-d");
xAxisDate.add(dailyInfo.date.format(formatter));
infected.add(dailyInfo.infected);
suspected.add(dailyInfo.suspected);
dead.add(dailyInfo.dead);
cured.add(dailyInfo.cured);
}
List<Object> chartData=new ArrayList<>();
chartData.add(xAxisDate.toArray());
chartData.add(infected.toArray());
chartData.add(suspected.toArray());
chartData.add(dead.toArray());
chartData.add(cured.toArray());
return chartData;
}
Controller层处理请求,并根据传参情况,由业务层的类指导日志分析,并取得所需的数据传给页面模板
@RequestMapping("/country") //全国页面
public String testCountry(Model map,String newDate){
LocalDate date;
InfectStatistic infectInfoOperator = new InfectStatistic();
infectInfoOperator.readLogs();
if(newDate!=null) {
date = LocalDate.parse(newDate);
}
else{
date=infectInfoOperator.getEndDate();
}
DailyInfo countryStatistic = infectInfoOperator.getCountryStatistic(date);
DailyInfo countryChange = infectInfoOperator.getCountryChange(date);
List<CountryMapData> mapData = infectInfoOperator.getCountryMapData(date);
map.addAttribute("statistic",countryStatistic);
map.addAttribute("change",countryChange);
map.addAttribute("mapData",mapData);
map.addAttribute("articles",localRepository.getList("全国"));
return "country";
}
疫情快讯管理方便,由于时间问题,没有使用数据库,使用一个ArticleRepository类来实现疫情快讯的增删查
以增加文章为例,判断标签后插入对应的文章列表中:
public void addArticle(Article article) {
String tag=article.getTag();
//如果仓库中没有该标签的文章列表,就新建对应的列表
ArrayList<Article> list=articleRepository.get(tag);
if(list==null){
list=new ArrayList<>();
articleRepository.put(tag,list);
list.add(article);
}
else{
list.add(article);
}
}
6、《构造之法》阅读笔记
- 关于代码规范
- 代码设计规范。牵涉到程序设计,模块之间的关系,设计模式等方方面面的通用原则。
- 代码风格规范。主要是文字上的规范,看似表面文章,实际上非常重要。代码风格规范要求简明,易读,无二义性。
- 结对编程
每人在各自独立设计、实现软件的过程中不免要犯这样那样的错误。j结对编程有如下好处:
- 在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作解决问题的能力更强。
- 对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
- 在企业管理层次上,结对能更有效地交流,相互学习和传递经验,分享知识, 能更好地应对人员流动。
- 团队的特点
- 团队有一致的集体目标,团队要一起完成这目标。一个团队的成员不一定要同时工作,例如接力赛跑。
- 团队成员有各自的分工,互相依赖合作,共同完成任务。
- 随着团队的成熟和环境变化,团队模式会演化成以下几种模式:
- 主治医师模式:有一个主刀医师,各司其职,为主刀医师服务。
- 明星模式:主治医师模式运用到极点可以蜕化为明星模式,此时明星的光芒盖过了团队综合。
- 社区模式:每个人参与各自感兴趣的项目,众人拾柴火焰高。
- 业余剧团模式:这样的团队在每一个项目(剧目)中,不同的人会挑选不同的角色。在下一个剧目中,这些人也许会换一个完全不同的角色类型。各人在团队中听从一个中央指挥(导演)的指导和安排。
- 秘密团队:一些软件项目在秘密状态下进行,别人不知道他们具体在做什么。这种模式的好处是:团队内部有极大的自由,没有外界的干扰(不用每周给别人介绍项目进展,听领导的最新指示,等等),团队成员有极大的投入。
- 特工团队:软件行业的一些团队由一些有特殊技能的专业人士组成,负责解决一些棘手而有紧迫性的问题。这些团队成员必须了解传统语言和老式系统,才能胜任这样的任务。现在还有一些专门做网站安全性服务的团队,也属于这一类型。
心路历程&结对感悟
队员1:叶尤澎
心路历程:
- 刚发作业时看到只有六天还是不怎么着急的(事实证明这就是在作),但是随着开发遇到一些问题比如前端echarts拿不到合适格式的数据时,我慌了,百度各种方法试过还是拿不到还是很绝望的,差点都想建议直接在页面将数据写死(但这意味着队友的后端白忙活了),幸运地是队友后来尝试使用thymeleaf成功了以下为当时的截图。
结对感悟:
- 结对开发要及时和队友交流并提前做好模块分工,这样开发时互不影响。
- 队员互帮互助可以提高解决问题的效率。
- 结对良好的氛围可以降低开发时的压力,比如时不时开玩笑聊聊天。
- 评价队友
队友做事严谨,个人认为他属于稳中求进的性格且开发工具的掌握比我熟练,经常能够知道我。
队员2:陈启元
- 心路历程:
跟队友一样,刚开始看到任务感觉给的时间是有点短,不过看要开发的功能也算简单,心中思路也算清晰就没在意,一直拖到了最后两天。结果在觉得会很简单的前后端传值上翻车了,试了试用jsp和json都出现很多问题。哎,谁叫自己都是现学现卖懂些皮毛。。正好又遇上github实训,真的好烦啊。还好作业推迟了一天,我得以转用框架推荐的页面模板来实现传参,以及实现附加的疫情快讯相关的功能。结果也还算好。
- 收获:软件开发的时间估计很容易不准确,特别是对于我们这种比较菜的,更应该尽量早开始开发,给自己留更多的余地。还有就是还是要多学习,一知半解害死人。
- 评价队友:队友是舍友,所以交流很顺畅可以无所顾忌,合作过程比较愉快。然后稍微小吐槽一点:就是界面做的感觉不是很美观(笑)。不过也没办法,时间有限,再加上本来我们就都是想学后端的,前端做不好也很正常。