效果图

图片描述

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>d3 tree</title>
    <style type="text/css">
        /* svg style */
.node circle {
    fill: #fff;
    stroke: steelblue;
    stroke-width: 3px;
}

.node text {
    font: 12px sans-serif;
}

.link {
    fill: none;
    stroke: #ccc;
    stroke-width: 2px;
}

    </style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="js/d3.min.js"></script> 
<script type="text/javascript" src="js/tree.js"></script>   
</body>
</html>

json

{
    "success": true,
    "data": {
        "1": {
            "id": "1",
            "parent": "0",
            "title": "根节点",
            "url": "NULL",
            "status": "0",
            "shape": "root",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "2": {
            "id": "2",
            "parent": "1",
            "title": "一级节点1",
            "url": "NULL",
            "status": "1",
            "shape": "suqare",
            "product_id": "300000001",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "3": {
            "id": "3",
            "parent": "2",
            "title": "三级节点1",
            "url": "NULL",
            "status": "1",
            "shape": "square",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "4": {
            "id": "4",
            "parent": "2",
            "title": "三级节点2",
            "url": "NULL",
            "status": "1",
            "shape": "square",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "5": {
            "id": "5",
            "parent": "14",
            "title": "子节点1",
            "url": "NULL",
            "status": 1,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "6": {
            "id": "6",
            "parent": "14",
            "title": "子节点2",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "7": {
            "id": "7",
            "parent": "14",
            "title": "子节点3",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "8": {
            "id": "8",
            "parent": "14",
            "title": "子节点4",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "9": {
            "id": "9",
            "parent": "14",
            "title": "子节点5",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "10": {
            "id": "10",
            "parent": "14",
            "title": "子节点6",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "11": {
            "id": "11",
            "parent": "14",
            "title": "子节点7",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "12": {
            "id": "12",
            "parent": "14",
            "title": "子节点8",
            "url": "NULL",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": true,
            "checkIfHeatAvailable": false
        },
        "13": {
            "id": "13",
            "parent": "14",
            "title": "子节点9",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "14": {
            "id": "14",
            "parent": "3",
            "title": "子节点10",
            "url": "NULL",
            "status": "1",
            "shape": "square",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "15": {
            "id": "15",
            "parent": "4",
            "title": "四级节点2",
            "url": "http://www.baidu.com",
            "status": 0,
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "16": {
            "id": "16",
            "parent": "1",
            "title": "一级节点2",
            "url": "NULL",
            "status": "0",
            "shape": "square",
            "product_id": "300000002",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "17": {
            "id": "17",
            "parent": "16",
            "title": "子节点11",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "18": {
            "id": "18",
            "parent": "16",
            "title": "子节点12",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "19": {
            "id": "19",
            "parent": "16",
            "title": "子节点13",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        },
        "20": {
            "id": "20",
            "parent": "16",
            "title": "子节点14",
            "url": "NULL",
            "status": "0",
            "shape": "round",
            "product_id": "0",
            "checkIfTrendAvailable": false,
            "checkIfHeatAvailable": false
        }
    }
}

tree.js

var api = {
        'tree': 'json/tree.json'
    },
    base = '/img/',
    trendUrl = 'a.php?nodeId=',
    hotUrl = 'b.php?nodeId=';


/* d3.layout.tree */
var margin = {top: 20, right: 20, bottom: 20, left: 120},
    width = $('#chart').parent().width()-$(".sidebar").width(),
    //height = 1900 - margin.top - margin.bottom, // 1900
    //height = $(window).height()-$("#header").height()-$("#footer").height() -58,
    height = $(window).height()-58, 
    //height = 1500;
    _height = 100,
    min_height = 25,
    max_height = 100;

    i = 0,
    duration = 750,
    rootOrig = {},
    root = {};


    var img_width = 14,
        img_height = 14,
        round_img_width = 20,
    round_img_height = 20,
    squ_img_width = 40,
    squ_img_height = 20,
    t_width = 14,
    t_height = 14;

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("#chart").append("svg")
        .attr("width", '100%')
        .attr("height", height + margin.top + margin.bottom)
        .attr("id", "svgWrapper")                           
        .append("g")
        .attr("id", "svg")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ") scale(0.9)");
/* d3.layout.tree */

function renderTreeData(){
    var request = $.ajax({
        url : api.tree,
        type : 'GET',
        data : {},
        async: false, 
        dataType : 'json'
    });

    request.done(function(json){
        if (json.success) {
            var data = json.data;
                rootOrig = data,
                dataArr = [];

            for (var prop in data) {
                dataArr.push(data[prop]);
            }

            var treeData = [],
                dataMap = dataArr.reduce(function(map, node) {
                  map[node.id] = node;
                  return map;
                }, {});

            dataArr.forEach(function(node) {
              // add to parent
              var parent = dataMap[node.parent];
              if (parent) {
                // create child array if it doesn't exist
                (parent.children || (parent.children = []))
                  // add node to child array
                  .push(node);
              } else {
                // parent is null or missing
                treeData.push(node);
              }
            });    

            root = treeData[0];

            root.x0 = 300;
                root.y0 = 0;

            function collapse(d) {
                if (d.status == 1) { // abnormal
                    if (d.children){ // abnormal and has children
                        d.children.forEach(collapse);   
                    } else { // abnormal and has not children
                    }

                } else { // normal
                    if (d.children) { // normal and has children
                        d._children = d.children;
                        d.children = null;
                    } else {

                    }
                }
            }
            root.children.forEach(collapse);
            update(root);
            resizeLayout();

        } else {

        }
    });
}

function update(source) {

    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse(),
    links = tree.links(nodes);

    // Normalize for fixed-depth.
    nodes.forEach(function(d) { d.y = d.depth * 180; });

    // Update the nodes…
    var node = svg.selectAll("g.node")
        .data(nodes, function(d) {return d.id });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
        .attr("id", function(d){
            return "node" + d.id;
        });


    nodeEnter.append("svg:image")
        .attr("xlink:href", function(d){ 
            return getPicture(d);

        })
        .attr('width', function(d){
            return img_width;
        })
        .attr('height', function(d){
            return img_height;
        })
        .attr('x', function(d){
            return -img_width/2;
        })
        .attr('y', function(d){
            return -img_height/2;
        })
        .style('cursor', function(d){ return d.children || d._children ?"pointer":"default" })
        .on("click", click);


    var nodeEnterText = nodeEnter.append("text")
        .attr("x", function(d) { return d.children || d._children ? -20 : 20; })
        .attr("dy", ".35em")
        .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
        .text(function(d) { 
            return d.title; 
        })
        .style("fill-opacity", 1e-6);

    var nodeTrend = nodeEnter.append("svg:a")
            .attr("xlink:href", function(d){
                var url = "javascript:;";
            if (d.checkIfTrendAvailable) {
                url = trendUrl +d.id+ '&name='+ encodeURI(d.title);
            } else {
                url = (d.url !="NULL") ? d.url : "javascript:;";
            }
            return url; 

            })
            .attr("target", function(d){
                var target = "_self";
                if (d.checkIfTrendAvailable) {
                target = "_blank";
            } else {
                target = (d.url !="NULL") ? "_blank" : "_self";
            }
            return target;  

            });

    nodeTrend.append("svg:image")
        .attr("xlink:href", function(d){ 
            var imgUrl;
            if (d.checkIfTrendAvailable) {
                imgUrl =  base + 'trend.png';
            } else {
                if (d.url!="NULL") {
                    imgUrl =  base + 'text.png';
                }
            }
            return imgUrl;          
        })
        .attr('width', function(d){
            return t_width;
        })
        .attr('height', function(d){
            return t_height;
        })
        .attr('x', function(d){
             var len = getLength(d.title),
                 preCountLen = len*6 + 40,
                 countLen = len*6 + 30;

             return d.children || d._children ? -preCountLen : countLen; 
        })
        .attr('y', function(d){
            return -t_height/2;
        });

    var nodeHot = nodeEnter.append("svg:a")
            .attr("xlink:href", function(d){
                var url = "javascript:;";
            if (d.checkIfHeatAvailable) {
                url = hotUrl +d.id+ '&name='+ encodeURI(d.title);
            } 

            return url; 

            })
            .attr("target", function(d){
                var target = "_self";
                if (d.checkIfHeatAvailable) {
                target = "_blank";
            } 
            return target;  

            });

        nodeHot.append("svg:image")
        .attr("xlink:href", function(d){ 
            var imgUrl;
            if (d.checkIfHeatAvailable) {
                imgUrl =  base + 'hot.png';
            } 
            return imgUrl;          
        })
        .attr('width', function(d){
            return t_width;
        })
        .attr('height', function(d){
            return t_height;
        })
        .attr('x', function(d){
             var len = getLength(d.title),
                 preCountLen = len*6 + 60,
                 countLen = len*6 + 50;

             return d.children || d._children ? -preCountLen : countLen; 
        })
        .attr('y', function(d){
            return -t_height/2;
        });


    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

    nodeUpdate.select("image")
        .attr("xlink:href", function(d){ 
                return getPicture(d);
        })
        .attr('width', function(d){
            return img_width;
        })
        .attr('height', function(d){
            return img_height;
        })
        .attr('x', function(d){
            return -img_width/2;
        })
        .attr('y', function(d){
            return -img_height/2;
        })
        .style('cursor', function(d){ return d.children || d._children ?"pointer":"default" });


    nodeUpdate.select("text")
        .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("text")
        .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});
        });

   // Transition links to their new position.
    link.transition()
        .duration(duration)
        .attr('style', function (d){
            if ( d.target.status == 0 ||  d.target.status ==2) {
                // return 'fill:none; stroke:#c7dbd6; stroke-width:2px;'
                return 'fill:none; stroke:#cdcdcd; stroke-width:2px;'
            } else {
                return 'fill:none; stroke:#f3dcdd; stroke-width:2px;'
            }

        })
        .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);
    resizeLayout();
}

function computeDistance(_tree) {

    if ( _tree.children ) {
        var _children = _tree.children,
             length = _children.length;

        for ( var i=0; i<length; ++i ) {
            if ( i<length-1 ) {
                if ( Math.abs(_children[i].x0-_children[i+1].x0)<_height ) {

                    _height = Math.ceil(Math.abs(_children[i].x0-_children[i+1].x0));
                }
            }

            computeDistance(_children[i]);
        }
    }
}

function resizeLayout(){
    _height = 1000;

    computeDistance(root);

    if ( _height<min_height ) {
        var scale = min_height / _height,
            new_height = d3.select('#svgWrapper').attr('height')*scale;

        var _size = tree.size();            
        tree.size([_size[0]*scale, _size[1]]);

        update(root);

    } else if ( _height>max_height && root.children ) {
        var scale = max_height / _height,
            new_height = d3.select('#svgWrapper').attr('height')*scale;


        if ( new_height < 700 ) {
            new_height = 700;
        }

        var _size = tree.size();            
        tree.size([_size[0]*scale, _size[1]]);

        update(root);
    }
}

/**
    @description get the node icon
*/
function getPicture(d){
    if(root.id == d.id) {
        return base + 'logo-new-s.png';
    } else {
        if (d.shape=="round") {
            var path = '';

            if (d.status==0) {
                path = base + 'child.png';
            } else if (d.status==1) {
                path = base + 'child-red.gif';
            } else {
                path = base + 'child-yellow.png';
            }

            return path;
        } else {
            var path = '';

            if (d.status==0) {
                path = base + 'add.png';
            } else if (d.status==1) {
                path = base + 'add-red.png';
            } else {
                path = base + 'add-yellow.png';
            }

            return path;            
        } 
    }       
}

/**
    @description calculate the string length
*/
function getLength(str){

     if (str) {
        return str.replace(/[\u4E00-\u9FA5]|[^\x00-\xff]/ig, "cc").replace(/[A-Z]/g, 'cc').length;
     }

     return 0;
}

$(function(){
    renderTreeData();

    d3.select('#chart').call(d3.behavior.zoom().scaleExtent([0.7, 1.2]).on("zoom", function(){

        var transform = d3.select("#svg").attr('transform');

        d3.event.scale=d3.event.scale-0.15;

        d3.select("#svg").attr("transform",
            transform.slice(0, transform.indexOf(' '))+" scale("+d3.event.scale+")"
        );  
    }));

    d3.select('#chart').call(d3.behavior.drag().on("drag", function() { 
        var transform = d3.select("#svg").attr('transform');

        var x = parseInt(transform.slice(10, transform.indexOf(','))) + d3.event.dx;
        var y = parseInt(transform.slice(transform.indexOf(',')+1, transform.indexOf(')')))+ d3.event.dy;

        if(y>300) {
            y=300;
        }

        d3.select("#svg").attr("transform",
            "translate(" + x +","+y + ") "+transform.slice(transform.indexOf(' ')+1));           
    }));

});

小渝人儿
1.1k 声望849 粉丝

前端工程师


引用和评论

0 条评论