这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2020
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277
这个作业的目标 在网页页面上呈现树形结构形式的师门树
成员 031802643周涛 051803216王长龙
博客链接 周涛 https://www.cnblogs.com/xiangfei1ae/p/13797687.html 王长龙 https://www.cnblogs.com/Sparks-/p/13800962.html
Github链接 https://github.com/Sparks-fzu/031802643-051803216

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 0.5 0.5
Estimate 估计这个任务需要多少时间 0.1 0.1
Development 开发 5 5
Analysis 需求分析 (包括学习新技术) 3 3
Design Spec 生成设计文档 1 1
Design Review 设计复审 0.5 0.5
Coding Standard 代码规范 (为目前的开发制定合适的规范) 0.2 0.2
Design 具体设计 1 1
Coding 具体编码 2 2
Code Review 代码复审 1 1
Test 测试(自我测试,修改代码,提交修改) 1 1.5
Reporting 报告 0.5 0.5
Test Report 测试报告 0.5 0.5
Size Measurement 计算工作量 0.2 0.2
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 0.5 0.5
all 合计 17 17.5

思路描述与设计实现说明

  • 在形成树形结构的模型中,我们查阅了相关材料后,选择应用d3.js库来实现树形结构;
  • 在分解数据中,我们采取了以下的思路
  • 以下是数据解析代码:

   function getdata() {
            var text = $("#text").val(); //获取文本内容
            var eachblock = text.split("\n\n\n");//多组数据输入时,调用split函数,以“\n\n\n"为关键字进行分组
            for (var k = 0; k < eachblock.length; k++) { //eachblock.length用于得到分组的数量
				var smallblock=eachblock[k].split("\n\n");//将大组中(导师、学生信息)与(学生经历)分开
                var arry = smallblock[0].split("\n");//对每一组数据,以“\n"为关键字进行分组,得到每条导师和学生的信息
				var length=smallblock.length-1;//学生经历的数量
				//处理不同学生的经历
				var messages=[];
				for(var i=1;i<=length;i++){
				var personnal=smallblock[i].split(":");//将不同学生经历以“:”为关键字进行分组,得到名字和经历
				var student={};
				student.name=personnal[0];//将学生名字和经历分别存储在对象student中
				student.experience=personnal[1];
				messages.push(student);
				}
                var teacher = {
                        name: '',
                        children: []
                    }
                for (var p = 0; p < arry.length; p++) {
                    var family = arry[p].split(":");//对(导师、学生信息),以“:”为关键字进行分组,得到身份标签和身份信息
                    var type = [];
                    var type1 = [];
                    var label1 = family[0]; //获取身份标签,保存在label
                    if (p != 0) {
                        type1.name = label1;
                    }
                    var label2 = family[1];//获取身份信息,保存在labe2
                    var label3 = label2.split("、");//针对每组身份信息,以“、”为关键字进行切分,得到每个人的名字信息
                    for (var j = 0; j < label3.length; j++) { //label3.length用于得到每条身份信息里名字的数量
                        var student = [];
                        if (p == 0) {
                            teacher.name = label3[j]; //当p等于0是,处理(导师、学生信息)第一行,即导师信息
                        }
                        if (p != 0) {				  //当p不等于0是,处理(导师、学生信息)中的学生信息
                            student.name = label3[j];
							//遍历messags 寻找是否有相对应的学生经历
							for(var i=0;i<length;i++){
							if(label3[j]==messages[i].name){      //若学生名字等于messages中某一个名字,则将经历变为type的子元素
							student.children=[{name:messages[i].experience}];
							}
							}
                            type.push(student);					  //type存储学生姓名

                        }
                    }
                    if (p != 0) {
                        type1.children = type;                   //type1存储类似2015级硕士生的标签,并将type变为type1的子元素
                        teacher.children.push(type1);
                    }
                }

                treeData[k] = [];
                treeData[k] = teacher;
                maketree(k);
            }
        }

