d3.js Tree

本文探讨了D3.js和Echarts在数据可视化领域的区别,包括D3.js使用SVG绘制,支持DOM操作,而Echarts依赖于canvas,更易用但灵活性较低。同时介绍了它们的浏览器兼容性和应用场景。

一、功能:
    1.增加节点
    2.删除节点
    3.上下移动节点
    4.保存
    5.画布可放大缩小
    6.单击节点选中,双击节点折叠
二、知识点:
    1.D3.js(官网:D3.js - Data-Driven Documents)(我是小白)
        echarts和D3的作用相似,那他们有什么不同之处呢?我觉得他们之间最大的不同之处就在于echarts它是使用canvas来绘制图形的,而D3是通过Svg来绘制图形的。这两者的不同之处在于,svg可以操作dom支持事件处理器,想要实现某个操作,直接调用相关的方法实现效果就行,他那个里面存在链式语法,这个和 jQuery 的链式调用差不多,简单易读。canvas不支持事件处理器所以只能展示数据,而不能修改。

D3使用svg绘制图形,echarts使用canvas绘制图形

D3兼容IE9及以上主流浏览器,echarts兼容IE6及以上主流浏览器

D3使用灵活,学习成本大,echarts封装好的,使用简单,不够灵活 

  2.广度遍历的方式
      英文缩写为BFS即Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进  入下一层。对于上面的例子来说,广度优先遍历的 结果是:1,2,3,4,5,6..(假设每层节点从上向下访问)。
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>D3 tree</title>
</head>
<style>
    .node circle {
        width: 39px;
        height: 39px;
        fill: #92BFFF;
        stroke: #92BFFF;
        stroke-width: 1.5px;
    }
    .circleStyle{
        width: 40px;
        height: 40px;
        background-color: #92BFFF;
    }
    .node text {
        font-size: 16px;
    }
    .link {
        fill: none;
        stroke: #3b8cff;
        stroke-width: 1.5px;
    }
    .tree{
        width: 1500px;
        height: 800px;
        margin: 0 auto;
        background-color: #eeeaea;
    }
    .tree svg{
        width: 100%;
        height: 100%;
    }
    .node img{
        width: 20px;
        height: 20px;
        display: block;
    }
    .button{
        margin-bottom: 20px;
        width: 500px;
        display: flex;
        justify-content: space-between;
    }
    .button div{
        padding: 5px 20px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        color: #fff;
        background-color: #3b8cff;
        border-radius: 5px;
        font-size: 16px;
        display: inline;
        text-align: center;
    }
</style>
<body>
<div class="button">
    <div class="delete">删除节点</div>
    <div class="add">增加节点</div>
    <div class="move_up">上移</div>
    <div class="move_down">下移</div>
    <div class="preservation_btn">保存</div>
