2020软工第二次结对作业——关系树生成器
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277 |
这个作业的目标 | 尝试用软件工程的思想编写一个小型应用软件 |
学号 | 031802129 031802133 |
github地址 | https://github.com/robinxlh/031802129-031802133 |
结对名单
谢林煌 031802133
吴涣祺 031802129
具体分工
谢林煌 :小部分页面设计、JavaScript算法页面及可视化设计,
吴涣祺 :页面设计、单元测试,
PSP
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/SE2020 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2020/homework/11167 |
这个作业的目标 | 编写出一个实验室关系树Web应用 |
学号 | 031802129 031802133 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 10 | 40 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 500 | 600 |
Design Spec | 生成设计文档 | 40 | 60 |
Design Review | 设计复审 | 30 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
Design | 具体设计 | 80 | 90 |
Coding | 具体编码 | 800 | 900 |
Code Review | 代码复审 | 60 | 90 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 140 |
Reporting | 报告 | ||
Test Report | 测试报告 | 60 | 40 |
Size Measurement | 计算工作量 | 20 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1720 | 2050 |
解题思路描述与设计实现说明
分析:
1、设计数据格式,对指定的数据进行编码测试处理,查看是否符合预期
2、编写简单的html,验证输入,并为下一步准备
3、编写css + 用d3处理数据,实现数据可视化,生成关系树状图。
4、多棵树问题,及删除之前提交的树 + 网页排版
代码实现思路
- messege()实现对输入数据的处理
- find()检查树的根节点是否出现过,判断是生成新树还是合并树
- Trim()对字节多余空字符进行处理
- init1()对处理后的数据可视化,绘制出树状图,拖拽,节点点击处理,并对树状图进行处理如鼠标进过会生成节点显示此人详细信息,对节点颜色调整,使得可视化能否延展,有无信息。
- 简单好看的图形界面,排版。
关键实现的流程图或数据流图
重要的/有价值的代码片段
- 这段代码是平均每行花费时间最长的代码,主要实现的是在节点下创建一个
节点,鼠标移入节点时可以显示当前节点的title属性,也就是树节点的skill内容,遇到了各种困难。
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.attr("title",function(d){
if(d.skill.length==0){
return "对不起,此人没有添加详细信息";
}
else return d.skill;
})
.on("mouseover",function(d) { //鼠标移上事件
// this.append('<div id="tooltip">' + '123' + "</div>"); //创建提示框,添加到页面中
var div = document.createElement("div");
div.id="tooltip";
div.textContent=this.getAttribute("title");
// this.appendChild(div);
body1.appendChild(div);
var posX = 0, posY = 0;
var event = event || window.event;
if (event.pageX || event.pageY) {
posX = event.pageX;
posY = event.pageY;
} else if (event.clientX || event.clientY) {
posX = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
posY = event.clientY + document.documentElement.scrollTop + document.body.scrollTop;
}
// $("#tree").appendChild(div);
$("#tooltip").css({
// left: (d.x + 30) + "px",
// top: d.y + "px",
left: (posX + 30) + "px",
top: posY + "px",
opacity: "0.8"
}).show(250) //设置提示框的坐标,并显示
})
.on("mouseout",function(d){
$("#tooltip").remove();
})
.on("click", click);
- 这个时对输入数据进行分析处理的函数,输入信息后,先对三个换行符进行分割,此时数组每个都是一颗树。
- 然后对每棵树进行两个换行符的分割,此时数组第一个为关系信息,数组后面则为某个人的详细信息。
- 先对数组后面的详细信息分割,并以名字为键,信息为值,保存在skillman对象中,方便之后建树时可直接插入信息。
- 然后对第一个进行一个换行符的分割,此时第一行即数组第一个为导师,另外的对其是学生关系,对学生以冒号分割,将年级也看成是导师的子节点,学生节点加在年纪的节点下面。
- 另外每次分割拿出名字时,调用
Trim()
函数,目的是防止名字前后有空格,影响之后的详细信息匹配。 - 最后查看之前保存的对象skillman有没有此人的详细信息,若有,则将详细信息写入skill中,若无则放空。
- 在读入第二树的数据的导师时,会检查Name对象调用
find()
函数,看看之前一棵树有没有出现该人,若有这加在之前的一棵树上,若无,则另立新树。
数据处理部分代码,要考虑的东西太多了,甚至可以无境的完善:
function messege() {
var mdata = document.getElementById("rk").value;
// var mdata=shuru;
var n = document.getElementById( 'tree' ).childNodes.length;
for ( var i = n-1; i >= 0; i--) {
document.getElementById( 'tree' ).removeChild(
document.getElementById( 'tree' ).childNodes[i]);
}
var n = document.getElementById( 'tree' ).childNodes.length;
for ( var i = 0; i < n; i++) {
document.getElementById( 'tree' ).removeChild(
document.getElementById( 'tree' ).childNodes[i]);
}
var trees = mdata.split("\n\n\n"); //分割树
var position = '';
var name = '';
var skill = '';
for (var i = 0; i < trees.length; i++) { //第几个树
var page = trees[i].split("\n\n"); //分割关系与信息
var skillman = {}; //清空技能人
for (var j = 0; j < page.length; j++) {
if (j == 0) { //关系
continue; //最后执行,先获得信息
} else { //人物信息 //先
var rela = page[j].split('\n'); //分割行
for (var k = 0; k < rela.length; k++) {
var hang = rela[k].split(':');
name = Trim(hang[0]);
skill = Trim(hang[1]);
skillman[name] = skill;
}
}
}
var now = [];
var po = i;
var rela = page[0].split('\n'); //分割行
var number1 = 0;
for (var k = 0; k < rela.length; k++) {
var hang = rela[k].split(':'); //注意符号中文
position = Trim(hang[0]);
name = Trim(hang[1]);
if (position == "导师") { //导师
var node = {
"name": name,
"children": [],
"tree": 0,
"skill": ""
};
if (skillman.hasOwnProperty(name)) {
node.skill = skillman[name];
}
find1 = 0;
now = find(name);
po = now.length;
if (find1) { //没找到用json
now[po] = node;
now = now[po].children;
}
} else { //学生
number1 = now.length;
var name1 = name.split("、");
var node1 = { //属于级别
"name": position,
"children": [],
"tree": 0,
"skill": ""
};
for (var k1 = 0; k1 < name1.length; k1++) { //学生
name1[k1]=Trim(name1[k1]);
var node2 = {
"name": name1[k1],
"children": [],
"tree": 0,
"skill": ""
}
if (skillman.hasOwnProperty(name1[k1])) { //这个学生有没有技能
node2.skill = skillman[name1[k1]];
}
Name[name1[k1]] = 1;
node1.children[k1] = node2;
}
now[number1] = node1;
}
}
}
for(var i=0;i<json.length;i++){ //生成多颗树
init1(i);
}
Name={}; //防止干扰下一次提交
json=[];
}
- 这是深搜代码,会将树重新排序,方便插入,试了各种深搜,比如在排序的时候查找插入,可是考虑情况太多了,出了各种bug,最后换成现将树预处理一下,在送进去
var zname={};
var trees2=[];
var ztname=[];
var zttn=[];
for(var i=0;i<trees.length;i++){ //重新排列树顺序
var page = trees[i].split("\n\n"); //分割关系与信息
var rela = page[0].split('\n'); //分割行 只要每棵树信息就好
var teacn='';
var name5='';
for(var j=0;j<rela.length;j++){
var hang = rela[j].split(':'); //注意符号中文
var position = Trim(hang[0]);
var name = Trim(hang[1]);
if(j==0){
ztname[i]=name;
zttn[i]=-1;
name5=name;
if(zname.hasOwnProperty(name)==0){
zname[name]={};
}
}
else {
var name1 = name.split("、");
for (var k1 = 0; k1 < name1.length; k1++){
name1[k1]=Trim(name1[k1]);
zname[name5][name1[k1]]=1;
}
}
}
}
for(var i=0;i<ztname.length;i++){
for(var j=0;j<ztname.length;j++){
if(i!=j){
if(zname[ztname[j]].hasOwnProperty(ztname[i])){
zttn[i]=j;
break;
}
}
}
}
var allt=0;
for(var i=0;i<ztname.length;i++){
if(zttn[i]==-1){
allt=1;
}
}
if(!allt){
alert("语法错误,该图非树,为环");
return ;
}
for(var i=0;i<trees.length;i++){
for(var j=0;j<trees.length;j++){
if(zttn[j]!=-2){
if(zttn[j]==-1){
trees2.push(trees[j]);//加入
zttn[j]=-2;
break;
}
else if(zttn[zttn[j]]==-2){
trees2.push(trees[j]);
zttn[j]=-2;
break;
}
}
}
}
trees=trees2;
- css部分:这将让你的按钮变得闪闪发光(blingbling)。
@-webkit-keyframes bluePulse {
from {
background-color: #007d9a;
-webkit-box-shadow: 0 0 9px #333;
}
50% {
background-color: #2daebf;
-webkit-box-shadow: 0 0 18px #2daebf;
}
to {
background-color: #007d9a;
-webkit-box-shadow: 0 0 9px #333;
}
}
.blue.button {
-webkit-animation-name: bluePulse;
-webkit-animation-duration: 4s;
}
附加特点设计与展示
树的并列
可以存在多颗树
树的合并
若树根节点是某棵树的叶节点则可以自动合并(若非树为环会提示)(即使这颗树是之后的树叶节点的扩张也能找到,但不能一个人有多个导师(怎么可能有两个导师呀))
节点的可视化
节点蓝色说明节点可点击延展,节点之后还有子树,节点黑色说明节点没有添加详细信息,节点白色说明节点有添加详细信息,可查看
又优化了一下,这回年级会有紫色显示了,告诉用户这不是人
- 赶紧去复习落下的课了,前后面图片都是旧的了
详细信息的查看
鼠标移动到节点上即可查看详细信息
小细节
-
虚化的格式提示
-
树可以鼠标拉拽移动,以及滑轮放大缩小
-
输入框的固定,不随鼠标滑动移动,方便数据修改及对比查看
-
重复提交会将之前生成的树删除后,再画新树,而非继续往下累计
目录说明和使用说明
目录说明
│ README.md
│ word_process.js
│
├─demo1
│ background.jpg
│ index.html
│ jquery-1.12.3.min.js
│ playground.css
│ v3.js
│
└─test_program
│ messege.js
│
└─test
messege.test.js
使用说明
单元测试
- 测试工具 Mocha + Istanbul
- 安装方式:
- 使用 npm 指令在安装局部的npm
npm i -D mocha
- 修改package.json
{ "scripts": { "test": "nyc mocha --timeout=3000" } }
- 测试用例编写:在 test 文件夹中创建 xx.test.js 文件(xx为被测试文件的文件名),然后在xx.test.js编写测试用例即可。
- 运行
- 在 test 文件夹内打开命令行界面,输入
npm test
即可运行
- 在 test 文件夹内打开命令行界面,输入
- 相关代码
- 用于测试的数据
var test1 = "导师: ZZZ\ \n2016 级博士生: 天二、 王二、 吴五\ \n2015 级硕士生: 李四、 王五、 许六\ \n2016 级硕士生: 刘一、 李二、 李三\ \n2017 级本科生: 刘六、 琪七、 司四\ \n\n刘六: JAVA、 数学建模\ \n\n李二: 字节跳动、 京东云"; //全部信息按照格式的情况 var test2 = "导师:XLH\ \n2016级博士生:天一、王二、吴五\ \n2015级硕士生:李四、王五、许六\ \n2016级硕士生:刘一、李二、李三\ \n\n刘六:JAVA、数学建模\ \n\n李二:字节跳动、京东云\ \n\n李二:字节跳动、京东云"; //在学生信息中输入重复信息的情况 var test3 = "导师:WHQ\ \n\n刘六:JAVA、数学建模\ \n\n李二:字节跳动、京东云"; //在学生信息中输入不相关信息的情况 var test4 = "导师:YH\ \n2016级博士生:\ \n2015级硕士生:\ \n2016级硕士生:\ \n2017级本科生:\ \n\n刘六:JAVA、数学建模\ \n\n李二:字节跳动、京东云"; //届数信息为空的情况 var test5 = "导师:WPH\ \n2016级博士生:天一、王二、吴五\ \n2015级硕士生:李四、王五、许六\ \n2016级硕士生:刘一、李二、李三\ \n\n刘六:JAVA、数学建模\ \n\n\n导师:李二\ \n2016级博士生:天、王、吴\ \n2015级硕士生:四、五、六\ \n2016级硕士生:刘、李、李R\ \n\n刘:JAVA、数学建模"; //有导师的学生再做导师的情况
- 测试代码
describe('数据生成树测试', function() { it('测试样例一:信息完整', function() { assert.equal(messege(test1), ans1); }); it('测试样例二:有重复学生信息', function() { assert.equal(messege(test2), ans2); }); it('测试样例三:学生信息与导师不相关', function() { assert.equal(messege(test3), ans3); }); it('测试样例四:导师届数为空', function() { assert.equal(messege(test4), ans4); }); it('测试样例五:导师的学生在做导师', function() { assert.equal(messege(test5), ans5); });
【注】:ans1-ans5 分别保存着正确树形数据的 str 数据
-
测试结果
-
测试思路
我们的程序只需要一个格式化的 json 对象,就可以根据这个对象生成一颗对应的树,所以只要这个对象是正确的,生成的树就是正确的。所以我们编写了这个测试工具,ans中存放着正确的 json 数据,test中存放着输入的原始字符串数据,然后经过测试检验,如果得到正确的结果,就可以认为转换成功,生成的树正确。
Github的代码签入记录截图
遇到的代码模块异常或结对困难及解决方法
调试问题
- 第一次上手前端,贼多不熟悉的,感觉最近一个星期里学了贼多东西,前期代码调试真是举步维艰,使用浏览器的
console.log()
传统调试方法,进度捉急; - 解决:强烈推荐vscode的Debug for Chrome插件,直接起飞
数据输入问题
- 初次对输入的数据处理,在后面的调试过程中发现若名字前后带有空格会使数据无法与之后输入的人物信息相匹配
- 解决:用正则对数据进行处理,删除名字前后无用的空格。
function Trim(str)
{
return str.replace(/(^\s*)|(\s*$)/g, "");
}
- 最后一行是换行符,会出现无法输出的问题
- 解决:用正则对初始字符串字符串处理。
- 当输入的数据树根节点为之后的树叶节点是,也能精准插入
- 解决:试了各种角度的深搜,最后选择了预处理的时候深搜
树的交互问题
- 重复点击提交,之前的树仍然会存在
- 解决:检查标签id=tree下有没有过去的树,若有,对树删除。
var n = document.getElementById( 'tree' ).childNodes.length;
for ( var i = n-1; i >= 0; i--) {
document.getElementById( 'tree' ).removeChild(
document.getElementById( 'tree' ).childNodes[i]);
}
-
提高节点的可视化
-
解决:让不同状态的节点显示不同的颜色
-
想设计一个鼠标移到节点显示详细信息,了解了jquery的tooltips之后,成功添加了标签,却老是无法显示出添加的标签,以为是标签被svg标签覆盖,可无论怎么设置标签z轴的大小都无法显示,走了好多弯路
-
解决:交互信息不能用attr添加到svg的标签中,应该用on添加,还是别在svg中创建标签,直接在整体上创建标签,然后移到节点上后立刻获取鼠标位置信息,将该位置信息赋给显示详细信息的
标签,让用户看得更舒服。
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.attr("title",function(d){
if(d.skill.length==0){
return "对不起,此人没有添加详细信息";
}
else return d.skill;
})
.on("mouseover",function(d) { //鼠标移上事件
var div = document.createElement("div");
div.id="tooltip";
div.textContent=this.getAttribute("title");
// this.appendChild(div);
body1.appendChild(div);
var posX = 0, posY = 0;
var event = event || window.event;
if (event.pageX || event.pageY) {
posX = event.pageX;
posY = event.pageY;
} else if (event.clientX || event.clientY) {
posX = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
posY = event.clientY + document.documentElement.scrollTop + document.body.scrollTop;
}
// $("#tree").appendChild(div);
$("#tooltip").css({
// left: (d.x + 30) + "px",
// top: d.y + "px",
left: (posX + 30) + "px",
top: posY + "px",
opacity: "0.8"
}).show(250) //设置提示框的坐标,并显示
})
.on("mouseout",function(d){
$("#tooltip").remove();
})
.on("click", click);
评价你的队友
WHQ:
针不戳!和LH一起编程针不戳!!!
感觉抱了大佬的大腿,一起学了半天的 JS 和 HTML 的各种基础知识,最后开始干的时候
核心功能的大部分都是林煌写的。我就找了一个 D3 生成树的模板和需要的步骤,林煌就
开始码了!然后我就开始设计网站的外观和逻辑。林煌学习能力很强,learning by doing
在他身上体现的非常明显。而且 coding 的时候非常认真,能够一个接一个的解决问题。
XLH:
- 值得学习的地方:能肝,结对过程中曾多次一起肝到了凌晨3、4点,然后白天一起没精神。git bash、vscode调试大佬。前端遇到的各种问题基本都能解决,直接让我没有“前”顾之忧(少学了好多,只用大概了解一下,工作量直接减少,不然现学现卖真的好难完成这次作业)。
- 太急了,起步前认为搞不完,没完成前每天心慌慌,带着我也慌。