在数据解析中,我们首先以“\n\n\n”字符为分解符,将文本分成以导师和学生姓名以及个人经历为块的数组,
对每一块数组再以“/n/n”分块,将导师和学生姓名 与 个人经历 分开,分别对导师和学生姓名块 和 个人
经历块 进行处理和匹配。

  • 以下是树生成代码:
       function maketree(k) {
            var margin = {
                    top: 20,
                    right: 120,
                    bottom: 20,
                    left: 120
                },
                width = 960 - margin.right - margin.left,
                height = 500 - margin.top - margin.bottom;

            var i = 0,
                duration = 750, //过渡延迟时间
                root;

            var tree = d3.layout.tree() //创建一个树布局
                .size([height, width]);



            var diagonal = d3.svg.diagonal()
                .projection(function(d) {
                    return [d.y, d.x];
                }); //创建新的斜线生成器

            //声明与定义画布属性
            var svg = d3.select("body").append("svg")
                .attr("width", width + margin.right + margin.left)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            root = treeData[k]; //treeData为上边定义的节点属性
            root.x0 = height / 2;
            root.y0 = 0;


            update(root);


            function update(source) {
                // Compute the new tree layout.计算新树图的布局
                var nodes = tree.nodes(root).reverse(),
                    links = tree.links(nodes);

                // Normalize for fixed-depth.设置y坐标点,每层占180px
                nodes.forEach(function(d) {
                    d.y = d.depth * 180;
                });

                // Update the nodes…每个node对应一个group
                var node = svg.selectAll("g.node")
                    .data(nodes, function(d) {
                        return d.id || (d.id = ++i);
                    }); //data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定

                // Enter any new nodes at the parent's previous position.新增节点数据集,设置位置
                var nodeEnter = node.enter().append("g") //在 svg 中添加一个g,g是 svg 中的一个属性,是 group 的意思,它表示一组什么东西,如一组 lines , rects ,circles 其实坐标轴就是由这些东西构成的。
                    .attr("class", "node") //attr设置html属性,style设置css属性
                    .attr("transform", function(d) {
                        return "translate(" + source.y0 + "," + source.x0 + ")";
                    })
                    .on("click", click);

                //添加连接点---此处设置的是圆圈过渡时候的效果(颜色)
                // nodeEnter.append("circle")
                //   .attr("r", 1e-6)
                //   .style("fill", function(d) { return d._children ? "lightsteelblue" : "#357CAE"; });//d 代表数据,也就是与某元素绑定的数据。
                nodeEnter.append("rect")
                    .attr("x", -23)
                    .attr("y", -10)
                    .attr("width", 70)
                    .attr("height", 20)
                    .attr("rx", 10)
                    .style("fill", "#000000"); //d 代表数据,也就是与某元素绑定的数据。


                nodeEnter.append("text")
                    .attr("x", function(d) {
                        return d.children || d._children ? 13 : 13;
                    })
                    .attr("dy", "10")
                    .attr("text-anchor", "middle")
                    .text(function(d) {
                        return d.name;
                    })
                    .style("fill", "white")
                    .style("fill-opacity", 1);
                // Transition nodes to their new position.将节点过渡到一个新的位置-----主要是针对节点过渡过程中的过渡效果
                //node就是保留的数据集,为原来数据的图形添加过渡动画。首先是整个组的位置
                var nodeUpdate = node.transition() //开始一个动画过渡
                    .duration(duration) //过渡延迟时间,此处主要设置的是圆圈节点随斜线的过渡延迟
                    .attr("transform", function(d) {
                        return "translate(" + d.y + "," + d.x + ")";
                    });

                nodeUpdate.select("rect")
                    .attr("x", -40)
                    .attr("y", -5)
                    .attr("width", 100)
                    .attr("height", 20)
                    .attr("rx", 10)
                    .style("fill", "#778899");

                nodeUpdate.select("text")
                    .attr("text-anchor", "middle")
                    .style("fill-opacity", 1);

                // Transition exiting nodes to the parent's new position.过渡现有的节点到父母的新位置。
                //最后处理消失的数据,添加消失动画
                var nodeExit = node.exit().transition()
                    .duration(duration)
                    .attr("transform", function(d) {
                        return "translate(" + source.y + "," + source.x + ")";
                    })
                    .remove();

                nodeExit.select("circle")
                    .attr("r", 1e-6);


                nodeExit.select("text")
                    .attr("text-anchor", "middle")
                    .style("fill-opacity", 1e-6);

                // Update the links…线操作相关
                //再处理连线集合
                var link = svg.selectAll("path.link")
                    .data(links, function(d) {
                        return d.target.id;
                    });


                // Enter any new links at the parent's previous position.
                //添加新的连线
                link.enter().insert("path", "g")
                    .attr("class", "link")
                    .attr("d", function(d) {
                        var o = {
                            x: source.x0,
                            y: source.y0
                        };
                        return diagonal({
                            source: o,
                            target: o
                        }); //diagonal - 生成一个二维贝塞尔连接器, 用于节点连接图.
                    })
                    .attr('marker-end', 'url(#arrow)');

                // Transition links to their new position.将斜线过渡到新的位置
                //保留的连线添加过渡动画
                link.transition()
                    .duration(duration)
                    .attr("d", diagonal);

                // Transition exiting nodes to the parent's new position.过渡现有的斜线到父母的新位置。
                //消失的连线添加过渡动画
                link.exit().transition()
                    .duration(duration)
                    .attr("d", function(d) {
                        var o = {
                            x: source.x,
                            y: source.y
                        };
                        return diagonal({
                            source: o,
                            target: o
                        });
                    })
                    .remove();

                // Stash the old positions for transition.将旧的斜线过渡效果隐藏
                nodes.forEach(function(d) {
                    d.x0 = d.x;
                    d.y0 = d.y;
                });
            }

            //定义一个将某节点折叠的函数
            // Toggle children on click.切换子节点事件
            function click(d) {
                if (d.children) {
                    d._children = d.children;
                    d.children = null;
                } else {
                    d.children = d._children;
                    d._children = null;
                }
                update(d);
            }
        }
        //d3.select(self.frameElement).style("height", "500px");