</div>
<div class="tree" id="tree"></div>
</body>
<script src="../js/jquery-3.1.1.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
    var  data =  [
        {
            "name": "二级开关1",
            'delayed':'2', //延时
            "children": [
                {
                    "name": "分支1",
                    'delayed':'0',
                    "children":[
                        {"name": "开关1",'delayed':'0'},
                        {"name": "开关2",'delayed':'0'},
                        {"name": "开关3",'delayed':'0'},
                    ]
                }
            ]
        },
        {
            "name": "二级开关2",
            'delayed':'2', 
            "children":[
                {"name": "分支2",
                  'delayed':'0',},
                {"name": "分支3",
                  'delayed':'0',},
            ]
        },
        {
            "name": "二级开关3",
            'delayed':'3', //延时
            "children":[
                {"name": "分支2",'delayed':'9',},
                {"name": "分支3",'delayed':'8',},
              ]
        },
    ];
    var treeData = [
        {
            "name":"开始",
            "id":"-1",
            "children":data
        }
    ];
    var margin = [20, 120, 20, 120],
        width = document.getElementById("tree").offsetWidth,
        height = document.getElementById("tree").offsetHeight;
    var i = 0,
        duration = 750,
        root,
        lastNum = 0;
    var tree = d3.layout.tree() //创建d3树布局,并定义画布大小
        .size([height, width])
    var diagonal = d3.svg.diagonal()
        .projection(function(d) { return [d.y, d.x]; });
    var zoom = d3.behavior.zoom().scaleExtent([0.1, 100]).on("zoom", zoomed);//添加放大缩小事件
    var svg = d3.select("body").select("#tree").append("svg")
        .call(zoom).on("dblclick.zoom", null)//给svg绑定zoom事件
        .append("g")
        .attr("transform", "translate(" + margin[3] + "," + margin[0] + ")")
        .call(zoom).on("dblclick.zoom", null)//给g绑定zoom事件
        .append("g");
    function zoomed() { //根据操作更改svg的大小位置
        svg.attr("transform",
            "translate(" + zoom.translate() + ")" +
            "scale(" + zoom.scale() + ")"
        );
    }
    var arr = [];
    var domInfo;
    root = treeData[0];
    root.x0 = height / 2;
    root.y0 = 0;
    update(root);  //根据数据,创建节点以及连线
    // 传入一个数组
    // 按照特定方式格式化
    function sortArr(arr, str) {
        var _arr = [],
            _t = [],
            _tmp;  // 临时的变量
        // 按照特定的参数将数组排序将具有相同值得排在一起
        arr = arr.sort(function(a, b) {
            var s = a[str],
                t = b[str];
            return s - t;
        });
        if (arr.length ){
            _tmp = arr[0][str];
        }
        // 将相同类别的对象添加到统一个数组
        for (var i in arr) {
            if ( arr[i][str] === _tmp ){
                _t.push( arr[i] );
            } else {
                _tmp = arr[i][str];
                _arr.push( _t );
                _t = [arr[i]];
            }
        }
        // 将最后的内容推出新数组
        _arr.push( _t );
        return _arr;
    }
    function update(source) {
        // 计算新树的布局
        var nodes = tree.nodes(root);
        var links = tree.links(nodes);
        //树的广度方式遍历渲染执行顺序序号
        sortArr(nodes,'depth')
        var num = 0;
        var prevDelayed = 0;
        var prevDepth = 1;
        nodes.forEach(function(d) {
            // 树的深度这里树d.y。
            d.y = d.depth * 180;
            //数字排序顺序:自增1(1,2,3,4...)
             d.order  = num++;
            //数字排序顺序:1.同层级排序 2.同层级延时为0时序号一样 3.不同层级递增1 (领导暂时不用了)
            // if(d.id !== '-1'){
            //     console.log(Number(d.delayed));
            //     if(prevDepth === d.depth){
            //         if(Number(d.delayed) == 0){
            //             d.order = num;
            //             if(prevDelayed !== Number(d.delayed) ){
            //                 d.order = num = num+1;
            //             }
            //         }else{
            //             if(prevDelayed === Number(d.delayed) && Number(d.delayed) === 0){
            //                 d.order = num;
            //             }else{
            //                 d.order = num = num+1;
            //             }
            //         }
            //     }else{
            //         d.order = num = num+1;
            //     }
            // }else{
            //     d.order = 0;
            //     d.delayed = 0;
            //     d.depth = 0;
            // }
            // prevDelayed = Number(d.delayed)
            // prevDepth = Number(d.depth)
        });
       //数据连接,根据id绑定数据,最初新点开的节点都没有id,为没有id的节点添加上ID
        var node = svg.selectAll("g.node")
            .data(nodes, function(d) { return d.id || (d.id = ++i); });
        //点击时增加新的子节点
        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
            .on("dblclick",click)
            .on("click", function(d){
                domInfo = d;
                var color;
                $(".text").each(function(i,item){
                    d3.select(item).style('fill', '#666');
                    d3.select(item).attr("text-decoration","none");
                    d3.select(item).attr("font-weight","nromal");
                });
                $(".rectIcon").each(function(i,item){
                        if(item.parentNode.id == "-1"){
                            color = "#42FF42"
                        }else{
                            color = "#92BFFF"
                        }
                    d3.select(item).style('fill', color);
                });
                var sel1 = d3.select(this.childNodes[2]);
                var selIcon = d3.select(this.childNodes[0]);
                sel1.style('fill', '#3b8cff');
                sel1.attr("text-decoration","underline");
                sel1.attr("font-weight","bold");
                selIcon.style('fill', '#3B8CFF');
            })
            .style("cursor",'pointer')
            .attr("id",function(d){
                return d.id
            });
        nodeEnter.append("rect")
            .attr("class","rectIcon")
            .attr("r", 1e-6)
            .attr("x",-13)
            .attr("y",-13)
            .attr("width",26)
            .attr("height",26)
            .attr("rx",50)
            .style("fill", function(d){
                if(d.id == '-1'){
                    return "#42FF42"
                }else{
                    return "#92BFFF"
                }
            });
        //这个是添加数字的哦。
        nodeEnter.append("text")
            .style("fill",'#fff')
            .attr("font-size",14)
            .attr('class','number')
            .attr("y", 6)
            .attr("text-anchor", 'middle')
            .text(function(d,i) {
                if(d.id == '-1'){
                     return 0;
                }else{
                     return d.order;
                }
            });
        //执行时根据状态显示图标
        // nodeEnter.append("svg:image")
        //     .attr("xlink:href",function(d){
        //         // console.log(d);
        //         var icon = 'https://static.dwtyun.com/img/portal/pc/login/login_dwtyun_logo.png';
        //         if(d.depth == 3){
        //             icon = 'https://img-blog.csdnimg.cn/20201014180756754.png?x-oss-process=image/resize,m_fixed,h_64,w_64';
        //         }else if(d.depth == 2){
        //             icon = 'https://avatars.observableusercontent.com/avatar/c29cf4d85eece3bf402bbaffe1e260c6e5881a490c99c6a1e52a8a2f3f6fb10e?s=128';
        //         }
        //         return  icon;
        //     })
        //     .attr("width",26)
        //     .attr("height",26)
        //     .attr("x",-10)
        //     .attr("y",-10);
        var text = nodeEnter.append("text")
             .attr("x", function(d) { return d.children || d._children ? -15 : 15; })
            .attr('class','text')
            .attr("dy", ".35em")
            .style("fill",'#666')
            .attr("font-size",16)
            .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
            .text(function(d) { return d.name; })
            .style("fill-opacity", 1e-6);
        //执行的时候才显示
        text.selectAll("tspan")
            .data(['设备连接超时'])
            .enter()
            .append("tspan")
            .attr('class','tspan')
            .attr("x", function(d) { return d.children || d._children ? -15 : 15; })
            .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
            .attr("dy","1.5em")
            .style("fill",'#999')
            .attr("font-size",10)
            .text(function(d){
                if(d.id == '-1'){
                    return '';
                }else{
                    return '未执行';
                }
            });
        //原有节点更新到新位置
        var nodeUpdate = node.transition()
            .duration(duration)
            .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
        nodeUpdate.select("text")
            .text(function(d,i) {
                if(d.id == '-1'){
                    return 0;
                }else{
                    return d.order;
                }
            });
        nodeUpdate.select(".text")
            .attr("x", function(d) { return d.children || d._children ? -15 : 15; })
            .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
            .style("fill-opacity", 1)
        nodeUpdate.select("tspan")
            .attr("x", function(d) { return d.children || d._children ? -15 : 15; })
            .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
             .style("fill-opacity", 1)

        //折叠节点的子节点收缩回来
        var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
            .remove();
        nodeExit.select(".text")
            .style("fill-opacity", 1e-6);
        //数据连接,根据目标节点的id绑定数据
        var link = svg.selectAll("path.link")
            .data(links, function(d) { return d.target.id; });
        //增加新连接
        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});
            });
        //原有连接更新位置
        link.transition()
            .duration(duration)
            .attr("d", diagonal);
        //折叠的链接,收缩到源节点处
        link.exit().transition()
            .duration(duration)
            .attr("d", function(d) {
                var o = {x: source.x, y: source.y};
                return diagonal({source: o, target: o});
            })
            .remove();
        // 把旧位置存下来,用以过渡
        nodes.forEach(function(d) {
            d.x0 = d.x;
            d.y0 = d.y;
        });
    }
    //切换折叠与否
    function click(d) {
        if (d.children) {
            d._children = d.children;
            d.children = null;
        } else {
            d.children = d._children;
            d._children = null;
        }
         update(d); //重新渲染
    }
    //删除节点
    $(".delete").click(function(){
        console.log(domInfo);
        removeNode(domInfo)
    });
    function removeNode(d){
        var target = d;
        var children = [];
        if(target.parent){
            target.parent.children.forEach(function(child){
                if (child.id != target.id){
                    children.push(child);
                }
            });
            target.parent.children = children;
        }
        update(d) 
    }
    $(".add").click(function(){
        var  d = domInfo;
        var target = d;
        var children = [];
        var addTheseJSON = {
            "name": "新增未命名",
            'delayed':'0', //延时
        }
        if(target.children){
            children = target.children;
        }
        if(target._children){
            children = target._children;
        }
        if(target.children == undefined && target._children == null){
            children = target.children = [];
        }
        children.push(addTheseJSON);
         update(d);
    });
    $(".move_up").click(function(){
        var d = domInfo;
        var target = d;
        var index;
        if(target.parent){
            var parentChild = target.parent.children;
            parentChild.forEach(function(child,i){
                if (child.id === target.id){
                    index = i;
                }
            });
            indexUp(parentChild,index,parentChild.length);
        }
         update(d)
    });
    $(".move_down").click(function(){
        var d = domInfo;
        var target = d;
        var index;
        if(target.parent){
            var parentChild = target.parent.children;
            parentChild.forEach(function(child,i){
                if (child.id === target.id){
                  index = i;
                }
            });
            indexDown(parentChild,index,parentChild.length);
        }
        update(d)
    });
    /**
     * 数组元素交换位置
     * @param {array} arr 数组
     * @param {number} index1 添加项目的位置
     * @param {number} index2 删除项目的位置
     * index1和index2分别是两个数组的索引值,即是两个要交换元素位置的索引值,如1,5就是数组中下标为1和5的两个元素交换位置
     */
    function swapArray(arr, index1, index2) {
        arr[index1] = arr.splice(index2, 1, arr[index1])[0];
        return arr;
    }
    //上移 将当前数组index索引与前面一个元素互换位置,向数组前面移动一位
    function indexUp(arr,index,length){
        if(index!= 0){
            swapArray(arr, index, index-1);
        }else{
            alert('已经处于置顶,无法上移');
        }
    }
    //下移 将当前数组index索引与后面一个元素互换位置,向数组后面移动一位
    function indexDown(arr,index,length){
        if(index+1 != length){
            swapArray(arr, index, index+1);
        }else{
            alert('已经处于置底,无法下移');
        }
    }
    //保存
    $(".preservation_btn").click(function(){
        console.log(recursion(root));;
    });
    function recursion(data) {
        delete data.x
        delete data.y
        delete data.x0
        delete data.y0
        delete data.depth
        delete data.id
        delete data.parent
        if (data.children) {
            data.children.forEach(item => {
                recursion(item)
            });
        }
        return data
    }
</script>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值