对于该段代码,由于能力有限。。。我们只能做到改变代码对树的形状和线条,以及节点形状的修改。

附加特点设计与展示

  • 在使用方面我们通过js制作了两个开始生成和重新加载的按钮,并在中间建立了一个文本框,进行文件的输入
        <textarea type="textarea" id="text" cols="60" rows="10" class="center" placeholder="文本输入"></textarea>
        <button href="javascript:;" onclick="getdata()" class="button_left">创建师门树</button>
        <input type=button value=重新输入 onclick="location.reload()" class="button_right">
  • 并且对于每个树节点,增加了展开和折叠的功能。

 function click(d) {
                if (d.children) {
                    d._children = d.children;
                    d.children = null;
                } else {
                    d.children = d._children;
                    d._children = null;
                }
                update(d);
            }

可以实现通过点击节点达到子树伸缩的功能。
https://img2020.cnblogs.com/blog/2146984/202010/2146984-20201012125721944-392523311.png

使用说明

  • 将文本按照指定格式,复制粘贴到文本框,在通过点击生成按钮,即可生成树,可以在原树基础上,反复在文本框编辑
    即可生成更多师门树。若想重新加载,点击重新加载按钮,界面清空,可以重新生成树。
  • 效果图测试

单元测试

  • 在单元测试中,我们采用了Mocha测试框架
    http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html
    在该链接中完成了对Mocha的学习
  • 单元测试代码
      var getdata = require('./getdata.js');
      var expect = require('chai').expect;
      var arr='导师:张三\n2016级博士生:天一、王二、吴五\n2015级硕士生:李四、王五、许六\n2016级硕士生:刘一、李二、李三\n2017级本科生:刘六、琪七、司四\n\n刘六:JAVA、数学建模\n\n李二:字节跳动、京东云';
      describe('处理大块数据的测试', function() {
      it('只上传一个大块的情况', function() {
       expect(getdata(arr)).to.be.equal(1);
       });
      });
    
    

Github的代码签入记录截图

代码模块异常或结对困难及解决方法

  • 在创建树的过程中无法生成子节点
  • 通过将对象保存在数组中,数组.children来设置子元素
  • 实现了在节点中加入新的子节点
  • 对js编码有了更深的认识

评价队友

  • 王长龙是一个细致的人,在探讨代码时经常及时指出我的错误,同时也能够高效完成任务。
  • 周涛是一个认真勤奋的人,对于这次编程作业,一直是他在提醒我尽快落实,并做之前他已经找好了大量的相关资料,
    使得我们在html、css、js的学习和编程中较为的轻松。
  • 在代码编写过程中,我们两个经常会出现结构或思路上的冲突,但是双方都考虑不够周到,不能达成共识,需要反复
    的商量。